summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cirrus.yml9
-rw-r--r--.github/workflows/l10n.yml105
-rw-r--r--.github/workflows/main.yml8
-rw-r--r--.gitignore3
-rw-r--r--Documentation/MyFirstContribution.txt42
-rw-r--r--Documentation/MyFirstObjectWalk.txt4
-rw-r--r--Documentation/RelNotes/2.33.1.txt138
-rw-r--r--Documentation/RelNotes/2.34.0.txt307
-rw-r--r--Documentation/config.txt9
-rw-r--r--Documentation/config/advice.txt3
-rw-r--r--Documentation/config/branch.txt4
-rw-r--r--Documentation/config/gui.txt2
-rw-r--r--Documentation/config/help.txt16
-rw-r--r--Documentation/config/pack.txt4
-rw-r--r--Documentation/config/pull.txt4
-rw-r--r--Documentation/config/transfer.txt14
-rw-r--r--Documentation/diff-format.txt2
-rw-r--r--Documentation/git-add.txt9
-rw-r--r--Documentation/git-am.txt2
-rw-r--r--Documentation/git-branch.txt3
-rw-r--r--Documentation/git-bugreport.txt4
-rw-r--r--Documentation/git-bundle.txt145
-rw-r--r--Documentation/git-checkout.txt5
-rw-r--r--Documentation/git-column.txt2
-rw-r--r--Documentation/git-config.txt3
-rw-r--r--Documentation/git-cvsserver.txt27
-rw-r--r--Documentation/git-for-each-ref.txt9
-rw-r--r--Documentation/git-help.txt9
-rw-r--r--Documentation/git-http-backend.txt26
-rw-r--r--Documentation/git-index-pack.txt6
-rw-r--r--Documentation/git-maintenance.txt57
-rw-r--r--Documentation/git-merge.txt2
-rw-r--r--Documentation/git-multi-pack-index.txt6
-rw-r--r--Documentation/git-pull.txt27
-rw-r--r--Documentation/git-read-tree.txt23
-rw-r--r--Documentation/git-rebase.txt95
-rw-r--r--Documentation/git-receive-pack.txt5
-rw-r--r--Documentation/git-reset.txt3
-rw-r--r--Documentation/git-rm.txt6
-rw-r--r--Documentation/git-send-pack.txt4
-rw-r--r--Documentation/git-sparse-checkout.txt10
-rw-r--r--Documentation/git-status.txt25
-rw-r--r--Documentation/git-svn.txt1
-rw-r--r--Documentation/git-upload-pack.txt20
-rw-r--r--Documentation/git-version.txt28
-rw-r--r--Documentation/git.txt38
-rw-r--r--Documentation/gitfaq.txt2
-rw-r--r--Documentation/merge-options.txt44
-rw-r--r--Documentation/merge-strategies.txt99
-rw-r--r--Documentation/pretty-options.txt6
-rw-r--r--Documentation/rev-list-options.txt8
-rw-r--r--Documentation/technical/api-parse-options.txt5
-rw-r--r--Documentation/technical/api-trace2.txt54
-rw-r--r--Documentation/technical/directory-rename-detection.txt14
-rw-r--r--Documentation/technical/http-protocol.txt3
-rw-r--r--Documentation/technical/multi-pack-index.txt4
-rw-r--r--Documentation/technical/protocol-v2.txt17
-rw-r--r--Documentation/user-manual.txt2
-rwxr-xr-xGIT-VERSION-GEN2
-rw-r--r--INSTALL15
-rw-r--r--Makefile143
l---------RelNotes2
-rw-r--r--add-interactive.c8
-rw-r--r--advice.c100
-rw-r--r--advice.h35
-rw-r--r--apply.c22
-rw-r--r--archive.c7
-rw-r--r--attr.c15
-rw-r--r--bisect.c26
-rw-r--r--branch.c2
-rw-r--r--builtin.h1
-rw-r--r--builtin/add.c57
-rw-r--r--builtin/am.c10
-rw-r--r--builtin/archive.c4
-rw-r--r--builtin/bisect--helper.c160
-rw-r--r--builtin/branch.c4
-rw-r--r--builtin/bugreport.c51
-rw-r--r--builtin/bundle.c14
-rw-r--r--builtin/checkout.c18
-rw-r--r--builtin/clone.c167
-rw-r--r--builtin/column.c2
-rw-r--r--builtin/commit-graph.c92
-rw-r--r--builtin/commit-tree.c4
-rw-r--r--builtin/commit.c12
-rw-r--r--builtin/credential-cache.c32
-rw-r--r--builtin/credential-store.c2
-rw-r--r--builtin/diff-index.c6
-rw-r--r--builtin/difftool.c159
-rw-r--r--builtin/fast-export.c1
-rw-r--r--builtin/fetch.c99
-rw-r--r--builtin/for-each-ref.c2
-rw-r--r--builtin/gc.c686
-rw-r--r--builtin/grep.c67
-rw-r--r--builtin/hash-object.c6
-rw-r--r--builtin/help.c140
-rw-r--r--builtin/index-pack.c66
-rw-r--r--builtin/log.c2
-rw-r--r--builtin/ls-files.c8
-rw-r--r--builtin/ls-remote.c6
-rw-r--r--builtin/mailsplit.c4
-rw-r--r--builtin/merge.c38
-rw-r--r--builtin/multi-pack-index.c63
-rw-r--r--builtin/mv.c52
-rw-r--r--builtin/notes.c4
-rw-r--r--builtin/pack-objects.c123
-rw-r--r--builtin/prune.c1
-rw-r--r--builtin/pull.c67
-rw-r--r--builtin/push.c12
-rw-r--r--builtin/read-tree.c26
-rw-r--r--builtin/rebase.c391
-rw-r--r--builtin/receive-pack.c26
-rw-r--r--builtin/reflog.c13
-rw-r--r--builtin/remote.c3
-rw-r--r--builtin/repack.c5
-rw-r--r--builtin/replace.c2
-rw-r--r--builtin/reset.c12
-rw-r--r--builtin/rev-parse.c4
-rw-r--r--builtin/revert.c6
-rw-r--r--builtin/rm.c12
-rw-r--r--builtin/send-pack.c9
-rw-r--r--builtin/show-branch.c21
-rw-r--r--builtin/sparse-checkout.c94
-rw-r--r--builtin/stash.c27
-rw-r--r--builtin/submodule--helper.c633
-rw-r--r--builtin/tag.c10
-rw-r--r--builtin/update-index.c4
-rw-r--r--builtin/update-ref.c14
-rw-r--r--builtin/upload-pack.c28
-rw-r--r--builtin/worktree.c1
-rw-r--r--bulk-checkin.c31
-rw-r--r--bundle.c12
-rw-r--r--bundle.h14
-rw-r--r--cache.h76
-rw-r--r--cbtree.h5
-rwxr-xr-xcheck_bindir13
-rw-r--r--checkout.c2
-rwxr-xr-xci/install-dependencies.sh2
-rwxr-xr-xci/install-docker-dependencies.sh4
-rwxr-xr-xci/lib.sh9
-rwxr-xr-xci/run-build-and-tests.sh10
-rw-r--r--commit-graph.c78
-rw-r--r--commit-graph.h8
-rw-r--r--commit.c4
-rw-r--r--commit.h1
-rw-r--r--compat/linux/procinfo.c176
-rw-r--r--compat/mmap.c7
-rw-r--r--compat/nedmalloc/nedmalloc.c2
-rw-r--r--compat/simple-ipc/ipc-unix-socket.c14
-rw-r--r--compat/simple-ipc/ipc-win32.c179
-rw-r--r--compat/stub/procinfo.c11
-rw-r--r--compat/vcbuild/README2
-rw-r--r--compat/win32/lazyload.h14
-rw-r--r--config.c65
-rw-r--r--config.h5
-rw-r--r--config.mak.dev23
-rw-r--r--config.mak.uname15
-rw-r--r--connect.c4
-rw-r--r--connected.c16
-rw-r--r--connected.h2
-rw-r--r--contrib/buildsystems/CMakeLists.txt7
-rw-r--r--contrib/coccinelle/xopen.cocci19
-rw-r--r--contrib/completion/git-completion.bash37
-rw-r--r--contrib/completion/git-completion.tcsh5
-rw-r--r--contrib/credential/gnome-keyring/git-credential-gnome-keyring.c2
-rw-r--r--contrib/credential/libsecret/git-credential-libsecret.c2
-rwxr-xr-xcontrib/rerere-train.sh2
-rw-r--r--credential.c1
-rw-r--r--csum-file.c8
-rwxr-xr-xdetect-compiler9
-rw-r--r--diff-lib.c4
-rw-r--r--diff-merges.c14
-rw-r--r--diff-merges.h2
-rw-r--r--diff.c12
-rw-r--r--diffcore-rename.c68
-rw-r--r--diffcore.h3
-rw-r--r--dir.c224
-rw-r--r--dir.h19
-rw-r--r--editor.c2
-rw-r--r--entry.c13
-rw-r--r--entry.h5
-rw-r--r--environment.c12
-rw-r--r--fetch-negotiator.c1
-rw-r--r--fetch-pack.c22
-rwxr-xr-xgenerate-hooklist.sh20
-rw-r--r--gettext.h24
-rwxr-xr-xgit-bisect.sh89
-rw-r--r--git-compat-util.h9
-rw-r--r--git-curl-compat.h129
-rwxr-xr-xgit-cvsserver.perl5
-rw-r--r--git-rebase--preserve-merges.sh1057
-rwxr-xr-xgit-send-email.perl28
-rw-r--r--git-sh-setup.sh6
-rwxr-xr-xgit-submodule.sh233
-rwxr-xr-xgit-svn.perl1
-rw-r--r--git.c1
-rwxr-xr-xgitweb/gitweb.perl3
-rw-r--r--grep.c136
-rw-r--r--grep.h27
-rw-r--r--help.c37
-rw-r--r--help.h2
-rw-r--r--hook.c42
-rw-r--r--hook.h16
-rw-r--r--http-backend.c6
-rw-r--r--http-push.c29
-rw-r--r--http-walker.c14
-rw-r--r--http.c214
-rw-r--r--http.h47
-rw-r--r--imap-send.c6
-rw-r--r--list-objects.c8
-rw-r--r--list.h5
-rw-r--r--lockfile.h2
-rw-r--r--log-tree.h2
-rw-r--r--ls-refs.c48
-rw-r--r--ls-refs.h4
-rw-r--r--mailmap.c2
-rw-r--r--merge-ort.c266
-rw-r--r--merge-recursive.c62
-rw-r--r--merge.c8
-rw-r--r--mergesort.c121
-rw-r--r--midx.c43
-rw-r--r--midx.h1
-rw-r--r--notes-merge.c2
-rw-r--r--object-file.c77
-rw-r--r--object-name.c2
-rw-r--r--object-store.h12
-rw-r--r--object.h3
-rw-r--r--oid-array.h2
-rw-r--r--oidset.c5
-rw-r--r--oidset.h5
-rw-r--r--pack-bitmap.c41
-rw-r--r--pack-bitmap.h1
-rw-r--r--pack-revindex.h4
-rw-r--r--pack-write.c68
-rw-r--r--pack.h10
-rw-r--r--packfile.c45
-rw-r--r--packfile.h4
-rw-r--r--parse-options.c89
-rw-r--r--parse-options.h9
-rw-r--r--path.c32
-rw-r--r--path.h5
-rw-r--r--pathspec.c7
-rw-r--r--pkt-line.c37
-rw-r--r--pkt-line.h11
-rw-r--r--pretty.c6
-rw-r--r--protocol-caps.c8
-rw-r--r--protocol-caps.h4
-rw-r--r--protocol.c2
-rw-r--r--quote.c17
-rw-r--r--quote.h1
-rw-r--r--range-diff.c32
-rw-r--r--read-cache.c60
-rw-r--r--rebase-interactive.c29
-rw-r--r--rebase-interactive.h2
-rw-r--r--rebase.c5
-rw-r--r--rebase.h1
-rw-r--r--ref-filter.c267
-rw-r--r--ref-filter.h10
-rw-r--r--reflog-walk.c3
-rw-r--r--refs.c63
-rw-r--r--refs.h20
-rw-r--r--refs/debug.c15
-rw-r--r--refs/files-backend.c278
-rw-r--r--refs/packed-backend.c15
-rw-r--r--refs/ref-cache.c87
-rw-r--r--refs/ref-cache.h26
-rw-r--r--refs/refs-internal.h78
-rw-r--r--remote-curl.c19
-rw-r--r--remote.c20
-rw-r--r--repo-settings.c115
-rw-r--r--repository.c24
-rw-r--r--repository.h35
-rw-r--r--reset.c3
-rw-r--r--revision.c58
-rw-r--r--revision.h8
-rw-r--r--run-command.c178
-rw-r--r--run-command.h99
-rw-r--r--send-pack.c6
-rw-r--r--sequencer.c231
-rw-r--r--sequencer.h8
-rw-r--r--serve.c198
-rw-r--r--serve.h12
-rw-r--r--setup.c8
-rw-r--r--shallow.h4
-rw-r--r--simple-ipc.h27
-rw-r--r--sparse-index.c77
-rw-r--r--sparse-index.h3
-rw-r--r--strbuf.h2
-rw-r--r--string-list.c8
-rw-r--r--string-list.h5
-rw-r--r--strvec.h8
-rw-r--r--submodule-config.c5
-rw-r--r--submodule-config.h4
-rw-r--r--submodule.c154
-rw-r--r--submodule.h19
-rw-r--r--t/README28
-rw-r--r--t/helper/test-bitmap.c10
-rw-r--r--t/helper/test-dump-untracked-cache.c6
-rw-r--r--t/helper/test-mergesort.c368
-rw-r--r--t/helper/test-parse-options.c1
-rw-r--r--t/helper/test-run-command.c9
-rw-r--r--t/helper/test-serve-v2.c14
-rw-r--r--t/helper/test-simple-ipc.c233
-rw-r--r--t/helper/test-submodule-nested-repo-config.c4
-rw-r--r--t/lib-httpd/apache.conf7
-rw-r--r--t/lib-rebase.sh56
-rw-r--r--t/lib-subtest.sh95
-rwxr-xr-xt/perf/aggregate.perl4
-rwxr-xr-xt/perf/p0071-sort.sh40
-rwxr-xr-xt/perf/p3400-rebase.sh2
-rwxr-xr-xt/perf/p5326-multi-pack-bitmaps.sh15
-rwxr-xr-xt/perf/run2
-rwxr-xr-xt/t0000-basic.sh471
-rwxr-xr-xt/t0004-unwritable.sh3
-rwxr-xr-xt/t0011-hashmap.sh2
-rwxr-xr-xt/t0012-help.sh65
-rwxr-xr-xt/t0016-oidmap.sh2
-rwxr-xr-xt/t0017-env-helper.sh1
-rwxr-xr-xt/t0018-advice.sh1
-rwxr-xr-xt/t0021-conversion.sh71
-rwxr-xr-xt/t0030-stripspace.sh1
-rwxr-xr-xt/t0040-parse-options.sh5
-rwxr-xr-xt/t0060-path-utils.sh26
-rwxr-xr-xt/t0063-string-list.sh1
-rwxr-xr-xt/t0071-sort.sh11
-rwxr-xr-xt/t0090-cache-tree.sh1
-rwxr-xr-xt/t0091-bugreport.sh1
-rw-r--r--t/t0210/scrub_normal.perl6
-rw-r--r--t/t0211/scrub_perf.perl5
-rw-r--r--t/t0212/parse_events.perl5
-rwxr-xr-xt/t0301-credential-cache.sh32
-rwxr-xr-xt/t1013-read-tree-submodule.sh1
-rwxr-xr-xt/t1091-sparse-checkout-builtin.sh88
-rwxr-xr-xt/t1092-sparse-checkout-compatibility.sh206
-rwxr-xr-xt/t1400-update-ref.sh34
-rwxr-xr-xt/t1405-main-ref-store.sh18
-rwxr-xr-xt/t1410-reflog.sh4
-rwxr-xr-xt/t1430-bad-ref-name.sh2
-rwxr-xr-xt/t1502-rev-parse-parseopt.sh54
-rwxr-xr-xt/t1503-rev-parse-verify.sh2
-rwxr-xr-xt/t1600-index.sh13
-rwxr-xr-xt/t1700-split-index.sh34
-rwxr-xr-xt/t2021-checkout-overwrite.sh1
-rwxr-xr-xt/t2402-worktree-list.sh2
-rwxr-xr-xt/t2500-untracked-overwriting.sh244
-rwxr-xr-xt/t3200-branch.sh13
-rwxr-xr-xt/t3203-branch-output.sh4
-rwxr-xr-xt/t3320-notes-merge-worktrees.sh12
-rwxr-xr-xt/t3403-rebase-skip.sh13
-rwxr-xr-xt/t3404-rebase-interactive.sh90
-rwxr-xr-xt/t3407-rebase-abort.sh105
-rwxr-xr-xt/t3408-rebase-multi-line.sh10
-rwxr-xr-xt/t3409-rebase-preserve-merges.sh130
-rwxr-xr-xt/t3410-rebase-preserve-dropped-merges.sh90
-rwxr-xr-xt/t3411-rebase-preserve-around-merges.sh80
-rwxr-xr-xt/t3412-rebase-root.sh37
-rwxr-xr-xt/t3414-rebase-preserve-onto.sh85
-rwxr-xr-xt/t3418-rebase-continue.sh33
-rwxr-xr-xt/t3421-rebase-topology-linear.sh19
-rwxr-xr-xt/t3422-rebase-incompatible-options.sh11
-rwxr-xr-xt/t3425-rebase-topology-merges.sh151
-rwxr-xr-xt/t3427-rebase-subtree.sh26
-rwxr-xr-xt/t3430-rebase-merges.sh38
-rwxr-xr-xt/t3435-rebase-gpg-sign.sh1
-rwxr-xr-xt/t3501-revert-cherry-pick.sh16
-rwxr-xr-xt/t3507-cherry-pick-conflict.sh17
-rwxr-xr-xt/t3510-cherry-pick-sequence.sh1
-rwxr-xr-xt/t3602-rm-sparse-checkout.sh40
-rwxr-xr-xt/t3705-add-sparse-checkout.sh68
-rwxr-xr-xt/t3903-stash.sh58
-rwxr-xr-xt/t4013-diff-various.sh2
-rw-r--r--t/t4018/java-class-member-function4
-rw-r--r--t/t4018/java-enum-constant6
-rw-r--r--t/t4018/java-method-return-generic-bounded9
-rw-r--r--t/t4018/java-method-return-generic-wildcard9
-rw-r--r--t/t4018/java-nested-field6
-rw-r--r--t/t4018/php-enum4
-rwxr-xr-xt/t4045-diff-relative.sh53
-rwxr-xr-xt/t4060-diff-submodule-option-diff-format.sh159
-rwxr-xr-xt/t4103-apply-binary.sh23
-rwxr-xr-xt/t4108-apply-threeway.sh45
-rwxr-xr-xt/t4151-am-abort.sh39
-rwxr-xr-xt/t4210-log-i18n.sh7
-rwxr-xr-xt/t5300-pack-object.sh104
-rwxr-xr-xt/t5304-prune.sh1
-rwxr-xr-xt/t5312-prune-corruption.sh48
-rwxr-xr-xt/t5318-commit-graph.sh19
-rwxr-xr-xt/t5319-multi-pack-index.sh12
-rwxr-xr-xt/t5323-pack-redundant.sh4
-rwxr-xr-xt/t5326-multi-pack-bitmaps.sh30
-rwxr-xr-xt/t5510-fetch.sh13
-rwxr-xr-xt/t5516-fetch-push.sh23
-rwxr-xr-xt/t5520-pull.sh44
-rwxr-xr-xt/t5521-pull-options.sh4
-rwxr-xr-xt/t5524-pull-msg.sh4
-rwxr-xr-xt/t5549-fetch-push-http.sh72
-rwxr-xr-xt/t5551-http-fetch-smart.sh33
-rwxr-xr-xt/t5553-set-upstream.sh14
-rwxr-xr-xt/t5555-http-smart-common.sh161
-rw-r--r--t/t5562/invoke-with-content-length.pl16
-rwxr-xr-xt/t5582-fetch-negative-refspec.sh1
-rwxr-xr-xt/t5600-clone-fail-cleanup.sh4
-rwxr-xr-xt/t5604-clone-reference.sh4
-rwxr-xr-xt/t5606-clone-options.sh12
-rwxr-xr-xt/t5607-clone-bundle.sh5
-rwxr-xr-xt/t5701-git-serve.sh75
-rwxr-xr-xt/t5702-protocol-v2.sh19
-rwxr-xr-xt/t5703-upload-pack-ref-in-want.sh208
-rwxr-xr-xt/t5704-protocol-violations.sh15
-rwxr-xr-xt/t5705-session-id-in-capabilities.sh11
-rwxr-xr-xt/t6000-rev-list-misc.sh31
-rwxr-xr-xt/t6001-rev-list-graft.sh3
-rwxr-xr-xt/t6030-bisect-porcelain.sh18
-rwxr-xr-xt/t6050-replace.sh2
-rwxr-xr-xt/t6120-describe.sh6
-rwxr-xr-xt/t6300-for-each-ref.sh264
-rwxr-xr-xt/t6402-merge-rename.sh18
-rwxr-xr-xt/t6409-merge-subtree.sh6
-rwxr-xr-xt/t6415-merge-dir-to-symlink.sh6
-rwxr-xr-xt/t6417-merge-ours-theirs.sh10
-rwxr-xr-xt/t6424-merge-unrelated-index-changes.sh1
-rwxr-xr-xt/t6430-merge-recursive.sh4
-rwxr-xr-xt/t6436-merge-overwrite.sh3
-rwxr-xr-xt/t6500-gc.sh5
-rwxr-xr-xt/t7002-mv-sparse-checkout.sh189
-rwxr-xr-xt/t7004-tag.sh4
-rwxr-xr-xt/t7030-verify-tag.sh4
-rwxr-xr-xt/t7064-wtstatus-pv2.sh5
-rwxr-xr-xt/t7112-reset-submodule.sh1
-rwxr-xr-xt/t7201-co.sh1
-rwxr-xr-xt/t7500-commit-template-squash-signoff.sh9
-rwxr-xr-xt/t7505-prepare-commit-msg-hook.sh1
-rwxr-xr-xt/t7517-per-repo-email.sh13
-rwxr-xr-xt/t7519-status-fsmonitor.sh70
-rwxr-xr-xt/t7600-merge.sh1
-rwxr-xr-xt/t7601-merge-pull-config.sh244
-rwxr-xr-xt/t7603-merge-reduce-heads.sh2
-rwxr-xr-xt/t7800-difftool.sh74
-rwxr-xr-xt/t7814-grep-recurse-submodules.sh106
-rwxr-xr-xt/t7900-maintenance.sh132
-rwxr-xr-xt/t9001-send-email.sh62
-rwxr-xr-xt/t9002-column.sh18
-rwxr-xr-xt/t9351-fast-export-anonymize.sh10
-rwxr-xr-xt/t9400-git-cvsserver-server.sh9
-rwxr-xr-xt/t9902-completion.sh15
-rw-r--r--t/test-lib-functions.sh107
-rw-r--r--t/test-lib.sh40
-rw-r--r--trace.h2
-rw-r--r--trace2.c44
-rw-r--r--trace2.h43
-rw-r--r--trace2/tr2_tgt.h8
-rw-r--r--trace2/tr2_tgt_event.c43
-rw-r--r--trace2/tr2_tgt_normal.c33
-rw-r--r--trace2/tr2_tgt_perf.c31
-rw-r--r--trace2/tr2_tls.c1
-rw-r--r--transport-helper.c18
-rw-r--r--transport-internal.h2
-rw-r--r--transport.c40
-rw-r--r--transport.h4
-rw-r--r--tree-diff.c4
-rw-r--r--unicode-width.h44
-rw-r--r--unpack-trees.c89
-rw-r--r--unpack-trees.h14
-rw-r--r--upload-pack.c50
-rw-r--r--upload-pack.h14
-rw-r--r--userdiff.c18
-rw-r--r--wrapper.c16
-rw-r--r--write-or-die.c12
-rw-r--r--wt-status.c6
468 files changed, 12044 insertions, 7143 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index c2f5fe385a..e114ffee1a 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -2,8 +2,15 @@ env:
CIRRUS_CLONE_DEPTH: 1
freebsd_12_task:
+ env:
+ GIT_PROVE_OPTS: "--timer --jobs 10"
+ GIT_TEST_OPTS: "--no-chain-lint --no-bin-wrappers"
+ MAKEFLAGS: "-j4"
+ DEFAULT_TEST_TARGET: prove
+ DEVELOPER: 1
freebsd_instance:
- image: freebsd-12-1-release-amd64
+ image_family: freebsd-12-2
+ memory: 2G
install_script:
pkg install -y gettext gmake perl5
create_user_script:
diff --git a/.github/workflows/l10n.yml b/.github/workflows/l10n.yml
new file mode 100644
index 0000000000..27f72f0ff3
--- /dev/null
+++ b/.github/workflows/l10n.yml
@@ -0,0 +1,105 @@
+name: git-l10n
+
+on: [push, pull_request_target]
+
+jobs:
+ git-po-helper:
+ if: >-
+ endsWith(github.repository, '/git-po') ||
+ contains(github.head_ref, 'l10n') ||
+ contains(github.ref, 'l10n')
+ runs-on: ubuntu-latest
+ permissions:
+ pull-requests: write
+ steps:
+ - name: Setup base and head objects
+ id: setup-tips
+ run: |
+ if test "${{ github.event_name }}" = "pull_request_target"
+ then
+ base=${{ github.event.pull_request.base.sha }}
+ head=${{ github.event.pull_request.head.sha }}
+ else
+ base=${{ github.event.before }}
+ head=${{ github.event.after }}
+ fi
+ echo "::set-output name=base::$base"
+ echo "::set-output name=head::$head"
+ - name: Run partial clone
+ run: |
+ git -c init.defaultBranch=master init --bare .
+ git remote add \
+ --mirror=fetch \
+ origin \
+ https://github.com/${{ github.repository }}
+ # Fetch tips that may be unreachable from github.ref:
+ # - For a forced push, "$base" may be unreachable.
+ # - For a "pull_request_target" event, "$head" may be unreachable.
+ args=
+ for commit in \
+ ${{ steps.setup-tips.outputs.base }} \
+ ${{ steps.setup-tips.outputs.head }}
+ do
+ case $commit in
+ *[^0]*)
+ args="$args $commit"
+ ;;
+ *)
+ # Should not fetch ZERO-OID.
+ ;;
+ esac
+ done
+ git -c protocol.version=2 fetch \
+ --progress \
+ --no-tags \
+ --no-write-fetch-head \
+ --filter=blob:none \
+ origin \
+ ${{ github.ref }} \
+ $args
+ - uses: actions/setup-go@v2
+ with:
+ go-version: '>=1.16'
+ - name: Install git-po-helper
+ run: go install github.com/git-l10n/git-po-helper@main
+ - name: Install other dependencies
+ run: |
+ sudo apt-get update -q &&
+ sudo apt-get install -q -y gettext
+ - name: Run git-po-helper
+ id: check-commits
+ run: |
+ exit_code=0
+ git-po-helper check-commits \
+ --github-action-event="${{ github.event_name }}" -- \
+ ${{ steps.setup-tips.outputs.base }}..${{ steps.setup-tips.outputs.head }} \
+ >git-po-helper.out 2>&1 || exit_code=$?
+ if test $exit_code -ne 0 || grep -q WARNING git-po-helper.out
+ then
+ # Remove ANSI colors which are proper for console logs but not
+ # proper for PR comment.
+ echo "COMMENT_BODY<<EOF" >>$GITHUB_ENV
+ perl -pe 's/\e\[[0-9;]*m//g; s/\bEOF$//g' git-po-helper.out >>$GITHUB_ENV
+ echo "EOF" >>$GITHUB_ENV
+ fi
+ cat git-po-helper.out
+ exit $exit_code
+ - name: Create comment in pull request for report
+ uses: mshick/add-pr-comment@v1
+ if: >-
+ always() &&
+ github.event_name == 'pull_request_target' &&
+ env.COMMENT_BODY != ''
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ repo-token-user-login: 'github-actions[bot]'
+ message: >
+ ${{ steps.check-commits.outcome == 'failure' && 'Errors and warnings' || 'Warnings' }}
+ found by [git-po-helper](https://github.com/git-l10n/git-po-helper#readme) in workflow
+ [#${{ github.run_number }}](${{ env.GITHUB_SERVER_URL }}/${{ github.repository }}/actions/runs/${{ github.run_id }}):
+
+ ```
+
+ ${{ env.COMMENT_BODY }}
+
+ ```
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 47876a4f02..59acc35d37 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -198,7 +198,6 @@ jobs:
shell: bash
env:
NO_SVN_TESTS: 1
- GIT_TEST_SKIP_REBASE_P: 1
run: ci/run-test-slice.sh ${{matrix.nr}} 10
- name: ci/print-test-failures.sh
if: failure()
@@ -232,6 +231,9 @@ jobs:
- jobname: linux-gcc-default
cc: gcc
pool: ubuntu-latest
+ - jobname: linux-leaks
+ cc: gcc
+ pool: ubuntu-latest
env:
CC: ${{matrix.vector.cc}}
jobname: ${{matrix.vector.jobname}}
@@ -259,6 +261,8 @@ jobs:
image: alpine
- jobname: Linux32
image: daald/ubuntu32:xenial
+ - jobname: pedantic
+ image: fedora
env:
jobname: ${{matrix.vector.jobname}}
runs-on: ubuntu-latest
@@ -271,7 +275,7 @@ jobs:
if: failure()
- name: Upload failed tests' directories
if: failure() && env.FAILED_TEST_ARTIFACTS != ''
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v1
with:
name: failed-tests-${{matrix.vector.jobname}}
path: ${{env.FAILED_TEST_ARTIFACTS}}
diff --git a/.gitignore b/.gitignore
index 311841f9be..054249b20a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -125,7 +125,6 @@
/git-range-diff
/git-read-tree
/git-rebase
-/git-rebase--preserve-merges
/git-receive-pack
/git-reflog
/git-remote
@@ -190,6 +189,7 @@
/gitweb/static/gitweb.min.*
/config-list.h
/command-list.h
+/hook-list.h
*.tar.gz
*.dsc
*.deb
@@ -224,6 +224,7 @@
*.lib
*.res
*.sln
+*.sp
*.suo
*.ncb
*.vcproj
diff --git a/Documentation/MyFirstContribution.txt b/Documentation/MyFirstContribution.txt
index 015cf24631..b20bc8e914 100644
--- a/Documentation/MyFirstContribution.txt
+++ b/Documentation/MyFirstContribution.txt
@@ -1029,22 +1029,42 @@ kidding - be patient!)
[[v2-git-send-email]]
=== Sending v2
-Skip ahead to <<reviewing,Responding to Reviews>> for information on how to
-handle comments from reviewers. Continue this section when your topic branch is
-shaped the way you want it to look for your patchset v2.
+This section will focus on how to send a v2 of your patchset. To learn what
+should go into v2, skip ahead to <<reviewing,Responding to Reviews>> for
+information on how to handle comments from reviewers.
+
+We'll reuse our `psuh` topic branch for v2. Before we make any changes, we'll
+mark the tip of our v1 branch for easy reference:
-When you're ready with the next iteration of your patch, the process is fairly
-similar.
+----
+$ git checkout psuh
+$ git branch psuh-v1
+----
-First, generate your v2 patches again:
+Refine your patch series by using `git rebase -i` to adjust commits based upon
+reviewer comments. Once the patch series is ready for submission, generate your
+patches again, but with some new flags:
----
-$ git format-patch -v2 --cover-letter -o psuh/ master..psuh
+$ git format-patch -v2 --cover-letter -o psuh/ --range-diff master..psuh-v1 master..
----
-This will add your v2 patches, all named like `v2-000n-my-commit-subject.patch`,
-to the `psuh/` directory. You may notice that they are sitting alongside the v1
-patches; that's fine, but be careful when you are ready to send them.
+The `--range-diff master..psuh-v1` parameter tells `format-patch` to include a
+range-diff between `psuh-v1` and `psuh` in the cover letter (see
+linkgit:git-range-diff[1]). This helps tell reviewers about the differences
+between your v1 and v2 patches.
+
+The `-v2` parameter tells `format-patch` to output your patches
+as version "2". For instance, you may notice that your v2 patches are
+all named like `v2-000n-my-commit-subject.patch`. `-v2` will also format
+your patches by prefixing them with "[PATCH v2]" instead of "[PATCH]",
+and your range-diff will be prefaced with "Range-diff against v1".
+
+Afer you run this command, `format-patch` will output the patches to the `psuh/`
+directory, alongside the v1 patches. Using a single directory makes it easy to
+refer to the old v1 patches while proofreading the v2 patches, but you will need
+to be careful to send out only the v2 patches. We will use a pattern like
+"psuh/v2-*.patch" (not "psuh/*.patch", which would match v1 and v2 patches).
Edit your cover letter again. Now is a good time to mention what's different
between your last version and now, if it's something significant. You do not
@@ -1082,7 +1102,7 @@ to the command:
----
$ git send-email --to=target@example.com
--in-reply-to="<foo.12345.author@example.com>"
- psuh/v2*
+ psuh/v2-*.patch
----
[[single-patch]]
diff --git a/Documentation/MyFirstObjectWalk.txt b/Documentation/MyFirstObjectWalk.txt
index 2d10eea7a9..45eb84d8b4 100644
--- a/Documentation/MyFirstObjectWalk.txt
+++ b/Documentation/MyFirstObjectWalk.txt
@@ -691,7 +691,7 @@ help understand. In our case, that means we omit trees and blobs not directly
referenced by `HEAD` or `HEAD`'s history, because we begin the walk with only
`HEAD` in the `pending` list.)
-First, we'll need to `#include "list-objects-filter-options.h`" and set up the
+First, we'll need to `#include "list-objects-filter-options.h"` and set up the
`struct list_objects_filter_options` at the top of the function.
----
@@ -779,7 +779,7 @@ Count all the objects within and modify the print statement:
while ((oid = oidset_iter_next(&oit)))
omitted_count++;
- printf("commits %d\nblobs %d\ntags %d\ntrees%d\nomitted %d\n",
+ printf("commits %d\nblobs %d\ntags %d\ntrees %d\nomitted %d\n",
commit_count, blob_count, tag_count, tree_count, omitted_count);
----
diff --git a/Documentation/RelNotes/2.33.1.txt b/Documentation/RelNotes/2.33.1.txt
new file mode 100644
index 0000000000..b71738e654
--- /dev/null
+++ b/Documentation/RelNotes/2.33.1.txt
@@ -0,0 +1,138 @@
+Git 2.33.1 Release Notes
+========================
+
+This primarily is to backport various fixes accumulated during the
+development towards Git 2.34, the next feature release.
+
+
+Fixes since v2.33
+-----------------
+
+ * The unicode character width table (used for output alignment) has
+ been updated.
+
+ * Input validation of "git pack-objects --stdin-packs" has been
+ corrected.
+
+ * Bugfix for common ancestor negotiation recently introduced in "git
+ push" codepath.
+
+ * "git pull" had various corner cases that were not well thought out
+ around its --rebase backend, e.g. "git pull --ff-only" did not stop
+ but went ahead and rebased when the history on other side is not a
+ descendant of our history. The series tries to fix them up.
+
+ * "git apply" miscounted the bytes and failed to read to the end of
+ binary hunks.
+
+ * "git range-diff" code clean-up.
+
+ * "git commit --fixup" now works with "--edit" again, after it was
+ broken in v2.32.
+
+ * Use upload-artifacts v1 (instead of v2) for 32-bit linux, as the
+ new version has a blocker bug for that architecture.
+
+ * Checking out all the paths from HEAD during the last conflicted
+ step in "git rebase" and continuing would cause the step to be
+ skipped (which is expected), but leaves MERGE_MSG file behind in
+ $GIT_DIR and confuses the next "git commit", which has been
+ corrected.
+
+ * Various bugs in "git rebase -r" have been fixed.
+
+ * mmap() imitation used to call xmalloc() that dies upon malloc()
+ failure, which has been corrected to just return an error to the
+ caller to be handled.
+
+ * "git diff --relative" segfaulted and/or produced incorrect result
+ when there are unmerged paths.
+
+ * The delayed checkout code path in "git checkout" etc. were chatty
+ even when --quiet and/or --no-progress options were given.
+
+ * "git branch -D <branch>" used to refuse to remove a broken branch
+ ref that points at a missing commit, which has been corrected.
+
+ * Build update for Apple clang.
+
+ * The parser for the "--nl" option of "git column" has been
+ corrected.
+
+ * "git upload-pack" which runs on the other side of "git fetch"
+ forgot to take the ref namespaces into account when handling
+ want-ref requests.
+
+ * The sparse-index support can corrupt the index structure by storing
+ a stale and/or uninitialized data, which has been corrected.
+
+ * Buggy tests could damage repositories outside the throw-away test
+ area we created. We now by default export GIT_CEILING_DIRECTORIES
+ to limit the damage from such a stray test.
+
+ * Even when running "git send-email" without its own threaded
+ discussion support, a threading related header in one message is
+ carried over to the subsequent message to result in an unwanted
+ threading, which has been corrected.
+
+ * The output from "git fast-export", when its anonymization feature
+ is in use, showed an annotated tag incorrectly.
+
+ * Recent "diff -m" changes broke "gitk", which has been corrected.
+
+ * "git maintenance" scheduler fix for macOS.
+
+ * A pathname in an advice message has been made cut-and-paste ready.
+
+ * The "git apply -3" code path learned not to bother the lower level
+ merge machinery when the three-way merge can be trivially resolved
+ without the content level merge.
+
+ * The code that optionally creates the *.rev reverse index file has
+ been optimized to avoid needless computation when it is not writing
+ the file out.
+
+ * "git range-diff -I... <range> <range>" segfaulted, which has been
+ corrected.
+
+ * The order in which various files that make up a single (conceptual)
+ packfile has been reevaluated and straightened up. This matters in
+ correctness, as an incomplete set of files must not be shown to a
+ running Git.
+
+ * The "mode" word is useless in a call to open(2) that does not
+ create a new file. Such a call in the files backend of the ref
+ subsystem has been cleaned up.
+
+ * "git update-ref --stdin" failed to flush its output as needed,
+ which potentially led the conversation to a deadlock.
+
+ * When "git am --abort" fails to abort correctly, it still exited
+ with exit status of 0, which has been corrected.
+
+ * Correct nr and alloc members of strvec struct to be of type size_t.
+
+ * "git stash", where the tentative change involves changing a
+ directory to a file (or vice versa), was confused, which has been
+ corrected.
+
+ * "git clone" from a repository whose HEAD is unborn into a bare
+ repository didn't follow the branch name the other side used, which
+ is corrected.
+
+ * "git cvsserver" had a long-standing bug in its authentication code,
+ which has finally been corrected (it is unclear and is a separate
+ question if anybody is seriously using it, though).
+
+ * "git difftool --dir-diff" mishandled symbolic links.
+
+ * Sensitive data in the HTTP trace were supposed to be redacted, but
+ we failed to do so in HTTP/2 requests.
+
+ * "make clean" has been updated to remove leftover .depend/
+ directories, even when it is not told to use them to compute header
+ dependencies.
+
+ * Protocol v0 clients can get stuck parsing a malformed feature line.
+
+Also contains various documentation updates and code clean-ups.
diff --git a/Documentation/RelNotes/2.34.0.txt b/Documentation/RelNotes/2.34.0.txt
new file mode 100644
index 0000000000..0bfeaea546
--- /dev/null
+++ b/Documentation/RelNotes/2.34.0.txt
@@ -0,0 +1,307 @@
+Git 2.34 Release Notes
+======================
+
+Updates since Git 2.33
+----------------------
+
+UI, Workflows & Features
+
+ * Pathname expansion (like "~username/") learned a way to specify a
+ location relative to Git installation (e.g. its $sharedir which is
+ $(prefix)/share), with "%(prefix)".
+
+ * Use `ort` instead of `recursive` as the default merge strategy.
+
+ * The userdiff pattern for "java" language has been updated.
+
+ * "git rebase" by default skips changes that are equivalent to
+ commits that are already in the history the branch is rebased onto;
+ give messages when this happens to let the users be aware of
+ skipped commits, and also teach them how to tell "rebase" to keep
+ duplicated changes.
+
+ * The advice message that "git cherry-pick" gives when it asks
+ conflicted replay of a commit to be resolved by the end user has
+ been updated.
+
+ * After "git clone --recurse-submodules", all submodules are cloned
+ but they are not by default recursed into by other commands. With
+ submodule.stickyRecursiveClone configuration set, submodule.recurse
+ configuration is set to true in a repository created by "clone"
+ with "--recurse-submodules" option.
+
+ * The logic for auto-correction of misspelt subcommands learned to go
+ interactive when the help.autocorrect configuration variable is set
+ to 'prompt'.
+
+ * "git maintenance" scheduler learned to use systemd timers as a
+ possible backend.
+
+ * "git diff --submodule=diff" showed failure from run_command() when
+ trying to run diff inside a submodule, when the user manually
+ removes the submodule directory.
+
+ * "git bundle unbundle" learned to show progress display.
+
+ * In cone mode, the sparse-index code path learned to remove ignored
+ files (like build artifacts) outside the sparse cone, allowing the
+ entire directory outside the sparse cone to be removed, which is
+ especially useful when the sparse patterns change.
+
+ * Taking advantage of the CGI interface, http-backend has been
+ updated to enable protocol v2 automatically when the other side
+ asks for it.
+
+ * The credential-cache helper has been adjusted to Windows.
+
+ * The error in "git help no-such-git-command" is handled better.
+
+ * The unicode character width table (used for output alignment) has
+ been updated.
+
+ * The ref iteration code used to optionally allow dangling refs to be
+ shown, which has been tightened up.
+
+ * "git add", "git mv", and "git rm" have been adjusted to avoid
+ updating paths outside of the sparse-checkout definition unless
+ the user specifies a "--sparse" option.
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * "git bisect" spawned "git show-branch" only to pretty-print the
+ title of the commit after checking out the next version to be
+ tested; this has been rewritten in C.
+
+ * "git add" can work better with the sparse index.
+
+ * Support for ancient versions of cURL library (pre 7.19.4) has been
+ dropped.
+
+ * A handful of tests that assumed implementation details of files
+ backend for refs have been cleaned up.
+
+ * trace2 logs learned to show parent process name to see in what
+ context Git was invoked.
+
+ * Loading of ref tips to prepare for common ancestry negotiation in
+ "git fetch-pack" has been optimized by taking advantage of the
+ commit graph when available.
+
+ * Remind developers that the userdiff patterns should be kept simple
+ and permissive, assuming that the contents they apply are always
+ syntactically correct.
+
+ * The current implementation of GIT_TEST_FAIL_PREREQS is broken in
+ that checking for the lack of a prerequisite would not work. Avoid
+ the use of "if ! test_have_prereq X" in a test script.
+
+ * The revision traversal API has been optimized by taking advantage
+ of the commit-graph, when available, to determine if a commit is
+ reachable from any of the existing refs.
+
+ * "git fetch --quiet" optimization to avoid useless computation of
+ info that will never be displayed.
+
+ * Callers from older advice_config[] based API has been updated to
+ use the newer advice_if_enabled() and advice_enabled() API.
+
+ * Teach "test_pause" and "debug" helpers to allow using the HOME and
+ TERM environment variables the user usually uses.
+
+ * "make INSTALL_STRIP=-s install" allows the installation step to use
+ "install -s" to strip the binaries as they get installed.
+
+ * Code that handles large number of refs in the "git fetch" code
+ path has been optimized.
+
+ * The reachability bitmap file used to be generated only for a single
+ pack, but now we've learned to generate bitmaps for history that
+ span across multiple packfiles.
+
+ * The code to make "git grep" recurse into submodules has been
+ updated to migrate away from the "add submodule's object store as
+ an alternate object store" mechanism (which is suboptimal).
+
+ * The tracing of process ancestry information has been enhanced.
+
+ * Reduce number of write(2) system calls while sending the
+ ref advertisement.
+
+ * Update the build procedure to use the "-pedantic" build when
+ DEVELOPER makefile macro is in effect.
+
+ * Large part of "git submodule add" gets rewritten in C.
+
+ * The run-command API has been updated so that the callers can easily
+ ask the file descriptors open for packfiles to be closed immediately
+ before spawning commands that may trigger auto-gc.
+
+ * An oddball OPTION_ARGUMENT feature has been removed from the
+ parse-options API.
+
+ * The mergesort implementation used to sort linked list has been
+ optimized.
+
+ * Remove external declaration of functions that no longer exist.
+
+ * "git multi-pack-index write --bitmap" learns to propagate the
+ hashcache from original bitmap to resulting bitmap.
+
+ * CI learns to run the leak sanitizer builds.
+
+ * "git grep --recurse-submodules" takes trees and blobs from the
+ submodule repository, but the textconv settings when processing a
+ blob from the submodule is not taken from the submodule repository.
+ A test is added to demonstrate the issue, without fixing it.
+
+ * Teach "git help -c" into helping the command line completion of
+ configuration variables.
+
+ * When "git cmd -h" shows more than one line of usage text (e.g.
+ the cmd subcommand may take sub-sub-command), parse-options API
+ learned to align these lines, even across i18n/l10n.
+
+ * Prevent "make sparse" from running for the source files that
+ haven't been modified.
+
+
+Fixes since v2.33
+-----------------
+
+ * Input validation of "git pack-objects --stdin-packs" has been
+ corrected.
+
+ * Bugfix for common ancestor negotiation recently introduced in "git
+ push" code path.
+
+ * "git pull" had various corner cases that were not well thought out
+ around its --rebase backend, e.g. "git pull --ff-only" did not stop
+ but went ahead and rebased when the history on other side is not a
+ descendant of our history. The series tries to fix them up.
+
+ * "git apply" miscounted the bytes and failed to read to the end of
+ binary hunks.
+
+ * "git range-diff" code clean-up.
+
+ * "git commit --fixup" now works with "--edit" again, after it was
+ broken in v2.32.
+
+ * Use upload-artifacts v1 (instead of v2) for 32-bit linux, as the
+ new version has a blocker bug for that architecture.
+
+ * Checking out all the paths from HEAD during the last conflicted
+ step in "git rebase" and continuing would cause the step to be
+ skipped (which is expected), but leaves MERGE_MSG file behind in
+ $GIT_DIR and confuses the next "git commit", which has been
+ corrected.
+
+ * Various bugs in "git rebase -r" have been fixed.
+
+ * mmap() imitation used to call xmalloc() that dies upon malloc()
+ failure, which has been corrected to just return an error to the
+ caller to be handled.
+
+ * "git diff --relative" segfaulted and/or produced incorrect result
+ when there are unmerged paths.
+
+ * The delayed checkout code path in "git checkout" etc. were chatty
+ even when --quiet and/or --no-progress options were given.
+
+ * "git branch -D <branch>" used to refuse to remove a broken branch
+ ref that points at a missing commit, which has been corrected.
+
+ * Build update for Apple clang.
+
+ * The parser for the "--nl" option of "git column" has been
+ corrected.
+
+ * "git upload-pack" which runs on the other side of "git fetch"
+ forgot to take the ref namespaces into account when handling
+ want-ref requests.
+
+ * The sparse-index support can corrupt the index structure by storing
+ a stale and/or uninitialized data, which has been corrected.
+
+ * Buggy tests could damage repositories outside the throw-away test
+ area we created. We now by default export GIT_CEILING_DIRECTORIES
+ to limit the damage from such a stray test.
+
+ * Even when running "git send-email" without its own threaded
+ discussion support, a threading related header in one message is
+ carried over to the subsequent message to result in an unwanted
+ threading, which has been corrected.
+
+ * The output from "git fast-export", when its anonymization feature
+ is in use, showed an annotated tag incorrectly.
+
+ * Doc update plus improved error reporting.
+
+ * Recent "diff -m" changes broke "gitk", which has been corrected.
+
+ * Regression fix.
+
+ * The "git apply -3" code path learned not to bother the lower level
+ merge machinery when the three-way merge can be trivially resolved
+ without the content level merge. This fixes a regression caused by
+ recent "-3way first and fall back to direct application" change.
+
+ * The code that optionally creates the *.rev reverse index file has
+ been optimized to avoid needless computation when it is not writing
+ the file out.
+
+ * "git range-diff -I... <range> <range>" segfaulted, which has been
+ corrected.
+
+ * The order in which various files that make up a single (conceptual)
+ packfile has been reevaluated and straightened up. This matters in
+ correctness, as an incomplete set of files must not be shown to a
+ running Git.
+
+ * The "mode" word is useless in a call to open(2) that does not
+ create a new file. Such a call in the files backend of the ref
+ subsystem has been cleaned up.
+
+ * "git update-ref --stdin" failed to flush its output as needed,
+ which potentially led the conversation to a deadlock.
+
+ * When "git am --abort" fails to abort correctly, it still exited
+ with exit status of 0, which has been corrected.
+
+ * Correct nr and alloc members of strvec struct to be of type size_t.
+
+ * "git stash", where the tentative change involves changing a
+ directory to a file (or vice versa), was confused, which has been
+ corrected.
+
+ * "git clone" from a repository whose HEAD is unborn into a bare
+ repository didn't follow the branch name the other side used, which
+ is corrected.
+
+ * "git cvsserver" had a long-standing bug in its authentication code,
+ which has finally been corrected (it is unclear and is a separate
+ question if anybody is seriously using it, though).
+
+ * "git difftool --dir-diff" mishandled symbolic links.
+
+ * Sensitive data in the HTTP trace were supposed to be redacted, but
+ we failed to do so in HTTP/2 requests.
+
+ * "make clean" has been updated to remove leftover .depend/
+ directories, even when it is not told to use them to compute header
+ dependencies.
+
+ * Protocol v0 clients can get stuck parsing a malformed feature line.
+
+ * A few kinds of changes "git status" can show were not documented.
+ (merge d2a534c515 ja/doc-status-types-and-copies later to maint).
+
+ * Other code cleanup, docfix, build fix, etc.
+ (merge f188160be9 ab/bundle-remove-verbose-option later to maint).
+ (merge 8c6b4332b4 rs/close-pack-leakfix later to maint).
+ (merge 51b04c05b7 bs/difftool-msg-tweak later to maint).
+ (merge dd20e4a6db ab/make-compdb-fix later to maint).
+ (merge 6ffb990dc4 os/status-docfix later to maint).
+ (merge 100c2da2d3 rs/p3400-lose-tac later to maint).
+ (merge 76f3b69896 tb/aggregate-ignore-leading-whitespaces later to maint).
diff --git a/Documentation/config.txt b/Documentation/config.txt
index bf82766a6a..0c0e6b859f 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -298,6 +298,15 @@ pathname::
tilde expansion happens to such a string: `~/`
is expanded to the value of `$HOME`, and `~user/` to the
specified user's home directory.
++
+If a path starts with `%(prefix)/`, the remainder is interpreted as a
+path relative to Git's "runtime prefix", i.e. relative to the location
+where Git itself was installed. For example, `%(prefix)/bin/` refers to
+the directory in which the Git executable itself lives. If Git was
+compiled without runtime prefix support, the compiled-in prefix will be
+subsituted instead. In the unlikely event that a literal path needs to
+be specified that should _not_ be expanded, it needs to be prefixed by
+`./`, like so: `./%(prefix)/bin`.
Variables
diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt
index 8b2849ff7b..063eec2511 100644
--- a/Documentation/config/advice.txt
+++ b/Documentation/config/advice.txt
@@ -44,6 +44,9 @@ advice.*::
Shown when linkgit:git-push[1] rejects a forced update of
a branch when its remote-tracking ref has updates that we
do not have locally.
+ skippedCherryPicks::
+ Shown when linkgit:git-rebase[1] skips a commit that has already
+ been cherry-picked onto the upstream branch.
statusAheadBehind::
Shown when linkgit:git-status[1] computes the ahead/behind
counts for a local ref compared to its remote tracking ref,
diff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt
index cc5f3249fc..d323d7327f 100644
--- a/Documentation/config/branch.txt
+++ b/Documentation/config/branch.txt
@@ -85,10 +85,6 @@ When `merges` (or just 'm'), pass the `--rebase-merges` option to 'git rebase'
so that the local merge commits are included in the rebase (see
linkgit:git-rebase[1] for details).
+
-When `preserve` (or just 'p', deprecated in favor of `merges`), also pass
-`--preserve-merges` along to 'git rebase' so that locally committed merge
-commits will not be flattened by running 'git pull'.
-+
When the value is `interactive` (or just 'i'), the rebase is run in interactive
mode.
+
diff --git a/Documentation/config/gui.txt b/Documentation/config/gui.txt
index d30831a130..0c087fd8c9 100644
--- a/Documentation/config/gui.txt
+++ b/Documentation/config/gui.txt
@@ -11,7 +11,7 @@ gui.displayUntracked::
in the file list. The default is "true".
gui.encoding::
- Specifies the default encoding to use for displaying of
+ Specifies the default character encoding to use for displaying of
file contents in linkgit:git-gui[1] and linkgit:gitk[1].
It can be overridden by setting the 'encoding' attribute
for relevant files (see linkgit:gitattributes[5]).
diff --git a/Documentation/config/help.txt b/Documentation/config/help.txt
index 783a90a0f9..610701f9a3 100644
--- a/Documentation/config/help.txt
+++ b/Documentation/config/help.txt
@@ -9,13 +9,15 @@ help.format::
help.autoCorrect::
If git detects typos and can identify exactly one valid command similar
- to the error, git will automatically run the intended command after
- waiting a duration of time defined by this configuration value in
- deciseconds (0.1 sec). If this value is 0, the suggested corrections
- will be shown, but not executed. If it is a negative integer, or
- "immediate", the suggested command
- is run immediately. If "never", suggestions are not shown at all. The
- default value is zero.
+ to the error, git will try to suggest the correct command or even
+ run the suggestion automatically. Possible config values are:
+ - 0 (default): show the suggested command.
+ - positive number: run the suggested command after specified
+deciseconds (0.1 sec).
+ - "immediate": run the suggested command immediately.
+ - "prompt": show the suggestion and prompt for confirmation to run
+the command.
+ - "never": don't run or show any suggested command.
help.htmlPath::
Specify the path where the HTML documentation resides. File system paths
diff --git a/Documentation/config/pack.txt b/Documentation/config/pack.txt
index 763f7af7c4..ad7f73a1ea 100644
--- a/Documentation/config/pack.txt
+++ b/Documentation/config/pack.txt
@@ -159,6 +159,10 @@ pack.writeBitmapHashCache::
between an older, bitmapped pack and objects that have been
pushed since the last gc). The downside is that it consumes 4
bytes per object of disk space. Defaults to true.
++
+When writing a multi-pack reachability bitmap, no new namehashes are
+computed; instead, any namehashes stored in an existing bitmap are
+permuted into their appropriate location when writing a new bitmap.
pack.writeReverseIndex::
When true, git will write a corresponding .rev file (see:
diff --git a/Documentation/config/pull.txt b/Documentation/config/pull.txt
index 5404830609..9349e09261 100644
--- a/Documentation/config/pull.txt
+++ b/Documentation/config/pull.txt
@@ -18,10 +18,6 @@ When `merges` (or just 'm'), pass the `--rebase-merges` option to 'git rebase'
so that the local merge commits are included in the rebase (see
linkgit:git-rebase[1] for details).
+
-When `preserve` (or just 'p', deprecated in favor of `merges`), also pass
-`--preserve-merges` along to 'git rebase' so that locally committed merge
-commits will not be flattened by running 'git pull'.
-+
When the value is `interactive` (or just 'i'), the rebase is run in interactive
mode.
+
diff --git a/Documentation/config/transfer.txt b/Documentation/config/transfer.txt
index 505126a780..b49429eb4d 100644
--- a/Documentation/config/transfer.txt
+++ b/Documentation/config/transfer.txt
@@ -52,13 +52,17 @@ If you have multiple hideRefs values, later entries override earlier ones
(and entries in more-specific config files override less-specific ones).
+
If a namespace is in use, the namespace prefix is stripped from each
-reference before it is matched against `transfer.hiderefs` patterns.
+reference before it is matched against `transfer.hiderefs` patterns. In
+order to match refs before stripping, add a `^` in front of the ref name. If
+you combine `!` and `^`, `!` must be specified first.
++
For example, if `refs/heads/master` is specified in `transfer.hideRefs` and
the current namespace is `foo`, then `refs/namespaces/foo/refs/heads/master`
-is omitted from the advertisements but `refs/heads/master` and
-`refs/namespaces/bar/refs/heads/master` are still advertised as so-called
-"have" lines. In order to match refs before stripping, add a `^` in front of
-the ref name. If you combine `!` and `^`, `!` must be specified first.
+is omitted from the advertisements. If `uploadpack.allowRefInWant` is set,
+`upload-pack` will treat `want-ref refs/heads/master` in a protocol v2
+`fetch` command as if `refs/namespaces/foo/refs/heads/master` did not exist.
+`receive-pack`, on the other hand, will still advertise the object id the
+ref is pointing to without mentioning its name (a so-called ".have" line).
+
Even if you hide refs, a client may still be able to steal the target
objects via the techniques described in the "SECURITY" section of the
diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt
index fbbd410a84..7a9c3b6ff4 100644
--- a/Documentation/diff-format.txt
+++ b/Documentation/diff-format.txt
@@ -59,7 +59,7 @@ Possible status letters are:
- D: deletion of a file
- M: modification of the contents or mode of a file
- R: renaming of a file
-- T: change in the type of the file
+- T: change in the type of the file (regular file, symbolic link or submodule)
- U: file is unmerged (you must complete the merge before it can
be committed)
- X: "unknown" change type (most probably a bug, please report it)
diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt
index be5e3ac54b..11eb70f16c 100644
--- a/Documentation/git-add.txt
+++ b/Documentation/git-add.txt
@@ -9,7 +9,7 @@ SYNOPSIS
--------
[verse]
'git add' [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [--patch | -p]
- [--edit | -e] [--[no-]all | --[no-]ignore-removal | [--update | -u]]
+ [--edit | -e] [--[no-]all | --[no-]ignore-removal | [--update | -u]] [--sparse]
[--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] [--renormalize]
[--chmod=(+|-)x] [--pathspec-from-file=<file> [--pathspec-file-nul]]
[--] [<pathspec>...]
@@ -79,6 +79,13 @@ in linkgit:gitglossary[7].
--force::
Allow adding otherwise ignored files.
+--sparse::
+ Allow updating index entries outside of the sparse-checkout cone.
+ Normally, `git add` refuses to update index entries whose paths do
+ not fit within the sparse-checkout cone, since those files might
+ be removed from the working tree without warning. See
+ linkgit:git-sparse-checkout[1] for more details.
+
-i::
--interactive::
Add modified contents in the working tree interactively to
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 8714dfcb76..0a4a984dfd 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -178,6 +178,8 @@ default. You can use `--no-utf8` to override this.
--abort::
Restore the original branch and abort the patching operation.
+ Revert contents of files involved in the am operation to their
+ pre-am state.
--quit::
Abort the patching operation but keep HEAD and the index
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index 94dc9a54f2..5449767121 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -118,7 +118,8 @@ OPTIONS
Reset <branchname> to <startpoint>, even if <branchname> exists
already. Without `-f`, 'git branch' refuses to change an existing branch.
In combination with `-d` (or `--delete`), allow deleting the
- branch irrespective of its merged status. In combination with
+ branch irrespective of its merged status, or whether it even
+ points to a valid commit. In combination with
`-m` (or `--move`), allow renaming the branch even if the new
branch name already exists, the same applies for `-c` (or `--copy`).
diff --git a/Documentation/git-bugreport.txt b/Documentation/git-bugreport.txt
index 66e88c2e31..d8817bf3ce 100644
--- a/Documentation/git-bugreport.txt
+++ b/Documentation/git-bugreport.txt
@@ -40,8 +40,8 @@ OPTIONS
-------
-o <path>::
--output-directory <path>::
- Place the resulting bug report file in `<path>` instead of the root of
- the Git repository.
+ Place the resulting bug report file in `<path>` instead of the current
+ directory.
-s <format>::
--suffix <format>::
diff --git a/Documentation/git-bundle.txt b/Documentation/git-bundle.txt
index 53804cad4b..71b5ecabd1 100644
--- a/Documentation/git-bundle.txt
+++ b/Documentation/git-bundle.txt
@@ -13,26 +13,53 @@ SYNOPSIS
[--version=<version>] <file> <git-rev-list-args>
'git bundle' verify [-q | --quiet] <file>
'git bundle' list-heads <file> [<refname>...]
-'git bundle' unbundle <file> [<refname>...]
+'git bundle' unbundle [--progress] <file> [<refname>...]
DESCRIPTION
-----------
-Some workflows require that one or more branches of development on one
-machine be replicated on another machine, but the two machines cannot
-be directly connected, and therefore the interactive Git protocols (git,
-ssh, http) cannot be used.
+Create, unpack, and manipulate "bundle" files. Bundles are used for
+the "offline" transfer of Git objects without an active "server"
+sitting on the other side of the network connection.
-The 'git bundle' command packages objects and references in an archive
-at the originating machine, which can then be imported into another
-repository using 'git fetch', 'git pull', or 'git clone',
-after moving the archive by some means (e.g., by sneakernet).
+They can be used to create both incremental and full backups of a
+repository, and to relay the state of the references in one repository
+to another.
-As no
-direct connection between the repositories exists, the user must specify a
-basis for the bundle that is held by the destination repository: the
-bundle assumes that all objects in the basis are already in the
-destination repository.
+Git commands that fetch or otherwise "read" via protocols such as
+`ssh://` and `https://` can also operate on bundle files. It is
+possible linkgit:git-clone[1] a new repository from a bundle, to use
+linkgit:git-fetch[1] to fetch from one, and to list the references
+contained within it with linkgit:git-ls-remote[1]. There's no
+corresponding "write" support, i.e.a 'git push' into a bundle is not
+supported.
+
+See the "EXAMPLES" section below for examples of how to use bundles.
+
+BUNDLE FORMAT
+-------------
+
+Bundles are `.pack` files (see linkgit:git-pack-objects[1]) with a
+header indicating what references are contained within the bundle.
+
+Like the the packed archive format itself bundles can either be
+self-contained, or be created using exclusions.
+See the "OBJECT PREREQUISITES" section below.
+
+Bundles created using revision exclusions are "thin packs" created
+using the `--thin` option to linkgit:git-pack-objects[1], and
+unbundled using the `--fix-thin` option to linkgit:git-index-pack[1].
+
+There is no option to create a "thick pack" when using revision
+exclusions, users should not be concerned about the difference. By
+using "thin packs" bundles created using exclusions are smaller in
+size. That they're "thin" under the hood is merely noted here as a
+curiosity, and as a reference to other documentation
+
+See link:technical/bundle-format.html[the `bundle-format`
+documentation] for more details and the discussion of "thin pack" in
+link:technical/pack-format.html[the pack format documentation] for
+further details.
OPTIONS
-------
@@ -117,28 +144,88 @@ unbundle <file>::
SPECIFYING REFERENCES
---------------------
-'git bundle' will only package references that are shown by
-'git show-ref': this includes heads, tags, and remote heads. References
-such as `master~1` cannot be packaged, but are perfectly suitable for
-defining the basis. More than one reference may be packaged, and more
-than one basis can be specified. The objects packaged are those not
-contained in the union of the given bases. Each basis can be
-specified explicitly (e.g. `^master~10`), or implicitly (e.g.
-`master~10..master`, `--since=10.days.ago master`).
+Revisions must accompanied by reference names to be packaged in a
+bundle.
+
+More than one reference may be packaged, and more than one set of prerequisite objects can
+be specified. The objects packaged are those not contained in the
+union of the prerequisites.
+
+The 'git bundle create' command resolves the reference names for you
+using the same rules as `git rev-parse --abbrev-ref=loose`. Each
+prerequisite can be specified explicitly (e.g. `^master~10`), or implicitly
+(e.g. `master~10..master`, `--since=10.days.ago master`).
+
+All of these simple cases are OK (assuming we have a "master" and
+"next" branch):
+
+----------------
+$ git bundle create master.bundle master
+$ echo master | git bundle create master.bundle --stdin
+$ git bundle create master-and-next.bundle master next
+$ (echo master; echo next) | git bundle create master-and-next.bundle --stdin
+----------------
+
+And so are these (and the same but omitted `--stdin` examples):
+
+----------------
+$ git bundle create recent-master.bundle master~10..master
+$ git bundle create recent-updates.bundle master~10..master next~5..next
+----------------
+
+A revision name or a range whose right-hand-side cannot be resolved to
+a reference is not accepted:
+
+----------------
+$ git bundle create HEAD.bundle $(git rev-parse HEAD)
+fatal: Refusing to create empty bundle.
+$ git bundle create master-yesterday.bundle master~10..master~5
+fatal: Refusing to create empty bundle.
+----------------
+
+OBJECT PREREQUISITES
+--------------------
+
+When creating bundles it is possible to create a self-contained bundle
+that can be unbundled in a repository with no common history, as well
+as providing negative revisions to exclude objects needed in the
+earlier parts of the history.
+
+Feeding a revision such as `new` to `git bundle create` will create a
+bundle file that contains all the objects reachable from the revision
+`new`. That bundle can be unbundled in any repository to obtain a full
+history that leads to the revision `new`:
+
+----------------
+$ git bundle create full.bundle new
+----------------
+
+A revision range such as `old..new` will produce a bundle file that
+will require the revision `old` (and any objects reachable from it)
+to exist for the bundle to be "unbundle"-able:
+
+----------------
+$ git bundle create full.bundle old..new
+----------------
+
+A self-contained bundle without any prerequisites can be extracted
+into anywhere, even into an empty repository, or be cloned from
+(i.e., `new`, but not `old..new`).
-It is very important that the basis used be held by the destination.
It is okay to err on the side of caution, causing the bundle file
to contain objects already in the destination, as these are ignored
when unpacking at the destination.
-`git clone` can use any bundle created without negative refspecs
-(e.g., `new`, but not `old..new`).
If you want to match `git clone --mirror`, which would include your
refs such as `refs/remotes/*`, use `--all`.
If you want to provide the same set of refs that a clone directly
from the source repository would get, use `--branches --tags` for
the `<git-rev-list-args>`.
+The 'git bundle verify' command can be used to check whether your
+recipient repository has the required prerequisite commits for a
+bundle.
+
EXAMPLES
--------
@@ -149,7 +236,7 @@ but we can move data from A to B via some mechanism (CD, email, etc.).
We want to update R2 with development made on the branch master in R1.
To bootstrap the process, you can first create a bundle that does not have
-any basis. You can use a tag to remember up to what commit you last
+any prerequisites. You can use a tag to remember up to what commit you last
processed, in order to make it easy to later update the other repository
with an incremental bundle:
@@ -200,7 +287,7 @@ machineB$ git pull
If you know up to what commit the intended recipient repository should
have the necessary objects, you can use that knowledge to specify the
-basis, giving a cut-off point to limit the revisions and objects that go
+prerequisites, giving a cut-off point to limit the revisions and objects that go
in the resulting bundle. The previous example used the lastR2bundle tag
for this purpose, but you can use any other options that you would give to
the linkgit:git-log[1] command. Here are more examples:
@@ -211,7 +298,7 @@ You can use a tag that is present in both:
$ git bundle create mybundle v1.0.0..master
----------------
-You can use a basis based on time:
+You can use a prerequisite based on time:
----------------
$ git bundle create mybundle --since=10.days master
@@ -224,7 +311,7 @@ $ git bundle create mybundle -10 master
----------------
You can run `git-bundle verify` to see if you can extract from a bundle
-that was created with a basis:
+that was created with a prerequisite:
----------------
$ git bundle verify mybundle
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index b1a6fe4499..d473c9bf38 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -118,8 +118,9 @@ OPTIONS
-f::
--force::
When switching branches, proceed even if the index or the
- working tree differs from `HEAD`. This is used to throw away
- local changes.
+ working tree differs from `HEAD`, and even if there are untracked
+ files in the way. This is used to throw away local changes and
+ any untracked files or directories that are in the way.
+
When checking out paths from the index, do not fail upon unmerged
entries; instead, unmerged entries are ignored.
diff --git a/Documentation/git-column.txt b/Documentation/git-column.txt
index f58e9c43e6..6cea9ab463 100644
--- a/Documentation/git-column.txt
+++ b/Documentation/git-column.txt
@@ -39,7 +39,7 @@ OPTIONS
--indent=<string>::
String to be printed at the beginning of each line.
---nl=<N>::
+--nl=<string>::
String to be printed at the end of each line,
including newline character.
diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt
index 2dc4bae6da..992225f612 100644
--- a/Documentation/git-config.txt
+++ b/Documentation/git-config.txt
@@ -71,6 +71,9 @@ codes are:
On success, the command returns the exit code 0.
+A list of all available configuration variables can be obtained using the
+`git help --config` command.
+
[[OPTIONS]]
OPTIONS
-------
diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt
index f2e4a47ebe..4dc57ed254 100644
--- a/Documentation/git-cvsserver.txt
+++ b/Documentation/git-cvsserver.txt
@@ -99,7 +99,7 @@ looks like
------
-Only anonymous access is provided by pserve by default. To commit you
+Only anonymous access is provided by pserver by default. To commit you
will have to create pserver accounts, simply add a gitcvs.authdb
setting in the config file of the repositories you want the cvsserver
to allow writes to, for example:
@@ -114,21 +114,20 @@ The format of these files is username followed by the encrypted password,
for example:
------
- myuser:$1Oyx5r9mdGZ2
- myuser:$1$BA)@$vbnMJMDym7tA32AamXrm./
+ myuser:sqkNi8zPf01HI
+ myuser:$1$9K7FzU28$VfF6EoPYCJEYcVQwATgOP/
+ myuser:$5$.NqmNH1vwfzGpV8B$znZIcumu1tNLATgV2l6e1/mY8RzhUDHMOaVOeL1cxV3
------
You can use the 'htpasswd' facility that comes with Apache to make these
-files, but Apache's MD5 crypt method differs from the one used by most C
-library's crypt() function, so don't use the -m option.
+files, but only with the -d option (or -B if your system suports it).
-Alternatively you can produce the password with perl's crypt() operator:
------
- perl -e 'my ($user, $pass) = @ARGV; printf "%s:%s\n", $user, crypt($user, $pass)' $USER password
------
+Preferably use the system specific utility that manages password hash
+creation in your platform (e.g. mkpasswd in Linux, encrypt in OpenBSD or
+pwhash in NetBSD) and paste it in the right location.
Then provide your password via the pserver method, for example:
------
- cvs -d:pserver:someuser:somepassword <at> server/path/repo.git co <HEAD_name>
+ cvs -d:pserver:someuser:somepassword@server:/path/repo.git co <HEAD_name>
------
No special setup is needed for SSH access, other than having Git tools
in the PATH. If you have clients that do not accept the CVS_SERVER
@@ -138,7 +137,7 @@ Note: Newer CVS versions (>= 1.12.11) also support specifying
CVS_SERVER directly in CVSROOT like
------
-cvs -d ":ext;CVS_SERVER=git cvsserver:user@server/path/repo.git" co <HEAD_name>
+ cvs -d ":ext;CVS_SERVER=git cvsserver:user@server/path/repo.git" co <HEAD_name>
------
This has the advantage that it will be saved in your 'CVS/Root' files and
you don't need to worry about always setting the correct environment
@@ -186,8 +185,8 @@ allowing access over SSH.
+
--
------
- export CVSROOT=:ext:user@server:/var/git/project.git
- export CVS_SERVER="git cvsserver"
+ export CVSROOT=:ext:user@server:/var/git/project.git
+ export CVS_SERVER="git cvsserver"
------
--
4. For SSH clients that will make commits, make sure their server-side
@@ -203,7 +202,7 @@ allowing access over SSH.
`project-master` directory:
+
------
- cvs co -d project-master master
+ cvs co -d project-master master
------
[[dbbackend]]
diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
index 2ae2478de7..6da899c629 100644
--- a/Documentation/git-for-each-ref.txt
+++ b/Documentation/git-for-each-ref.txt
@@ -235,6 +235,15 @@ and `date` to extract the named component. For email fields (`authoremail`,
without angle brackets, and `:localpart` to get the part before the `@` symbol
out of the trimmed email.
+The raw data in an object is `raw`.
+
+raw:size::
+ The raw data size of the object.
+
+Note that `--format=%(raw)` can not be used with `--python`, `--shell`, `--tcl`,
+because such language may not support arbitrary binary data in their string
+variable type.
+
The message in a commit or a tag object is `contents`, from which
`contents:<part>` can be used to extract various parts out of:
diff --git a/Documentation/git-help.txt b/Documentation/git-help.txt
index 44fe8860b3..96d5f598b4 100644
--- a/Documentation/git-help.txt
+++ b/Documentation/git-help.txt
@@ -8,8 +8,10 @@ git-help - Display help information about Git
SYNOPSIS
--------
[verse]
-'git help' [-a|--all [--[no-]verbose]] [-g|--guides]
- [-i|--info|-m|--man|-w|--web] [COMMAND|GUIDE]
+'git help' [-a|--all [--[no-]verbose]]
+ [[-i|--info] [-m|--man] [-w|--web]] [COMMAND|GUIDE]
+'git help' [-g|--guides]
+'git help' [-c|--config]
DESCRIPTION
-----------
@@ -58,8 +60,7 @@ OPTIONS
-g::
--guides::
- Prints a list of the Git concept guides on the standard output. This
- option overrides any given command or guide name.
+ Prints a list of the Git concept guides on the standard output.
-i::
--info::
diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt
index 558966aa83..0c5c0dde19 100644
--- a/Documentation/git-http-backend.txt
+++ b/Documentation/git-http-backend.txt
@@ -16,7 +16,9 @@ A simple CGI program to serve the contents of a Git repository to Git
clients accessing the repository over http:// and https:// protocols.
The program supports clients fetching using both the smart HTTP protocol
and the backwards-compatible dumb HTTP protocol, as well as clients
-pushing using the smart HTTP protocol.
+pushing using the smart HTTP protocol. It also supports Git's
+more-efficient "v2" protocol if properly configured; see the
+discussion of `GIT_PROTOCOL` in the ENVIRONMENT section below.
It verifies that the directory has the magic file
"git-daemon-export-ok", and it will refuse to export any Git directory
@@ -77,6 +79,18 @@ Apache 2.x::
SetEnv GIT_PROJECT_ROOT /var/www/git
SetEnv GIT_HTTP_EXPORT_ALL
ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/
+
+# This is not strictly necessary using Apache and a modern version of
+# git-http-backend, as the webserver will pass along the header in the
+# environment as HTTP_GIT_PROTOCOL, and http-backend will copy that into
+# GIT_PROTOCOL. But you may need this line (or something similar if you
+# are using a different webserver), or if you want to support older Git
+# versions that did not do that copying.
+#
+# Having the webserver set up GIT_PROTOCOL is perfectly fine even with
+# modern versions (and will take precedence over HTTP_GIT_PROTOCOL,
+# which means it can be used to override the client's request).
+SetEnvIf Git-Protocol ".*" GIT_PROTOCOL=$0
----------------------------------------------------------------
+
To enable anonymous read access but authenticated write access,
@@ -264,6 +278,16 @@ a repository with an extremely large number of refs. The value can be
specified with a unit (e.g., `100M` for 100 megabytes). The default is
10 megabytes.
+Clients may probe for optional protocol capabilities (like the v2
+protocol) using the `Git-Protocol` HTTP header. In order to support
+these, the contents of that header must appear in the `GIT_PROTOCOL`
+environment variable. Most webservers will pass this header to the CGI
+via the `HTTP_GIT_PROTOCOL` variable, and `git-http-backend` will
+automatically copy that to `GIT_PROTOCOL`. However, some webservers may
+be more selective about which headers they'll pass, in which case they
+need to be configured explicitly (see the mention of `Git-Protocol` in
+the Apache config from the earlier EXAMPLES section).
+
The backend process sets GIT_COMMITTER_NAME to '$REMOTE_USER' and
GIT_COMMITTER_EMAIL to '$\{REMOTE_USER}@http.$\{REMOTE_ADDR\}',
ensuring that any reflogs created by 'git-receive-pack' contain some
diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt
index 7fa74b9e79..1f1e359225 100644
--- a/Documentation/git-index-pack.txt
+++ b/Documentation/git-index-pack.txt
@@ -82,6 +82,12 @@ OPTIONS
--strict::
Die, if the pack contains broken objects or links.
+--progress-title::
+ For internal use only.
++
+Set the title of the progress bar. The title is "Receiving objects" by
+default and "Indexing objects" when `--stdin` is specified.
+
--check-self-contained-and-connected::
Die if the pack contains broken links. For internal use only.
diff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt
index 1e738ad398..e2cfb68ab5 100644
--- a/Documentation/git-maintenance.txt
+++ b/Documentation/git-maintenance.txt
@@ -179,6 +179,17 @@ OPTIONS
`maintenance.<task>.enabled` configured as `true` are considered.
See the 'TASKS' section for the list of accepted `<task>` values.
+--scheduler=auto|crontab|systemd-timer|launchctl|schtasks::
+ When combined with the `start` subcommand, specify the scheduler
+ for running the hourly, daily and weekly executions of
+ `git maintenance run`.
+ Possible values for `<scheduler>` are `auto`, `crontab`
+ (POSIX), `systemd-timer` (Linux), `launchctl` (macOS), and
+ `schtasks` (Windows). When `auto` is specified, the
+ appropriate platform-specific scheduler is used; on Linux,
+ `systemd-timer` is used if available, otherwise
+ `crontab`. Default is `auto`.
+
TROUBLESHOOTING
---------------
@@ -277,6 +288,52 @@ schedule to ensure you are executing the correct binaries in your
schedule.
+BACKGROUND MAINTENANCE ON LINUX SYSTEMD SYSTEMS
+-----------------------------------------------
+
+While Linux supports `cron`, depending on the distribution, `cron` may
+be an optional package not necessarily installed. On modern Linux
+distributions, systemd timers are superseding it.
+
+If user systemd timers are available, they will be used as a replacement
+of `cron`.
+
+In this case, `git maintenance start` will create user systemd timer units
+and start the timers. The current list of user-scheduled tasks can be found
+by running `systemctl --user list-timers`. The timers written by `git
+maintenance start` are similar to this:
+
+-----------------------------------------------------------------------
+$ systemctl --user list-timers
+NEXT LEFT LAST PASSED UNIT ACTIVATES
+Thu 2021-04-29 19:00:00 CEST 42min left Thu 2021-04-29 18:00:11 CEST 17min ago git-maintenance@hourly.timer git-maintenance@hourly.service
+Fri 2021-04-30 00:00:00 CEST 5h 42min left Thu 2021-04-29 00:00:11 CEST 18h ago git-maintenance@daily.timer git-maintenance@daily.service
+Mon 2021-05-03 00:00:00 CEST 3 days left Mon 2021-04-26 00:00:11 CEST 3 days ago git-maintenance@weekly.timer git-maintenance@weekly.service
+-----------------------------------------------------------------------
+
+One timer is registered for each `--schedule=<frequency>` option.
+
+The definition of the systemd units can be inspected in the following files:
+
+-----------------------------------------------------------------------
+~/.config/systemd/user/git-maintenance@.timer
+~/.config/systemd/user/git-maintenance@.service
+~/.config/systemd/user/timers.target.wants/git-maintenance@hourly.timer
+~/.config/systemd/user/timers.target.wants/git-maintenance@daily.timer
+~/.config/systemd/user/timers.target.wants/git-maintenance@weekly.timer
+-----------------------------------------------------------------------
+
+`git maintenance start` will overwrite these files and start the timer
+again with `systemctl --user`, so any customization should be done by
+creating a drop-in file, i.e. a `.conf` suffixed file in the
+`~/.config/systemd/user/git-maintenance@.service.d` directory.
+
+`git maintenance stop` will stop the user systemd timers and delete
+the above mentioned files.
+
+For more details, see `systemd.timer(5)`.
+
+
BACKGROUND MAINTENANCE ON MACOS SYSTEMS
---------------------------------------
diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt
index 3819fadac1..e4f3352eb5 100644
--- a/Documentation/git-merge.txt
+++ b/Documentation/git-merge.txt
@@ -61,6 +61,8 @@ merge has resulted in conflicts.
OPTIONS
-------
+:git-merge: 1
+
include::merge-options.txt[]
-m <msg>::
diff --git a/Documentation/git-multi-pack-index.txt b/Documentation/git-multi-pack-index.txt
index 27f83932e4..b008ce2850 100644
--- a/Documentation/git-multi-pack-index.txt
+++ b/Documentation/git-multi-pack-index.txt
@@ -9,8 +9,7 @@ git-multi-pack-index - Write and verify multi-pack-indexes
SYNOPSIS
--------
[verse]
-'git multi-pack-index' [--object-dir=<dir>] [--[no-]progress]
- [--preferred-pack=<pack>] [--[no-]bitmap] <subcommand>
+'git multi-pack-index' [--object-dir=<dir>] [--[no-]bitmap] <sub-command>
DESCRIPTION
-----------
@@ -28,7 +27,8 @@ OPTIONS
--[no-]progress::
Turn progress on/off explicitly. If neither is specified, progress is
- shown if standard error is connected to a terminal.
+ shown if standard error is connected to a terminal. Supported by
+ sub-commands `write`, `verify`, `expire`, and `repack.
The following subcommands are available:
diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt
index 7f4b2d1982..0e14f8b5b2 100644
--- a/Documentation/git-pull.txt
+++ b/Documentation/git-pull.txt
@@ -15,14 +15,17 @@ SYNOPSIS
DESCRIPTION
-----------
-Incorporates changes from a remote repository into the current
-branch. In its default mode, `git pull` is shorthand for
-`git fetch` followed by `git merge FETCH_HEAD`.
-
-More precisely, 'git pull' runs 'git fetch' with the given
-parameters and calls 'git merge' to merge the retrieved branch
-heads into the current branch.
-With `--rebase`, it runs 'git rebase' instead of 'git merge'.
+Incorporates changes from a remote repository into the current branch.
+If the current branch is behind the remote, then by default it will
+fast-forward the current branch to match the remote. If the current
+branch and the remote have diverged, the user needs to specify how to
+reconcile the divergent branches with `--rebase` or `--no-rebase` (or
+the corresponding configuration option in `pull.rebase`).
+
+More precisely, `git pull` runs `git fetch` with the given parameters
+and then depending on configuration options or command line flags,
+will call either `git rebase` or `git merge` to reconcile diverging
+branches.
<repository> should be the name of a remote repository as
passed to linkgit:git-fetch[1]. <refspec> can name an
@@ -102,7 +105,7 @@ Options related to merging
include::merge-options.txt[]
-r::
---rebase[=false|true|merges|preserve|interactive]::
+--rebase[=false|true|merges|interactive]::
When true, rebase the current branch on top of the upstream
branch after fetching. If there is a remote-tracking branch
corresponding to the upstream branch and the upstream branch
@@ -113,10 +116,6 @@ When set to `merges`, rebase using `git rebase --rebase-merges` so that
the local merge commits are included in the rebase (see
linkgit:git-rebase[1] for details).
+
-When set to `preserve` (deprecated in favor of `merges`), rebase with the
-`--preserve-merges` option passed to `git rebase` so that locally created
-merge commits will not be flattened.
-+
When false, merge the upstream branch into the current branch.
+
When `interactive`, enable the interactive mode of rebase.
@@ -132,7 +131,7 @@ published that history already. Do *not* use this option
unless you have read linkgit:git-rebase[1] carefully.
--no-rebase::
- Override earlier --rebase.
+ This is shorthand for --rebase=false.
Options related to fetching
~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt
index 5fa8bab64c..8c3aceb832 100644
--- a/Documentation/git-read-tree.txt
+++ b/Documentation/git-read-tree.txt
@@ -10,8 +10,7 @@ SYNOPSIS
--------
[verse]
'git read-tree' [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>]
- [-u [--exclude-per-directory=<gitignore>] | -i]]
- [--index-output=<file>] [--no-sparse-checkout]
+ [-u | -i]] [--index-output=<file>] [--no-sparse-checkout]
(--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])
@@ -39,8 +38,9 @@ OPTIONS
--reset::
Same as -m, except that unmerged entries are discarded instead
- of failing. When used with `-u`, updates leading to loss of
- working tree changes will not abort the operation.
+ of failing. When used with `-u`, updates leading to loss of
+ working tree changes or untracked files or directories will not
+ abort the operation.
-u::
After a successful merge, update the files in the work
@@ -88,21 +88,6 @@ OPTIONS
The command will refuse to overwrite entries that already
existed in the original index file.
---exclude-per-directory=<gitignore>::
- When running the command with `-u` and `-m` options, the
- merge result may need to overwrite paths that are not
- tracked in the current branch. The command usually
- refuses to proceed with the merge to avoid losing such a
- path. However this safety valve sometimes gets in the
- way. For example, it often happens that the other
- branch added a file that used to be a generated file in
- your branch, and the safety valve triggers when you try
- to switch to that branch after you ran `make` but before
- running `make clean` to remove the generated file. This
- option tells the command to read per-directory exclude
- file (usually '.gitignore') and allows such an untracked
- but explicitly ignored file to be overwritten.
-
--index-output=<file>::
Instead of writing the results out to `$GIT_INDEX_FILE`,
write the resulting index in the named file. While the
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 55af6fd24e..a1af21fcef 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -79,9 +79,10 @@ remain the checked-out branch.
If the upstream branch already contains a change you have made (e.g.,
because you mailed a patch which was applied upstream), then that commit
-will be skipped. For example, running `git rebase master` on the
-following history (in which `A'` and `A` introduce the same set of changes,
-but have different committer information):
+will be skipped and warnings will be issued (if the `merge` backend is
+used). For example, running `git rebase master` on the following
+history (in which `A'` and `A` introduce the same set of changes, but
+have different committer information):
------------
A---B---C topic
@@ -312,7 +313,10 @@ See also INCOMPATIBLE OPTIONS below.
By default (or if `--no-reapply-cherry-picks` is given), these commits
will be automatically dropped. Because this necessitates reading all
upstream commits, this can be expensive in repos with a large number
-of upstream commits that need to be read.
+of upstream commits that need to be read. When using the `merge`
+backend, warnings will be issued for each dropped commit (unless
+`--quiet` is given). Advice will also be issued unless
+`advice.skippedCherryPicks` is set to false (see linkgit:git-config[1]).
+
`--reapply-cherry-picks` allows rebase to forgo reading all upstream
commits, potentially improving performance.
@@ -340,9 +344,7 @@ See also INCOMPATIBLE OPTIONS below.
-m::
--merge::
- Use merging strategies to rebase. When the recursive (default) merge
- strategy is used, this allows rebase to be aware of renames on the
- upstream side. This is the default.
+ Using merging strategies to rebase (default).
+
Note that a rebase merge works by replaying each commit from the working
branch on top of the <upstream> branch. Because of this, when a merge
@@ -354,9 +356,8 @@ See also INCOMPATIBLE OPTIONS below.
-s <strategy>::
--strategy=<strategy>::
- Use the given merge strategy.
- If there is no `-s` option 'git merge-recursive' is used
- instead. This implies --merge.
+ Use the given merge strategy, instead of the default `ort`.
+ This implies `--merge`.
+
Because 'git rebase' replays each commit from the working branch
on top of the <upstream> branch using the given strategy, using
@@ -369,7 +370,7 @@ See also INCOMPATIBLE OPTIONS below.
--strategy-option=<strategy-option>::
Pass the <strategy-option> through to the merge strategy.
This implies `--merge` and, if no strategy has been
- specified, `-s recursive`. Note the reversal of 'ours' and
+ specified, `-s ort`. Note the reversal of 'ours' and
'theirs' as noted above for the `-m` option.
+
See also INCOMPATIBLE OPTIONS below.
@@ -445,7 +446,8 @@ When --fork-point is active, 'fork_point' will be used instead of
ends up being empty, the <upstream> will be used as a fallback.
+
If <upstream> is given on the command line, then the default is
-`--no-fork-point`, otherwise the default is `--fork-point`.
+`--no-fork-point`, otherwise the default is `--fork-point`. See also
+`rebase.forkpoint` in linkgit:git-config[1].
+
If your branch was based on <upstream> but <upstream> was rewound and
your branch contains commits which were dropped, this option can be used
@@ -525,29 +527,12 @@ i.e. commits that would be excluded by linkgit:git-log[1]'s
the `rebase-cousins` mode is turned on, such commits are instead rebased
onto `<upstream>` (or `<onto>`, if specified).
+
-The `--rebase-merges` mode is similar in spirit to the deprecated
-`--preserve-merges` but works with interactive rebases,
-where commits can be reordered, inserted and dropped at will.
-+
It is currently only possible to recreate the merge commits using the
-`recursive` merge strategy; Different merge strategies can be used only via
+`ort` merge strategy; different merge strategies can be used only via
explicit `exec git merge -s <strategy> [...]` commands.
+
See also REBASING MERGES and INCOMPATIBLE OPTIONS below.
--p::
---preserve-merges::
- [DEPRECATED: use `--rebase-merges` instead] Recreate merge commits
- instead of flattening the history by replaying commits a merge commit
- introduces. Merge conflict resolutions or manual amendments to merge
- commits are not preserved.
-+
-This uses the `--interactive` machinery internally, but combining it
-with the `--interactive` option explicitly is generally not a good
-idea unless you know what you are doing (see BUGS below).
-+
-See also INCOMPATIBLE OPTIONS below.
-
-x <cmd>::
--exec <cmd>::
Append "exec <cmd>" after each line creating a commit in the
@@ -579,9 +564,6 @@ See also INCOMPATIBLE OPTIONS below.
the root commit(s) on a branch. When used with --onto, it
will skip changes already contained in <newbase> (instead of
<upstream>) whereas without --onto it will operate on every change.
- When used together with both --onto and --preserve-merges,
- 'all' root commits will be rewritten to have <newbase> as parent
- instead.
+
See also INCOMPATIBLE OPTIONS below.
@@ -643,7 +625,6 @@ are incompatible with the following options:
* --allow-empty-message
* --[no-]autosquash
* --rebase-merges
- * --preserve-merges
* --interactive
* --exec
* --no-keep-empty
@@ -654,13 +635,6 @@ are incompatible with the following options:
In addition, the following pairs of options are incompatible:
- * --preserve-merges and --interactive
- * --preserve-merges and --signoff
- * --preserve-merges and --rebase-merges
- * --preserve-merges and --empty=
- * --preserve-merges and --ignore-whitespace
- * --preserve-merges and --committer-date-is-author-date
- * --preserve-merges and --ignore-date
* --keep-base and --onto
* --keep-base and --root
* --fork-point and --root
@@ -1219,12 +1193,16 @@ successful merge so that the user can edit the message.
If a `merge` command fails for any reason other than merge conflicts (i.e.
when the merge operation did not even start), it is rescheduled immediately.
-At this time, the `merge` command will *always* use the `recursive`
-merge strategy for regular merges, and `octopus` for octopus merges,
-with no way to choose a different one. To work around
-this, an `exec` command can be used to call `git merge` explicitly,
-using the fact that the labels are worktree-local refs (the ref
-`refs/rewritten/onto` would correspond to the label `onto`, for example).
+By default, the `merge` command will use the `ort` merge strategy for
+regular merges, and `octopus` for octopus merges. One can specify a
+default strategy for all merges using the `--strategy` argument when
+invoking rebase, or can override specific merges in the interactive
+list of commands by using an `exec` command to call `git merge`
+explicitly with a `--strategy` argument. Note that when calling `git
+merge` explicitly like this, you can make use of the fact that the
+labels are worktree-local refs (the ref `refs/rewritten/onto` would
+correspond to the label `onto`, for example) in order to refer to the
+branches you want to merge.
Note: the first command (`label onto`) labels the revision onto which
the commits are rebased; The name `onto` is just a convention, as a nod
@@ -1274,29 +1252,6 @@ CONFIGURATION
include::config/rebase.txt[]
include::config/sequencer.txt[]
-BUGS
-----
-The todo list presented by the deprecated `--preserve-merges --interactive`
-does not represent the topology of the revision graph (use `--rebase-merges`
-instead). Editing commits and rewording their commit messages should work
-fine, but attempts to reorder commits tend to produce counterintuitive results.
-Use `--rebase-merges` in such scenarios instead.
-
-For example, an attempt to rearrange
-------------
-1 --- 2 --- 3 --- 4 --- 5
-------------
-to
-------------
-1 --- 2 --- 4 --- 3 --- 5
-------------
-by moving the "pick 4" line will result in the following history:
-------------
- 3
- /
-1 --- 2 --- 4 --- 5
-------------
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt
index 25702ed730..014a78409b 100644
--- a/Documentation/git-receive-pack.txt
+++ b/Documentation/git-receive-pack.txt
@@ -41,6 +41,11 @@ OPTIONS
<directory>::
The repository to sync into.
+--http-backend-info-refs::
+ Used by linkgit:git-http-backend[1] to serve up
+ `$GIT_URL/info/refs?service=git-receive-pack` requests. See
+ `--http-backend-info-refs` in linkgit:git-upload-pack[1].
+
PRE-RECEIVE HOOK
----------------
Before any ref is updated, if $GIT_DIR/hooks/pre-receive file exists
diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt
index 252e2d4e47..6f7685f53d 100644
--- a/Documentation/git-reset.txt
+++ b/Documentation/git-reset.txt
@@ -69,7 +69,8 @@ linkgit:git-add[1]).
--hard::
Resets the index and working tree. Any changes to tracked files in the
- working tree since `<commit>` are discarded.
+ working tree since `<commit>` are discarded. Any untracked files or
+ directories in the way of writing any tracked files are simply deleted.
--merge::
Resets the index and updates the files in the working tree that are
diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt
index 26e9b28470..81bc23f3cd 100644
--- a/Documentation/git-rm.txt
+++ b/Documentation/git-rm.txt
@@ -72,6 +72,12 @@ For more details, see the 'pathspec' entry in linkgit:gitglossary[7].
--ignore-unmatch::
Exit with a zero status even if no files matched.
+--sparse::
+ Allow updating index entries outside of the sparse-checkout cone.
+ Normally, `git rm` refuses to update index entries whose paths do
+ not fit within the sparse-checkout cone. See
+ linkgit:git-sparse-checkout[1] for more.
+
-q::
--quiet::
`git rm` normally outputs one line (in the form of an `rm` command)
diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt
index 44fd146b91..be41f11974 100644
--- a/Documentation/git-send-pack.txt
+++ b/Documentation/git-send-pack.txt
@@ -9,10 +9,10 @@ git-send-pack - Push objects over Git protocol to another repository
SYNOPSIS
--------
[verse]
-'git send-pack' [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>]
+'git send-pack' [--dry-run] [--force] [--receive-pack=<git-receive-pack>]
[--verbose] [--thin] [--atomic]
[--[no-]signed|--signed=(true|false|if-asked)]
- [<host>:]<directory> [<ref>...]
+ [<host>:]<directory> (--all | <ref>...)
DESCRIPTION
-----------
diff --git a/Documentation/git-sparse-checkout.txt b/Documentation/git-sparse-checkout.txt
index fdcf43f87c..42056ee9ff 100644
--- a/Documentation/git-sparse-checkout.txt
+++ b/Documentation/git-sparse-checkout.txt
@@ -210,6 +210,16 @@ case-insensitive check. This corrects for case mismatched filenames in the
'git sparse-checkout set' command to reflect the expected cone in the working
directory.
+When changing the sparse-checkout patterns in cone mode, Git will inspect each
+tracked directory that is not within the sparse-checkout cone to see if it
+contains any untracked files. If all of those files are ignored due to the
+`.gitignore` patterns, then the directory will be deleted. If any of the
+untracked files within that directory is not ignored, then no deletions will
+occur within that directory and a warning message will appear. If these files
+are important, then reset your sparse-checkout definition so they are included,
+use `git add` and `git commit` to store them, then remove any remaining files
+manually to ensure Git can behave optimally.
+
SUBMODULES
----------
diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt
index 83f38e3198..4a2c3e0408 100644
--- a/Documentation/git-status.txt
+++ b/Documentation/git-status.txt
@@ -207,26 +207,29 @@ show tracked paths:
* ' ' = unmodified
* 'M' = modified
+* 'T' = file type changed (regular file, symbolic link or submodule)
* 'A' = added
* 'D' = deleted
* 'R' = renamed
-* 'C' = copied
+* 'C' = copied (if config option status.renames is set to "copies")
* 'U' = updated but unmerged
....
X Y Meaning
-------------------------------------------------
[AMD] not updated
-M [ MD] updated in index
-A [ MD] added to index
+M [ MTD] updated in index
+T [ MTD] type changed in index
+A [ MTD] added to index
D deleted from index
-R [ MD] renamed in index
-C [ MD] copied in index
-[MARC] index and work tree matches
-[ MARC] M work tree changed since index
-[ MARC] D deleted in work tree
-[ D] R renamed in work tree
-[ D] C copied in work tree
+R [ MTD] renamed in index
+C [ MTD] copied in index
+[MTARC] index and work tree matches
+[ MTARC] M work tree changed since index
+[ MTARC] T type changed in work tree since index
+[ MTARC] D deleted in work tree
+ R renamed in work tree
+ C copied in work tree
-------------------------------------------------
D D unmerged, both deleted
A U unmerged, added by us
@@ -363,7 +366,7 @@ Field Meaning
Unmerged entries have the following format; the first character is
a "u" to distinguish from ordinary changed entries.
- u <xy> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path>
+ u <XY> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path>
....
Field Meaning
diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt
index d5776ffcfd..222b556d7a 100644
--- a/Documentation/git-svn.txt
+++ b/Documentation/git-svn.txt
@@ -678,7 +678,6 @@ config key: svn.authorsProg
--strategy=<strategy>::
-p::
--rebase-merges::
---preserve-merges (DEPRECATED)::
These are only used with the 'dcommit' and 'rebase' commands.
+
Passed directly to 'git rebase' when using 'dcommit' if a
diff --git a/Documentation/git-upload-pack.txt b/Documentation/git-upload-pack.txt
index 9822c1eb1a..8f87b23ea8 100644
--- a/Documentation/git-upload-pack.txt
+++ b/Documentation/git-upload-pack.txt
@@ -36,14 +36,26 @@ OPTIONS
This fits with the HTTP POST request processing model where
a program may read the request, write a response, and must exit.
---advertise-refs::
- Only the initial ref advertisement is output, and the program exits
- immediately. This fits with the HTTP GET request model, where
- no request content is received but a response must be produced.
+--http-backend-info-refs::
+ Used by linkgit:git-http-backend[1] to serve up
+ `$GIT_URL/info/refs?service=git-upload-pack` requests. See
+ "Smart Clients" in link:technical/http-protocol.html[the HTTP
+ transfer protocols] documentation and "HTTP Transport" in
+ link:technical/protocol-v2.html[the Git Wire Protocol, Version
+ 2] documentation. Also understood by
+ linkgit:git-receive-pack[1].
<directory>::
The repository to sync from.
+ENVIRONMENT
+-----------
+
+`GIT_PROTOCOL`::
+ Internal variable used for handshaking the wire protocol. Server
+ admins may need to configure some transports to allow this
+ variable to be passed. See the discussion in linkgit:git[1].
+
SEE ALSO
--------
linkgit:gitnamespaces[7]
diff --git a/Documentation/git-version.txt b/Documentation/git-version.txt
new file mode 100644
index 0000000000..80fa7754a6
--- /dev/null
+++ b/Documentation/git-version.txt
@@ -0,0 +1,28 @@
+git-version(1)
+==============
+
+NAME
+----
+git-version - Display version information about Git
+
+SYNOPSIS
+--------
+[verse]
+'git version' [--build-options]
+
+DESCRIPTION
+-----------
+With no options given, the version of 'git' is printed on the standard output.
+
+Note that `git --version` is identical to `git version` because the
+former is internally converted into the latter.
+
+OPTIONS
+-------
+--build-options::
+ Include additional information about how git was built for diagnostic
+ purposes.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 6dd241ef83..d63c65e67d 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -41,6 +41,10 @@ OPTIONS
-------
--version::
Prints the Git suite version that the 'git' program came from.
++
+This option is internaly converted to `git version ...` and accepts
+the same options as the linkgit:git-version[1] command. If `--help` is
+also given, it takes precedence over `--version`.
--help::
Prints the synopsis and a list of the most commonly used
@@ -863,15 +867,16 @@ for full details.
end user, to be recorded in the body of the reflog.
`GIT_REF_PARANOIA`::
- If set to `1`, include broken or badly named refs when iterating
- over lists of refs. In a normal, non-corrupted repository, this
- does nothing. However, enabling it may help git to detect and
- abort some operations in the presence of broken refs. Git sets
- this variable automatically when performing destructive
- operations like linkgit:git-prune[1]. You should not need to set
- it yourself unless you want to be paranoid about making sure
- an operation has touched every ref (e.g., because you are
- cloning a repository to make a backup).
+ If set to `0`, ignore broken or badly named refs when iterating
+ over lists of refs. Normally Git will try to include any such
+ refs, which may cause some operations to fail. This is usually
+ preferable, as potentially destructive operations (e.g.,
+ linkgit:git-prune[1]) are better off aborting rather than
+ ignoring broken refs (and thus considering the history they
+ point to as not worth saving). The default value is `1` (i.e.,
+ be paranoid about detecting and aborting all operations). You
+ should not normally need to set this to `0`, but it may be
+ useful when trying to salvage data from a corrupted repository.
`GIT_ALLOW_PROTOCOL`::
If set to a colon-separated list of protocols, behave as if
@@ -894,6 +899,21 @@ for full details.
Contains a colon ':' separated list of keys with optional values
'key[=value]'. Presence of unknown keys and values must be
ignored.
++
+Note that servers may need to be configured to allow this variable to
+pass over some transports. It will be propagated automatically when
+accessing local repositories (i.e., `file://` or a filesystem path), as
+well as over the `git://` protocol. For git-over-http, it should work
+automatically in most configurations, but see the discussion in
+linkgit:git-http-backend[1]. For git-over-ssh, the ssh server may need
+to be configured to allow clients to pass this variable (e.g., by using
+`AcceptEnv GIT_PROTOCOL` with OpenSSH).
++
+This configuration is optional. If the variable is not propagated, then
+clients will fall back to the original "v0" protocol (but may miss out
+on some performance improvements or features). This variable currently
+only affects clones and fetches; it is not yet used for pushes (but may
+be in the future).
`GIT_OPTIONAL_LOCKS`::
If set to `0`, Git will complete any requested operation without
diff --git a/Documentation/gitfaq.txt b/Documentation/gitfaq.txt
index afdaeab850..8c1f2d5675 100644
--- a/Documentation/gitfaq.txt
+++ b/Documentation/gitfaq.txt
@@ -275,7 +275,7 @@ best to always use a regular merge commit.
[[merge-two-revert-one]]
If I make a change on two branches but revert it on one, why does the merge of those branches include the change?::
- By default, when Git does a merge, it uses a strategy called the recursive
+ By default, when Git does a merge, it uses a strategy called the `ort`
strategy, which does a fancy three-way merge. In such a case, when Git
performs the merge, it considers exactly three points: the two heads and a
third point, called the _merge base_, which is usually the common ancestor of
diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt
index 52565014c1..61ec157c2f 100644
--- a/Documentation/merge-options.txt
+++ b/Documentation/merge-options.txt
@@ -2,6 +2,9 @@
--no-commit::
Perform the merge and commit the result. This option can
be used to override --no-commit.
+ifdef::git-pull[]
+ Only useful when merging.
+endif::git-pull[]
+
With --no-commit perform the merge and stop just before creating
a merge commit, to give the user a chance to inspect and further
@@ -39,6 +42,7 @@ set to `no` at the beginning of them.
to `MERGE_MSG` before being passed on to the commit machinery in the
case of a merge conflict.
+ifdef::git-merge[]
--ff::
--no-ff::
--ff-only::
@@ -47,6 +51,22 @@ set to `no` at the beginning of them.
default unless merging an annotated (and possibly signed) tag
that is not stored in its natural place in the `refs/tags/`
hierarchy, in which case `--no-ff` is assumed.
+endif::git-merge[]
+ifdef::git-pull[]
+--ff-only::
+ Only update to the new history if there is no divergent local
+ history. This is the default when no method for reconciling
+ divergent histories is provided (via the --rebase=* flags).
+
+--ff::
+--no-ff::
+ When merging rather than rebasing, specifies how a merge is
+ handled when the merged-in history is already a descendant of
+ the current history. If merging is requested, `--ff` is the
+ default unless merging an annotated (and possibly signed) tag
+ that is not stored in its natural place in the `refs/tags/`
+ hierarchy, in which case `--no-ff` is assumed.
+endif::git-pull[]
+
With `--ff`, when possible resolve the merge as a fast-forward (only
update the branch pointer to match the merged branch; do not create a
@@ -55,9 +75,11 @@ descendant of the current history), create a merge commit.
+
With `--no-ff`, create a merge commit in all cases, even when the merge
could instead be resolved as a fast-forward.
+ifdef::git-merge[]
+
With `--ff-only`, resolve the merge as a fast-forward when possible.
When not possible, refuse to merge and exit with a non-zero status.
+endif::git-merge[]
-S[<keyid>]::
--gpg-sign[=<keyid>]::
@@ -73,6 +95,9 @@ When not possible, refuse to merge and exit with a non-zero status.
In addition to branch names, populate the log message with
one-line descriptions from at most <n> actual commits that are being
merged. See also linkgit:git-fmt-merge-msg[1].
+ifdef::git-pull[]
+ Only useful when merging.
+endif::git-pull[]
+
With --no-log do not list one-line descriptions from the
actual commits being merged.
@@ -102,18 +127,25 @@ With --no-squash perform the merge and commit the result. This
option can be used to override --squash.
+
With --squash, --commit is not allowed, and will fail.
+ifdef::git-pull[]
++
+Only useful when merging.
+endif::git-pull[]
--no-verify::
This option bypasses the pre-merge and commit-msg hooks.
See also linkgit:githooks[5].
+ifdef::git-pull[]
+ Only useful when merging.
+endif::git-pull[]
-s <strategy>::
--strategy=<strategy>::
Use the given merge strategy; can be supplied more than
once to specify them in the order they should be tried.
If there is no `-s` option, a built-in list of strategies
- is used instead ('git merge-recursive' when merging a single
- head, 'git merge-octopus' otherwise).
+ is used instead (`ort` when merging a single head,
+ `octopus` otherwise).
-X <option>::
--strategy-option=<option>::
@@ -127,6 +159,10 @@ With --squash, --commit is not allowed, and will fail.
default trust model, this means the signing key has been signed by
a trusted key. If the tip commit of the side branch is not signed
with a valid key, the merge is aborted.
+ifdef::git-pull[]
++
+Only useful when merging.
+endif::git-pull[]
--summary::
--no-summary::
@@ -167,3 +203,7 @@ endif::git-pull[]
projects that started their lives independently. As that is
a very rare occasion, no configuration variable to enable
this by default exists and will not be added.
+ifdef::git-pull[]
++
+Only useful when merging.
+endif::git-pull[]
diff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt
index 2912de706b..5fc54ec060 100644
--- a/Documentation/merge-strategies.txt
+++ b/Documentation/merge-strategies.txt
@@ -6,28 +6,23 @@ backend 'merge strategies' to be chosen with `-s` option. Some strategies
can also take their own options, which can be passed by giving `-X<option>`
arguments to `git merge` and/or `git pull`.
-resolve::
- This can only resolve two heads (i.e. the current branch
- and another branch you pulled from) using a 3-way merge
- algorithm. It tries to carefully detect criss-cross
- merge ambiguities and is considered generally safe and
- fast.
-
-recursive::
- This can only resolve two heads using a 3-way merge
- algorithm. When there is more than one common
- ancestor that can be used for 3-way merge, it creates a
- merged tree of the common ancestors and uses that as
- the reference tree for the 3-way merge. This has been
- reported to result in fewer merge conflicts without
- causing mismerges by tests done on actual merge commits
- taken from Linux 2.6 kernel development history.
- Additionally this can detect and handle merges involving
- renames, but currently cannot make use of detected
- copies. This is the default merge strategy when pulling
- or merging one branch.
+ort::
+ This is the default merge strategy when pulling or merging one
+ branch. This strategy can only resolve two heads using a
+ 3-way merge algorithm. When there is more than one common
+ ancestor that can be used for 3-way merge, it creates a merged
+ tree of the common ancestors and uses that as the reference
+ tree for the 3-way merge. This has been reported to result in
+ fewer merge conflicts without causing mismerges by tests done
+ on actual merge commits taken from Linux 2.6 kernel
+ development history. Additionally this strategy can detect
+ and handle merges involving renames. It does not make use of
+ detected copies. The name for this algorithm is an acronym
+ ("Ostensibly Recursive's Twin") and came from the fact that it
+ was written as a replacement for the previous default
+ algorithm, `recursive`.
+
-The 'recursive' strategy can take the following options:
+The 'ort' strategy can take the following options:
ours;;
This option forces conflicting hunks to be auto-resolved cleanly by
@@ -43,19 +38,6 @@ theirs;;
This is the opposite of 'ours'; note that, unlike 'ours', there is
no 'theirs' merge strategy to confuse this merge option with.
-patience;;
- With this option, 'merge-recursive' spends a little extra time
- to avoid mismerges that sometimes occur due to unimportant
- matching lines (e.g., braces from distinct functions). Use
- this when the branches to be merged have diverged wildly.
- See also linkgit:git-diff[1] `--patience`.
-
-diff-algorithm=[patience|minimal|histogram|myers];;
- Tells 'merge-recursive' to use a different diff algorithm, which
- can help avoid mismerges that occur due to unimportant matching
- lines (such as braces from distinct functions). See also
- linkgit:git-diff[1] `--diff-algorithm`.
-
ignore-space-change;;
ignore-all-space;;
ignore-space-at-eol;;
@@ -84,11 +66,6 @@ no-renormalize;;
Disables the `renormalize` option. This overrides the
`merge.renormalize` configuration variable.
-no-renames;;
- Turn off rename detection. This overrides the `merge.renames`
- configuration variable.
- See also linkgit:git-diff[1] `--no-renames`.
-
find-renames[=<n>];;
Turn on rename detection, optionally setting the similarity
threshold. This is the default. This overrides the
@@ -105,6 +82,46 @@ subtree[=<path>];;
is prefixed (or stripped from the beginning) to make the shape of
two trees to match.
+recursive::
+ This can only resolve two heads using a 3-way merge
+ algorithm. When there is more than one common
+ ancestor that can be used for 3-way merge, it creates a
+ merged tree of the common ancestors and uses that as
+ the reference tree for the 3-way merge. This has been
+ reported to result in fewer merge conflicts without
+ causing mismerges by tests done on actual merge commits
+ taken from Linux 2.6 kernel development history.
+ Additionally this can detect and handle merges involving
+ renames. It does not make use of detected copies. This was
+ the default strategy for resolving two heads from Git v0.99.9k
+ until v2.33.0.
++
+The 'recursive' strategy takes the same options as 'ort'. However,
+there are three additional options that 'ort' ignores (not documented
+above) that are potentially useful with the 'recursive' strategy:
+
+patience;;
+ Deprecated synonym for `diff-algorithm=patience`.
+
+diff-algorithm=[patience|minimal|histogram|myers];;
+ Use a different diff algorithm while merging, which can help
+ avoid mismerges that occur due to unimportant matching lines
+ (such as braces from distinct functions). See also
+ linkgit:git-diff[1] `--diff-algorithm`. Note that `ort`
+ specifically uses `diff-algorithm=histogram`, while `recursive`
+ defaults to the `diff.algorithm` config setting.
+
+no-renames;;
+ Turn off rename detection. This overrides the `merge.renames`
+ configuration variable.
+ See also linkgit:git-diff[1] `--no-renames`.
+
+resolve::
+ This can only resolve two heads (i.e. the current branch
+ and another branch you pulled from) using a 3-way merge
+ algorithm. It tries to carefully detect criss-cross
+ merge ambiguities. It does not handle renames.
+
octopus::
This resolves cases with more than two heads, but refuses to do
a complex merge that needs manual resolution. It is
@@ -121,13 +138,13 @@ ours::
the 'recursive' merge strategy.
subtree::
- This is a modified recursive strategy. When merging trees A and
+ This is a modified `ort` strategy. When merging trees A and
B, if B corresponds to a subtree of A, B is first adjusted to
match the tree structure of A, instead of reading the trees at
the same level. This adjustment is also done to the common
ancestor tree.
-With the strategies that use 3-way merge (including the default, 'recursive'),
+With the strategies that use 3-way merge (including the default, 'ort'),
if a change is made on both branches, but later reverted on one of the
branches, that change will be present in the merged result; some people find
this behavior confusing. It occurs because only the heads and the merge base
diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt
index 27ddaf84a1..b3af850608 100644
--- a/Documentation/pretty-options.txt
+++ b/Documentation/pretty-options.txt
@@ -33,14 +33,16 @@ people using 80-column terminals.
used together.
--encoding=<encoding>::
- The commit objects record the encoding used for the log message
+ Commit objects record the character encoding used for the log message
in their encoding header; this option can be used to tell the
command to re-code the commit log message in the encoding
preferred by the user. For non plumbing commands this
defaults to UTF-8. Note that if an object claims to be encoded
in `X` and we are outputting in `X`, we will output the object
verbatim; this means that invalid sequences in the original
- commit may be copied to the output.
+ commit may be copied to the output. Likewise, if iconv(3) fails
+ to convert the commit, we will output the original object
+ verbatim, along with a warning.
--expand-tabs=<n>::
--expand-tabs::
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index 24569b06d1..b7bd27e171 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -968,6 +968,11 @@ list of the missing objects. Object IDs are prefixed with a ``?'' character.
objects.
endif::git-rev-list[]
+--unsorted-input::
+ Show commits in the order they were given on the command line instead
+ of sorting them in reverse chronological order by commit time. Cannot
+ be combined with `--no-walk` or `--no-walk=sorted`.
+
--no-walk[=(sorted|unsorted)]::
Only show the given commits, but do not traverse their ancestors.
This has no effect if a range is specified. If the argument
@@ -975,7 +980,8 @@ endif::git-rev-list[]
given on the command line. Otherwise (if `sorted` or no argument
was given), the commits are shown in reverse chronological order
by commit time.
- Cannot be combined with `--graph`.
+ Cannot be combined with `--graph`. Cannot be combined with
+ `--unsorted-input` if `sorted` or no argument was given.
--do-walk::
Overrides a previous `--no-walk`.
diff --git a/Documentation/technical/api-parse-options.txt b/Documentation/technical/api-parse-options.txt
index 5a60bbfa7f..acfd5dc1d8 100644
--- a/Documentation/technical/api-parse-options.txt
+++ b/Documentation/technical/api-parse-options.txt
@@ -198,11 +198,6 @@ There are some macros to easily define options:
The filename will be prefixed by passing the filename along with
the prefix argument of `parse_options()` to `prefix_filename()`.
-`OPT_ARGUMENT(long, &int_var, description)`::
- Introduce a long-option argument that will be kept in `argv[]`.
- If this option was seen, `int_var` will be set to one (except
- if a `NULL` pointer was passed).
-
`OPT_NUMBER_CALLBACK(&var, description, func_ptr)`::
Recognize numerical options like -123 and feed the integer as
if it was an argument to the function given by `func_ptr`.
diff --git a/Documentation/technical/api-trace2.txt b/Documentation/technical/api-trace2.txt
index 037a91cbca..ef7fe02a8f 100644
--- a/Documentation/technical/api-trace2.txt
+++ b/Documentation/technical/api-trace2.txt
@@ -493,6 +493,20 @@ about specific error arguments.
}
------------
+`"cmd_ancestry"`::
+ This event contains the text command name for the parent (and earlier
+ generations of parents) of the current process, in an array ordered from
+ nearest parent to furthest great-grandparent. It may not be implemented
+ on all platforms.
++
+------------
+{
+ "event":"cmd_ancestry",
+ ...
+ "ancestry":["bash","tmux: server","systemd"]
+}
+------------
+
`"cmd_name"`::
This event contains the command name for this git process
and the hierarchy of commands from parent git processes.
@@ -599,6 +613,46 @@ stopping after the waitpid() and includes OS process creation overhead).
So this time will be slightly larger than the atexit time reported by
the child process itself.
+`"child_ready"`::
+ This event is generated after the current process has started
+ a background process and released all handles to it.
++
+------------
+{
+ "event":"child_ready",
+ ...
+ "child_id":2,
+ "pid":14708, # child PID
+ "ready":"ready", # child ready state
+ "t_rel":0.110605 # observed run-time of child process
+}
+------------
++
+Note that the session-id of the child process is not available to
+the current/spawning process, so the child's PID is reported here as
+a hint for post-processing. (But it is only a hint because the child
+process may be a shell script which doesn't have a session-id.)
++
+This event is generated after the child is started in the background
+and given a little time to boot up and start working. If the child
+startups normally and while the parent is still waiting, the "ready"
+field will have the value "ready".
+If the child is too slow to start and the parent times out, the field
+will have the value "timeout".
+If the child starts but the parent is unable to probe it, the field
+will have the value "error".
++
+After the parent process emits this event, it will release all of its
+handles to the child process and treat the child as a background
+daemon. So even if the child does eventually finish booting up,
+the parent will not emit an updated event.
++
+Note that the `t_rel` field contains the observed run time in seconds
+when the parent released the child process into the background.
+The child is assumed to be a long-running daemon process and may
+outlive the parent process. So the parent's child event times should
+not be compared to the child's atexit times.
+
`"exec"`::
This event is generated before git attempts to `exec()`
another command rather than starting a child process.
diff --git a/Documentation/technical/directory-rename-detection.txt b/Documentation/technical/directory-rename-detection.txt
index 49b83ef3cc..029ee2cedc 100644
--- a/Documentation/technical/directory-rename-detection.txt
+++ b/Documentation/technical/directory-rename-detection.txt
@@ -2,9 +2,9 @@ Directory rename detection
==========================
Rename detection logic in diffcore-rename that checks for renames of
-individual files is aggregated and analyzed in merge-recursive for cases
-where combinations of renames indicate that a full directory has been
-renamed.
+individual files is also aggregated there and then analyzed in either
+merge-ort or merge-recursive for cases where combinations of renames
+indicate that a full directory has been renamed.
Scope of abilities
------------------
@@ -88,9 +88,11 @@ directory rename detection support in:
Folks have requested in the past that `git diff` detect directory
renames and somehow simplify its output. It is not clear whether this
would be desirable or how the output should be simplified, so this was
- simply not implemented. Further, to implement this, directory rename
- detection logic would need to move from merge-recursive to
- diffcore-rename.
+ simply not implemented. Also, while diffcore-rename has most of the
+ logic for detecting directory renames, some of the logic is still found
+ within merge-ort and merge-recursive. Fully supporting directory
+ rename detection in diffs would require copying or moving the remaining
+ bits of logic to the diff machinery.
* am
diff --git a/Documentation/technical/http-protocol.txt b/Documentation/technical/http-protocol.txt
index 96d89ea9b2..cc5126cfed 100644
--- a/Documentation/technical/http-protocol.txt
+++ b/Documentation/technical/http-protocol.txt
@@ -225,6 +225,9 @@ The client may send Extra Parameters (see
Documentation/technical/pack-protocol.txt) as a colon-separated string
in the Git-Protocol HTTP header.
+Uses the `--http-backend-info-refs` option to
+linkgit:git-upload-pack[1].
+
Dumb Server Response
^^^^^^^^^^^^^^^^^^^^
Dumb servers MUST respond with the dumb server reply format.
diff --git a/Documentation/technical/multi-pack-index.txt b/Documentation/technical/multi-pack-index.txt
index 1a73c3ee20..86f40f2490 100644
--- a/Documentation/technical/multi-pack-index.txt
+++ b/Documentation/technical/multi-pack-index.txt
@@ -36,7 +36,9 @@ Design Details
directory of an alternate. It refers only to packfiles in that
same directory.
-- The core.multiPackIndex config setting must be on to consume MIDX files.
+- The core.multiPackIndex config setting must be on (which is the
+ default) to consume MIDX files. Setting it to `false` prevents
+ Git from reading a MIDX file, even if one exists.
- The file format includes parameters for the object ID hash
function, so a future change of hash algorithm does not require
diff --git a/Documentation/technical/protocol-v2.txt b/Documentation/technical/protocol-v2.txt
index 1040d85319..21e8258ccf 100644
--- a/Documentation/technical/protocol-v2.txt
+++ b/Documentation/technical/protocol-v2.txt
@@ -42,7 +42,8 @@ Initial Client Request
In general a client can request to speak protocol v2 by sending
`version=2` through the respective side-channel for the transport being
used which inevitably sets `GIT_PROTOCOL`. More information can be
-found in `pack-protocol.txt` and `http-protocol.txt`. In all cases the
+found in `pack-protocol.txt` and `http-protocol.txt`, as well as the
+`GIT_PROTOCOL` definition in `git.txt`. In all cases the
response from the server is the capability advertisement.
Git Transport
@@ -58,6 +59,8 @@ SSH and File Transport
When using either the ssh:// or file:// transport, the GIT_PROTOCOL
environment variable must be set explicitly to include "version=2".
+The server may need to be configured to allow this environment variable
+to pass.
HTTP Transport
~~~~~~~~~~~~~~
@@ -81,6 +84,12 @@ A v2 server would reply:
Subsequent requests are then made directly to the service
`$GIT_URL/git-upload-pack`. (This works the same for git-receive-pack).
+Uses the `--http-backend-info-refs` option to
+linkgit:git-upload-pack[1].
+
+The server may need to be configured to pass this header's contents via
+the `GIT_PROTOCOL` variable. See the discussion in `git-http-backend.txt`.
+
Capability Advertisement
------------------------
@@ -190,7 +199,11 @@ ls-refs takes in the following arguments:
Show peeled tags.
ref-prefix <prefix>
When specified, only references having a prefix matching one of
- the provided prefixes are displayed.
+ the provided prefixes are displayed. Multiple instances may be
+ given, in which case references matching any prefix will be
+ shown. Note that this is purely for optimization; a server MAY
+ show refs not matching the prefix if it chooses, and clients
+ should filter the result themselves.
If the 'unborn' feature is advertised the following argument can be
included in the client's request.
diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt
index 96240598e3..865074bed4 100644
--- a/Documentation/user-manual.txt
+++ b/Documentation/user-manual.txt
@@ -3190,7 +3190,7 @@ that *updated* thing--the old state that you added originally ends up
not being pointed to by any commit or tree, so it's now a dangling blob
object.
-Similarly, when the "recursive" merge strategy runs, and finds that
+Similarly, when the "ort" merge strategy runs, and finds that
there are criss-cross merges and thus more than one merge base (which is
fairly unusual, but it does happen), it will generate one temporary
midway tree (or possibly even more, if you had lots of criss-crossing
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
index b1c0d4eb2e..b48559d654 100755
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
@@ -1,7 +1,7 @@
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v2.33.0
+DEF_VER=v2.33.GIT
LF='
'
diff --git a/INSTALL b/INSTALL
index 66389ce059..4140a3f5c8 100644
--- a/INSTALL
+++ b/INSTALL
@@ -138,12 +138,15 @@ Issues of note:
BLK_SHA1. Also included is a version optimized for PowerPC
(PPC_SHA1).
- - "libcurl" library is used by git-http-fetch, git-fetch, and, if
- the curl version >= 7.34.0, for git-imap-send. You might also
- want the "curl" executable for debugging purposes. If you do not
- use http:// or https:// repositories, and do not want to put
- patches into an IMAP mailbox, you do not have to have them
- (use NO_CURL).
+ - "libcurl" library is used for fetching and pushing
+ repositories over http:// or https://, as well as by
+ git-imap-send if the curl version is >= 7.34.0. If you do
+ not need that functionality, use NO_CURL to build without
+ it.
+
+ Git requires version "7.19.4" or later of "libcurl" to build
+ without NO_CURL. This version requirement may be bumped in
+ the future.
- "expat" library; git-http-push uses it for remote lock
management over DAV. Similar to "curl" above, this is optional
diff --git a/Makefile b/Makefile
index 9573190f1d..381bed2c1d 100644
--- a/Makefile
+++ b/Makefile
@@ -409,15 +409,6 @@ all::
# Define NEEDS_LIBRT if your platform requires linking with librt (glibc version
# before 2.17) for clock_gettime and CLOCK_MONOTONIC.
#
-# Define USE_PARENS_AROUND_GETTEXT_N to "yes" if your compiler happily
-# compiles the following initialization:
-#
-# static const char s[] = ("FOO");
-#
-# and define it to "no" if you need to remove the parentheses () around the
-# constant. The default is "auto", which means to use parentheses if your
-# compiler is detected to support it.
-#
# Define HAVE_BSD_SYSCTL if your platform has a BSD-compatible sysctl function.
#
# Define HAVE_GETDELIM if your system has the getdelim() function.
@@ -465,6 +456,9 @@ all::
# the global variable _wpgmptr containing the absolute path of the current
# executable (this is the case on Windows).
#
+# INSTALL_STRIP can be set to "-s" to strip binaries during installation,
+# if your $(INSTALL) command supports the option.
+#
# Define GENERATE_COMPILATION_DATABASE to "yes" to generate JSON compilation
# database entries during compilation if your compiler supports it, using the
# `-MJ` flag. The JSON entries will be placed in the `compile_commands/`
@@ -495,10 +489,9 @@ all::
# setting this flag the exceptions are removed, and all of
# -Wextra is used.
#
-# pedantic:
+# no-pedantic:
#
-# Enable -pedantic compilation. This also disables
-# USE_PARENS_AROUND_GETTEXT_N to produce only relevant warnings.
+# Disable -pedantic compilation.
GIT-VERSION-FILE: FORCE
@$(SHELL_PATH) ./GIT-VERSION-GEN
@@ -616,7 +609,6 @@ SCRIPT_SH += git-submodule.sh
SCRIPT_SH += git-web--browse.sh
SCRIPT_LIB += git-mergetool--lib
-SCRIPT_LIB += git-rebase--preserve-merges
SCRIPT_LIB += git-sh-i18n
SCRIPT_LIB += git-sh-setup
@@ -824,6 +816,10 @@ XDIFF_LIB = xdiff/lib.a
GENERATED_H += command-list.h
GENERATED_H += config-list.h
+GENERATED_H += hook-list.h
+
+.PHONY: generated-hdrs
+generated-hdrs: $(GENERATED_H)
LIB_H := $(sort $(patsubst ./%,%,$(shell git ls-files '*.h' ':!t/' ':!Documentation/' 2>/dev/null || \
$(FIND) . \
@@ -909,6 +905,7 @@ LIB_OBJS += hash-lookup.o
LIB_OBJS += hashmap.o
LIB_OBJS += help.o
LIB_OBJS += hex.o
+LIB_OBJS += hook.o
LIB_OBJS += ident.o
LIB_OBJS += json-writer.o
LIB_OBJS += kwset.o
@@ -1221,6 +1218,9 @@ PTHREAD_CFLAGS =
SPARSE_FLAGS ?=
SP_EXTRA_FLAGS = -Wno-universal-initializer
+# For informing GIT-BUILD-OPTIONS of the SANITIZE=leak target
+SANITIZE_LEAK =
+
# For the 'coccicheck' target; setting SPATCH_BATCH_SIZE higher will
# usually result in less CPU usage at the cost of higher peak memory.
# Setting it to 0 will feed all files in a single spatch invocation.
@@ -1265,6 +1265,7 @@ BASIC_CFLAGS += -DSHA1DC_FORCE_ALIGNED_ACCESS
endif
ifneq ($(filter leak,$(SANITIZERS)),)
BASIC_CFLAGS += -DSUPPRESS_ANNOTATED_LEAKS
+SANITIZE_LEAK = YesCompiledWithIt
endif
ifneq ($(filter address,$(SANITIZERS)),)
NO_REGEX = NeededForASAN
@@ -1285,6 +1286,7 @@ endif
ifeq ($(COMPUTE_HEADER_DEPENDENCIES),auto)
dep_check = $(shell $(CC) $(ALL_CFLAGS) \
+ -Wno-pedantic \
-c -MF /dev/null -MQ /dev/null -MMD -MP \
-x c /dev/null -o /dev/null 2>&1; \
echo $$?)
@@ -1310,6 +1312,7 @@ endif
ifeq ($(GENERATE_COMPILATION_DATABASE),yes)
compdb_check = $(shell $(CC) $(ALL_CFLAGS) \
+ -Wno-pedantic \
-c -MJ /dev/null \
-x c /dev/null -o /dev/null 2>&1; \
echo $$?)
@@ -1347,14 +1350,6 @@ ifneq (,$(SOCKLEN_T))
BASIC_CFLAGS += -Dsocklen_t=$(SOCKLEN_T)
endif
-ifeq (yes,$(USE_PARENS_AROUND_GETTEXT_N))
- BASIC_CFLAGS += -DUSE_PARENS_AROUND_GETTEXT_N=1
-else
-ifeq (no,$(USE_PARENS_AROUND_GETTEXT_N))
- BASIC_CFLAGS += -DUSE_PARENS_AROUND_GETTEXT_N=0
-endif
-endif
-
ifeq ($(uname_S),Darwin)
ifndef NO_FINK
ifeq ($(shell test -d /sw/lib && echo y),y)
@@ -1436,15 +1431,8 @@ else
REMOTE_CURL_NAMES = $(REMOTE_CURL_PRIMARY) $(REMOTE_CURL_ALIASES)
PROGRAM_OBJS += http-fetch.o
PROGRAMS += $(REMOTE_CURL_NAMES)
- curl_check := $(shell (echo 070908; $(CURL_CONFIG) --vernum | sed -e '/^70[BC]/s/^/0/') 2>/dev/null | sort -r | sed -ne 2p)
- ifeq "$(curl_check)" "070908"
- ifndef NO_EXPAT
- PROGRAM_OBJS += http-push.o
- else
- EXCLUDED_PROGRAMS += git-http-push
- endif
- else
- EXCLUDED_PROGRAMS += git-http-push
+ ifndef NO_EXPAT
+ PROGRAM_OBJS += http-push.o
endif
curl_check := $(shell (echo 072200; $(CURL_CONFIG) --vernum | sed -e '/^70[BC]/s/^/0/') 2>/dev/null | sort -r | sed -ne 2p)
ifeq "$(curl_check)" "072200"
@@ -1917,6 +1905,10 @@ ifneq ($(PROCFS_EXECUTABLE_PATH),)
BASIC_CFLAGS += '-DPROCFS_EXECUTABLE_PATH="$(procfs_executable_path_SQ)"'
endif
+ifndef HAVE_PLATFORM_PROCINFO
+ COMPAT_OBJS += compat/stub/procinfo.o
+endif
+
ifdef HAVE_NS_GET_EXECUTABLE_PATH
BASIC_CFLAGS += -DHAVE_NS_GET_EXECUTABLE_PATH
endif
@@ -2223,8 +2215,9 @@ git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS)
$(filter %.o,$^) $(LIBS)
help.sp help.s help.o: command-list.h
+hook.sp hook.s hook.o: hook-list.h
-builtin/help.sp builtin/help.s builtin/help.o: config-list.h GIT-PREFIX
+builtin/help.sp builtin/help.s builtin/help.o: config-list.h hook-list.h GIT-PREFIX
builtin/help.sp builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \
'-DGIT_HTML_PATH="$(htmldir_relative_SQ)"' \
'-DGIT_MAN_PATH="$(mandir_relative_SQ)"' \
@@ -2247,15 +2240,17 @@ $(BUILT_INS): git$X
config-list.h: generate-configlist.sh
config-list.h: Documentation/*config.txt Documentation/config/*.txt
- $(QUIET_GEN)$(SHELL_PATH) ./generate-configlist.sh \
- >$@+ && mv $@+ $@
+ $(QUIET_GEN)$(SHELL_PATH) ./generate-configlist.sh >$@
command-list.h: generate-cmdlist.sh command-list.txt
command-list.h: $(wildcard Documentation/git*.txt)
$(QUIET_GEN)$(SHELL_PATH) ./generate-cmdlist.sh \
$(patsubst %,--exclude-program %,$(EXCLUDED_PROGRAMS)) \
- command-list.txt >$@+ && mv $@+ $@
+ command-list.txt >$@
+
+hook-list.h: generate-hooklist.sh Documentation/githooks.txt
+ $(QUIET_GEN)$(SHELL_PATH) ./generate-hooklist.sh >$@
SCRIPT_DEFINES = $(SHELL_PATH_SQ):$(DIFF_SQ):$(GIT_VERSION):\
$(localedir_SQ):$(NO_CURL):$(USE_GETTEXT_SCHEME):$(SANE_TOOL_PATH_SQ):\
@@ -2477,7 +2472,6 @@ dep_args = -MF $(dep_file) -MQ $@ -MMD -MP
endif
ifneq ($(COMPUTE_HEADER_DEPENDENCIES),yes)
-dep_dirs =
missing_dep_dirs =
dep_args =
endif
@@ -2518,13 +2512,6 @@ ifneq ($(dep_files_present),)
include $(dep_files_present)
endif
else
-# Dependencies on header files, for platforms that do not support
-# the gcc -MMD option.
-#
-# Dependencies on automatically generated headers such as command-list.h
-# should _not_ be included here, since they are necessary even when
-# building an object for the first time.
-
$(OBJECTS): $(LIB_H) $(GENERATED_H)
endif
@@ -2603,10 +2590,10 @@ $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS
$(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS)
$(LIB_FILE): $(LIB_OBJS)
- $(QUIET_AR)$(AR) $(ARFLAGS) $@ $^
+ $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
$(XDIFF_LIB): $(XDIFF_OBJS)
- $(QUIET_AR)$(AR) $(ARFLAGS) $@ $^
+ $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
export DEFAULT_EDITOR DEFAULT_PAGER
@@ -2649,7 +2636,6 @@ XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --language=Perl \
--keyword=__ --keyword=N__ --keyword="__n:1,2"
LOCALIZED_C = $(C_OBJ:o=c) $(LIB_H) $(GENERATED_H)
LOCALIZED_SH = $(SCRIPT_SH)
-LOCALIZED_SH += git-rebase--preserve-merges.sh
LOCALIZED_SH += git-sh-setup.sh
LOCALIZED_PERL = $(SCRIPT_PERL)
@@ -2746,19 +2732,25 @@ FIND_SOURCE_FILES = ( \
| sed -e 's|^\./||' \
)
-$(ETAGS_TARGET): FORCE
- $(QUIET_GEN)$(RM) "$(ETAGS_TARGET)+" && \
- $(FIND_SOURCE_FILES) | xargs etags -a -o "$(ETAGS_TARGET)+" && \
- mv "$(ETAGS_TARGET)+" "$(ETAGS_TARGET)"
+FOUND_SOURCE_FILES = $(shell $(FIND_SOURCE_FILES))
+
+$(ETAGS_TARGET): $(FOUND_SOURCE_FILES)
+ $(QUIET_GEN)$(RM) $@+ && \
+ echo $(FOUND_SOURCE_FILES) | xargs etags -a -o $@+ && \
+ mv $@+ $@
+
+tags: $(FOUND_SOURCE_FILES)
+ $(QUIET_GEN)$(RM) $@+ && \
+ echo $(FOUND_SOURCE_FILES) | xargs ctags -a -o $@+ && \
+ mv $@+ $@
-tags: FORCE
- $(QUIET_GEN)$(RM) tags+ && \
- $(FIND_SOURCE_FILES) | xargs ctags -a -o tags+ && \
- mv tags+ tags
+cscope.out: $(FOUND_SOURCE_FILES)
+ $(QUIET_GEN)$(RM) $@+ && \
+ echo $(FOUND_SOURCE_FILES) | xargs cscope -f$@+ -b && \
+ mv $@+ $@
-cscope:
- $(RM) cscope*
- $(FIND_SOURCE_FILES) | xargs cscope -b
+.PHONY: cscope
+cscope: cscope.out
### Detect prefix changes
TRACK_PREFIX = $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ):\
@@ -2808,6 +2800,7 @@ GIT-BUILD-OPTIONS: FORCE
@echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@+
@echo PAGER_ENV=\''$(subst ','\'',$(subst ','\'',$(PAGER_ENV)))'\' >>$@+
@echo DC_SHA1=\''$(subst ','\'',$(subst ','\'',$(DC_SHA1)))'\' >>$@+
+ @echo SANITIZE_LEAK=\''$(subst ','\'',$(subst ','\'',$(SANITIZE_LEAK)))'\' >>$@+
@echo X=\'$(X)\' >>$@+
ifdef TEST_OUTPUT_DIRECTORY
@echo TEST_OUTPUT_DIRECTORY=\''$(subst ','\'',$(subst ','\'',$(TEST_OUTPUT_DIRECTORY)))'\' >>$@+
@@ -2849,6 +2842,11 @@ endif
ifdef GIT_TEST_PERL_FATAL_WARNINGS
@echo GIT_TEST_PERL_FATAL_WARNINGS=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_PERL_FATAL_WARNINGS)))'\' >>$@+
endif
+ifdef RUNTIME_PREFIX
+ @echo RUNTIME_PREFIX=\'true\' >>$@+
+else
+ @echo RUNTIME_PREFIX=\'false\' >>$@+
+endif
@if cmp $@+ $@ >/dev/null 2>&1; then $(RM) $@+; else mv $@+ $@; fi
### Detect Python interpreter path changes
@@ -2903,14 +2901,16 @@ check-sha1:: t/helper/test-tool$X
SP_OBJ = $(patsubst %.o,%.sp,$(C_OBJ))
-$(SP_OBJ): %.sp: %.c GIT-CFLAGS FORCE
+$(SP_OBJ): %.sp: %.c %.o GIT-CFLAGS
$(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \
- $(SPARSE_FLAGS) $(SP_EXTRA_FLAGS) $<
+ -Wsparse-error \
+ $(SPARSE_FLAGS) $(SP_EXTRA_FLAGS) $< && \
+ >$@
-.PHONY: sparse $(SP_OBJ)
+.PHONY: sparse
sparse: $(SP_OBJ)
-EXCEPT_HDRS := command-list.h config-list.h unicode-width.h compat/% xdiff/%
+EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/%
ifndef GCRYPT_SHA256
EXCEPT_HDRS += sha256/gcrypt.h
endif
@@ -2932,7 +2932,8 @@ hdr-check: $(HCO)
style:
git clang-format --style file --diff --extensions c,h
-check: config-list.h command-list.h
+.PHONY: check
+check: $(GENERATED_H)
@if sparse; \
then \
echo >&2 "Use 'make sparse' instead"; \
@@ -2942,7 +2943,7 @@ check: config-list.h command-list.h
exit 1; \
fi
-FOUND_C_SOURCES = $(filter %.c,$(shell $(FIND_SOURCE_FILES)))
+FOUND_C_SOURCES = $(filter %.c,$(FOUND_SOURCE_FILES))
COCCI_SOURCES = $(filter-out $(THIRD_PARTY_SOURCES),$(FOUND_C_SOURCES))
%.cocci.patch: %.cocci $(COCCI_SOURCES)
@@ -2995,7 +2996,8 @@ mergetools_instdir = $(prefix)/$(mergetoolsdir)
endif
mergetools_instdir_SQ = $(subst ','\'',$(mergetools_instdir))
-install_bindir_programs := $(patsubst %,%$X,$(BINDIR_PROGRAMS_NEED_X)) $(BINDIR_PROGRAMS_NO_X)
+install_bindir_xprograms := $(patsubst %,%$X,$(BINDIR_PROGRAMS_NEED_X))
+install_bindir_programs := $(install_bindir_xprograms) $(BINDIR_PROGRAMS_NO_X)
.PHONY: profile-install profile-fast-install
profile-install: profile
@@ -3004,12 +3006,17 @@ profile-install: profile
profile-fast-install: profile-fast
$(MAKE) install
+INSTALL_STRIP =
+
install: all
$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)'
$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
- $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
+ $(INSTALL) $(INSTALL_STRIP) $(PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
+ $(INSTALL) $(SCRIPTS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
$(INSTALL) -m 644 $(SCRIPT_LIB) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
- $(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)'
+ $(INSTALL) $(INSTALL_STRIP) $(install_bindir_xprograms) '$(DESTDIR_SQ)$(bindir_SQ)'
+ $(INSTALL) $(BINDIR_PROGRAMS_NO_X) '$(DESTDIR_SQ)$(bindir_SQ)'
+
ifdef MSVC
# We DO NOT install the individual foo.o.pdb files because they
# have already been rolled up into the exe's pdb file.
@@ -3090,8 +3097,7 @@ endif
ln "$$execdir/git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
ln -s "git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
cp "$$execdir/git-remote-http$X" "$$execdir/$$p" || exit; } \
- done && \
- ./check_bindir "z$$bindir" "z$$execdir" "$$bindir/git-add$X"
+ done
.PHONY: install-gitweb install-doc install-man install-man-perl install-html install-info install-pdf
.PHONY: quick-install-doc quick-install-man quick-install-html
@@ -3229,6 +3235,7 @@ clean: profile-clean coverage-clean cocciclean
$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X
$(RM) $(TEST_PROGRAMS)
$(RM) $(FUZZ_PROGRAMS)
+ $(RM) $(SP_OBJ)
$(RM) $(HCC)
$(RM) -r bin-wrappers $(dep_dirs) $(compdb_dir) compile_commands.json
$(RM) -r po/build/
@@ -3267,7 +3274,7 @@ endif
.PHONY: all install profile-clean cocciclean clean strip
.PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell
-.PHONY: FORCE cscope
+.PHONY: FORCE
### Check documentation
#
diff --git a/RelNotes b/RelNotes
index f071367dc3..6c94469548 120000
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.33.0.txt \ No newline at end of file
+Documentation/RelNotes/2.34.0.txt \ No newline at end of file
diff --git a/add-interactive.c b/add-interactive.c
index 36ebdbdf7e..6498ae196f 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -102,8 +102,12 @@ struct prefix_item_list {
int *selected; /* for multi-selections */
size_t min_length, max_length;
};
-#define PREFIX_ITEM_LIST_INIT \
- { STRING_LIST_INIT_DUP, STRING_LIST_INIT_NODUP, NULL, 1, 4 }
+#define PREFIX_ITEM_LIST_INIT { \
+ .items = STRING_LIST_INIT_DUP, \
+ .sorted = STRING_LIST_INIT_NODUP, \
+ .min_length = 1, \
+ .max_length = 4, \
+}
static void prefix_item_list_clear(struct prefix_item_list *list)
{
diff --git a/advice.c b/advice.c
index 0b9c89c48a..1dfc91d176 100644
--- a/advice.c
+++ b/advice.c
@@ -4,37 +4,6 @@
#include "help.h"
#include "string-list.h"
-int advice_fetch_show_forced_updates = 1;
-int advice_push_update_rejected = 1;
-int advice_push_non_ff_current = 1;
-int advice_push_non_ff_matching = 1;
-int advice_push_already_exists = 1;
-int advice_push_fetch_first = 1;
-int advice_push_needs_force = 1;
-int advice_push_unqualified_ref_name = 1;
-int advice_push_ref_needs_update = 1;
-int advice_status_hints = 1;
-int advice_status_u_option = 1;
-int advice_status_ahead_behind_warning = 1;
-int advice_commit_before_merge = 1;
-int advice_reset_quiet_warning = 1;
-int advice_resolve_conflict = 1;
-int advice_sequencer_in_use = 1;
-int advice_implicit_identity = 1;
-int advice_detached_head = 1;
-int advice_set_upstream_failure = 1;
-int advice_object_name_warning = 1;
-int advice_amworkdir = 1;
-int advice_rm_hints = 1;
-int advice_add_embedded_repo = 1;
-int advice_ignored_hook = 1;
-int advice_waiting_for_editor = 1;
-int advice_graft_file_deprecated = 1;
-int advice_checkout_ambiguous_remote_branch_name = 1;
-int advice_submodule_alternate_error_strategy_die = 1;
-int advice_add_ignored_file = 1;
-int advice_add_empty_pathspec = 1;
-
static int advice_use_color = -1;
static char advice_colors[][COLOR_MAXLEN] = {
GIT_COLOR_RESET,
@@ -63,49 +32,12 @@ static const char *advise_get_color(enum color_advice ix)
}
static struct {
- const char *name;
- int *preference;
-} advice_config[] = {
- { "fetchShowForcedUpdates", &advice_fetch_show_forced_updates },
- { "pushUpdateRejected", &advice_push_update_rejected },
- { "pushNonFFCurrent", &advice_push_non_ff_current },
- { "pushNonFFMatching", &advice_push_non_ff_matching },
- { "pushAlreadyExists", &advice_push_already_exists },
- { "pushFetchFirst", &advice_push_fetch_first },
- { "pushNeedsForce", &advice_push_needs_force },
- { "pushUnqualifiedRefName", &advice_push_unqualified_ref_name },
- { "pushRefNeedsUpdate", &advice_push_ref_needs_update },
- { "statusHints", &advice_status_hints },
- { "statusUoption", &advice_status_u_option },
- { "statusAheadBehindWarning", &advice_status_ahead_behind_warning },
- { "commitBeforeMerge", &advice_commit_before_merge },
- { "resetQuiet", &advice_reset_quiet_warning },
- { "resolveConflict", &advice_resolve_conflict },
- { "sequencerInUse", &advice_sequencer_in_use },
- { "implicitIdentity", &advice_implicit_identity },
- { "detachedHead", &advice_detached_head },
- { "setUpstreamFailure", &advice_set_upstream_failure },
- { "objectNameWarning", &advice_object_name_warning },
- { "amWorkDir", &advice_amworkdir },
- { "rmHints", &advice_rm_hints },
- { "addEmbeddedRepo", &advice_add_embedded_repo },
- { "ignoredHook", &advice_ignored_hook },
- { "waitingForEditor", &advice_waiting_for_editor },
- { "graftFileDeprecated", &advice_graft_file_deprecated },
- { "checkoutAmbiguousRemoteBranchName", &advice_checkout_ambiguous_remote_branch_name },
- { "submoduleAlternateErrorStrategyDie", &advice_submodule_alternate_error_strategy_die },
- { "addIgnoredFile", &advice_add_ignored_file },
- { "addEmptyPathspec", &advice_add_empty_pathspec },
-
- /* make this an alias for backward compatibility */
- { "pushNonFastForward", &advice_push_update_rejected }
-};
-
-static struct {
const char *key;
int enabled;
} advice_setting[] = {
[ADVICE_ADD_EMBEDDED_REPO] = { "addEmbeddedRepo", 1 },
+ [ADVICE_ADD_EMPTY_PATHSPEC] = { "addEmptyPathspec", 1 },
+ [ADVICE_ADD_IGNORED_FILE] = { "addIgnoredFile", 1 },
[ADVICE_AM_WORK_DIR] = { "amWorkDir", 1 },
[ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME] = { "checkoutAmbiguousRemoteBranchName", 1 },
[ADVICE_COMMIT_BEFORE_MERGE] = { "commitBeforeMerge", 1 },
@@ -133,6 +65,7 @@ static struct {
[ADVICE_RM_HINTS] = { "rmHints", 1 },
[ADVICE_SEQUENCER_IN_USE] = { "sequencerInUse", 1 },
[ADVICE_SET_UPSTREAM_FAILURE] = { "setUpstreamFailure", 1 },
+ [ADVICE_SKIPPED_CHERRY_PICKS] = { "skippedCherryPicks", 1 },
[ADVICE_STATUS_AHEAD_BEHIND_WARNING] = { "statusAheadBehindWarning", 1 },
[ADVICE_STATUS_HINTS] = { "statusHints", 1 },
[ADVICE_STATUS_U_OPTION] = { "statusUoption", 1 },
@@ -221,13 +154,6 @@ int git_default_advice_config(const char *var, const char *value)
if (!skip_prefix(var, "advice.", &k))
return 0;
- for (i = 0; i < ARRAY_SIZE(advice_config); i++) {
- if (strcasecmp(k, advice_config[i].name))
- continue;
- *advice_config[i].preference = git_config_bool(var, value);
- break;
- }
-
for (i = 0; i < ARRAY_SIZE(advice_setting); i++) {
if (strcasecmp(k, advice_setting[i].key))
continue;
@@ -262,7 +188,7 @@ int error_resolve_conflict(const char *me)
error(_("It is not possible to %s because you have unmerged files."),
me);
- if (advice_resolve_conflict)
+ if (advice_enabled(ADVICE_RESOLVE_CONFLICT))
/*
* Message used both when 'git commit' fails and when
* other commands doing a merge do.
@@ -281,11 +207,16 @@ void NORETURN die_resolve_conflict(const char *me)
void NORETURN die_conclude_merge(void)
{
error(_("You have not concluded your merge (MERGE_HEAD exists)."));
- if (advice_resolve_conflict)
+ if (advice_enabled(ADVICE_RESOLVE_CONFLICT))
advise(_("Please, commit your changes before merging."));
die(_("Exiting because of unfinished merge."));
}
+void NORETURN die_ff_impossible(void)
+{
+ die(_("Not possible to fast-forward, aborting."));
+}
+
void advise_on_updating_sparse_paths(struct string_list *pathspec_list)
{
struct string_list_item *item;
@@ -293,15 +224,16 @@ void advise_on_updating_sparse_paths(struct string_list *pathspec_list)
if (!pathspec_list->nr)
return;
- fprintf(stderr, _("The following pathspecs didn't match any"
- " eligible path, but they do match index\n"
- "entries outside the current sparse checkout:\n"));
+ fprintf(stderr, _("The following paths and/or pathspecs matched paths that exist\n"
+ "outside of your sparse-checkout definition, so will not be\n"
+ "updated in the index:\n"));
for_each_string_list_item(item, pathspec_list)
fprintf(stderr, "%s\n", item->string);
advise_if_enabled(ADVICE_UPDATE_SPARSE_PATH,
- _("Disable or modify the sparsity rules if you intend"
- " to update such entries."));
+ _("If you intend to update such entries, try one of the following:\n"
+ "* Use the --sparse option.\n"
+ "* Disable or modify the sparsity rules."));
}
void detach_advice(const char *new_name)
diff --git a/advice.h b/advice.h
index 9f8ffc7354..601265fd10 100644
--- a/advice.h
+++ b/advice.h
@@ -5,37 +5,6 @@
struct string_list;
-extern int advice_fetch_show_forced_updates;
-extern int advice_push_update_rejected;
-extern int advice_push_non_ff_current;
-extern int advice_push_non_ff_matching;
-extern int advice_push_already_exists;
-extern int advice_push_fetch_first;
-extern int advice_push_needs_force;
-extern int advice_push_unqualified_ref_name;
-extern int advice_push_ref_needs_update;
-extern int advice_status_hints;
-extern int advice_status_u_option;
-extern int advice_status_ahead_behind_warning;
-extern int advice_commit_before_merge;
-extern int advice_reset_quiet_warning;
-extern int advice_resolve_conflict;
-extern int advice_sequencer_in_use;
-extern int advice_implicit_identity;
-extern int advice_detached_head;
-extern int advice_set_upstream_failure;
-extern int advice_object_name_warning;
-extern int advice_amworkdir;
-extern int advice_rm_hints;
-extern int advice_add_embedded_repo;
-extern int advice_ignored_hook;
-extern int advice_waiting_for_editor;
-extern int advice_graft_file_deprecated;
-extern int advice_checkout_ambiguous_remote_branch_name;
-extern int advice_submodule_alternate_error_strategy_die;
-extern int advice_add_ignored_file;
-extern int advice_add_empty_pathspec;
-
/*
* To add a new advice, you need to:
* Define a new advice_type.
@@ -45,6 +14,8 @@ extern int advice_add_empty_pathspec;
*/
enum advice_type {
ADVICE_ADD_EMBEDDED_REPO,
+ ADVICE_ADD_EMPTY_PATHSPEC,
+ ADVICE_ADD_IGNORED_FILE,
ADVICE_AM_WORK_DIR,
ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,
ADVICE_COMMIT_BEFORE_MERGE,
@@ -75,6 +46,7 @@ extern int advice_add_empty_pathspec;
ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,
ADVICE_UPDATE_SPARSE_PATH,
ADVICE_WAITING_FOR_EDITOR,
+ ADVICE_SKIPPED_CHERRY_PICKS,
};
int git_default_advice_config(const char *var, const char *value);
@@ -96,6 +68,7 @@ void advise_if_enabled(enum advice_type type, const char *advice, ...);
int error_resolve_conflict(const char *me);
void NORETURN die_resolve_conflict(const char *me);
void NORETURN die_conclude_merge(void);
+void NORETURN die_ff_impossible(void);
void advise_on_updating_sparse_paths(struct string_list *pathspec_list);
void detach_advice(const char *new_name);
diff --git a/apply.c b/apply.c
index 44bc31d6eb..43a0aebf4e 100644
--- a/apply.c
+++ b/apply.c
@@ -1917,6 +1917,7 @@ static struct fragment *parse_binary_hunk(struct apply_state *state,
state->linenr++;
buffer += llen;
+ size -= llen;
while (1) {
int byte_length, max_byte_length, newsize;
llen = linelen(buffer, size);
@@ -3467,6 +3468,21 @@ static int load_preimage(struct apply_state *state,
return 0;
}
+static int resolve_to(struct image *image, const struct object_id *result_id)
+{
+ unsigned long size;
+ enum object_type type;
+
+ clear_image(image);
+
+ image->buf = read_object_file(result_id, &type, &size);
+ if (!image->buf || type != OBJ_BLOB)
+ die("unable to read blob object %s", oid_to_hex(result_id));
+ image->len = size;
+
+ return 0;
+}
+
static int three_way_merge(struct apply_state *state,
struct image *image,
char *path,
@@ -3478,6 +3494,12 @@ static int three_way_merge(struct apply_state *state,
mmbuffer_t result = { NULL };
int status;
+ /* resolve trivial cases first */
+ if (oideq(base, ours))
+ return resolve_to(image, theirs);
+ else if (oideq(base, theirs) || oideq(ours, theirs))
+ return resolve_to(image, ours);
+
read_mmblob(&base_file, base);
read_mmblob(&our_file, ours);
read_mmblob(&their_file, theirs);
diff --git a/archive.c b/archive.c
index 3c266d1d7b..a3bbb09125 100644
--- a/archive.c
+++ b/archive.c
@@ -191,7 +191,7 @@ static int write_archive_entry(const struct object_id *oid, const char *base,
return err;
}
-static void queue_directory(const unsigned char *sha1,
+static void queue_directory(const struct object_id *oid,
struct strbuf *base, const char *filename,
unsigned mode, struct archiver_context *c)
{
@@ -203,7 +203,7 @@ static void queue_directory(const unsigned char *sha1,
d->mode = mode;
c->bottom = d;
d->len = xsnprintf(d->path, len, "%.*s%s/", (int)base->len, base->buf, filename);
- oidread(&d->oid, sha1);
+ oidcpy(&d->oid, oid);
}
static int write_directory(struct archiver_context *c)
@@ -250,8 +250,7 @@ static int queue_or_write_archive_entry(const struct object_id *oid,
if (check_attr_export_ignore(check))
return 0;
- queue_directory(oid->hash, base, filename,
- mode, c);
+ queue_directory(oid, base, filename, mode, c);
return READ_TREE_RECURSIVE;
}
diff --git a/attr.c b/attr.c
index d029e681f2..79adaa50ea 100644
--- a/attr.c
+++ b/attr.c
@@ -14,6 +14,7 @@
#include "utf8.h"
#include "quote.h"
#include "thread-utils.h"
+#include "dir.h"
const char git_attr__true[] = "(builtin)true";
const char git_attr__false[] = "\0(builtin)false";
@@ -744,6 +745,20 @@ static struct attr_stack *read_attr_from_index(struct index_state *istate,
if (!istate)
return NULL;
+ /*
+ * The .gitattributes file only applies to files within its
+ * parent directory. In the case of cone-mode sparse-checkout,
+ * the .gitattributes file is sparse if and only if all paths
+ * within that directory are also sparse. Thus, don't load the
+ * .gitattributes file since it will not matter.
+ *
+ * In the case of a sparse index, it is critical that we don't go
+ * looking for a .gitattributes file, as doing so would cause the
+ * index to expand.
+ */
+ if (!path_in_cone_mode_sparse_checkout(path, istate))
+ return NULL;
+
buf = read_blob_data_from_index(istate, path, NULL);
if (!buf)
return NULL;
diff --git a/bisect.c b/bisect.c
index af2863d044..888949fba6 100644
--- a/bisect.c
+++ b/bisect.c
@@ -23,7 +23,6 @@ static struct oid_array skipped_revs;
static struct object_id *current_bad_oid;
static const char *argv_checkout[] = {"checkout", "-q", NULL, "--", NULL};
-static const char *argv_show_branch[] = {"show-branch", NULL, NULL};
static const char *term_bad;
static const char *term_good;
@@ -728,7 +727,9 @@ static int is_expected_rev(const struct object_id *oid)
static enum bisect_error bisect_checkout(const struct object_id *bisect_rev, int no_checkout)
{
char bisect_rev_hex[GIT_MAX_HEXSZ + 1];
- enum bisect_error res = BISECT_OK;
+ struct commit *commit;
+ struct pretty_print_context pp = {0};
+ struct strbuf commit_msg = STRBUF_INIT;
oid_to_hex_r(bisect_rev_hex, bisect_rev);
update_ref(NULL, "BISECT_EXPECTED_REV", bisect_rev, NULL, 0, UPDATE_REFS_DIE_ON_ERR);
@@ -738,24 +739,21 @@ static enum bisect_error bisect_checkout(const struct object_id *bisect_rev, int
update_ref(NULL, "BISECT_HEAD", bisect_rev, NULL, 0,
UPDATE_REFS_DIE_ON_ERR);
} else {
- res = run_command_v_opt(argv_checkout, RUN_GIT_CMD);
- if (res)
+ if (run_command_v_opt(argv_checkout, RUN_GIT_CMD))
/*
* Errors in `run_command()` itself, signaled by res < 0,
* and errors in the child process, signaled by res > 0
- * can both be treated as regular BISECT_FAILURE (-1).
+ * can both be treated as regular BISECT_FAILED (-1).
*/
- return -abs(res);
+ return BISECT_FAILED;
}
- argv_show_branch[1] = bisect_rev_hex;
- res = run_command_v_opt(argv_show_branch, RUN_GIT_CMD);
- /*
- * Errors in `run_command()` itself, signaled by res < 0,
- * and errors in the child process, signaled by res > 0
- * can both be treated as regular BISECT_FAILURE (-1).
- */
- return -abs(res);
+ commit = lookup_commit_reference(the_repository, bisect_rev);
+ format_commit_message(commit, "[%H] %s%n", &commit_msg, &pp);
+ fputs(commit_msg.buf, stdout);
+ strbuf_release(&commit_msg);
+
+ return BISECT_OK;
}
static struct commit *get_commit_reference(struct repository *r,
diff --git a/branch.c b/branch.c
index 7a88a4861e..07a46430b3 100644
--- a/branch.c
+++ b/branch.c
@@ -271,7 +271,7 @@ void create_branch(struct repository *r,
real_ref = NULL;
if (get_oid_mb(start_name, &oid)) {
if (explicit_tracking) {
- if (advice_set_upstream_failure) {
+ if (advice_enabled(ADVICE_SET_UPSTREAM_FAILURE)) {
error(_(upstream_missing), start_name);
advise(_(upstream_advice));
exit(1);
diff --git a/builtin.h b/builtin.h
index 16ecd5586f..8a58743ed6 100644
--- a/builtin.h
+++ b/builtin.h
@@ -225,7 +225,6 @@ int cmd_submodule__helper(int argc, const char **argv, const char *prefix);
int cmd_switch(int argc, const char **argv, const char *prefix);
int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
int cmd_tag(int argc, const char **argv, const char *prefix);
-int cmd_tar_tree(int argc, const char **argv, const char *prefix);
int cmd_unpack_file(int argc, const char **argv, const char *prefix);
int cmd_unpack_objects(int argc, const char **argv, const char *prefix);
int cmd_update_index(int argc, const char **argv, const char *prefix);
diff --git a/builtin/add.c b/builtin/add.c
index 09e684585d..ef6b619c45 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -30,6 +30,7 @@ static int patch_interactive, add_interactive, edit_interactive;
static int take_worktree_changes;
static int add_renormalize;
static int pathspec_file_nul;
+static int include_sparse;
static const char *pathspec_from_file;
static int legacy_stash_p; /* support for the scripted `git stash` */
@@ -46,7 +47,9 @@ static int chmod_pathspec(struct pathspec *pathspec, char flip, int show_only)
struct cache_entry *ce = active_cache[i];
int err;
- if (ce_skip_worktree(ce))
+ if (!include_sparse &&
+ (ce_skip_worktree(ce) ||
+ !path_in_sparse_checkout(ce->name, &the_index)))
continue;
if (pathspec && !ce_path_match(&the_index, ce, pathspec, NULL))
@@ -94,6 +97,10 @@ static void update_callback(struct diff_queue_struct *q,
for (i = 0; i < q->nr; i++) {
struct diff_filepair *p = q->queue[i];
const char *path = p->one->path;
+
+ if (!include_sparse && !path_in_sparse_checkout(path, &the_index))
+ continue;
+
switch (fix_unmerged_status(p, data)) {
default:
die(_("unexpected diff status %c"), p->status);
@@ -144,12 +151,12 @@ static int renormalize_tracked_files(const struct pathspec *pathspec, int flags)
{
int i, retval = 0;
- /* TODO: audit for interaction with sparse-index. */
- ensure_full_index(&the_index);
for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = active_cache[i];
- if (ce_skip_worktree(ce))
+ if (!include_sparse &&
+ (ce_skip_worktree(ce) ||
+ !path_in_sparse_checkout(ce->name, &the_index)))
continue;
if (ce_stage(ce))
continue; /* do not touch unmerged paths */
@@ -198,7 +205,10 @@ static int refresh(int verbose, const struct pathspec *pathspec)
_("Unstaged changes after refreshing the index:"));
for (i = 0; i < pathspec->nr; i++) {
if (!seen[i]) {
- if (matches_skip_worktree(pathspec, i, &skip_worktree_seen)) {
+ const char *path = pathspec->items[i].original;
+
+ if (matches_skip_worktree(pathspec, i, &skip_worktree_seen) ||
+ !path_in_sparse_checkout(path, &the_index)) {
string_list_append(&only_match_skip_worktree,
pathspec->items[i].original);
} else {
@@ -313,9 +323,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix)
rev.diffopt.output_format = DIFF_FORMAT_PATCH;
rev.diffopt.use_color = 0;
rev.diffopt.flags.ignore_dirty_submodules = 1;
- out = open(file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
- if (out < 0)
- die(_("Could not open '%s' for writing."), file);
+ out = xopen(file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
rev.diffopt.file = xfdopen(out, "w");
rev.diffopt.close_file = 1;
if (run_diff_files(&rev, 0))
@@ -378,6 +386,7 @@ static struct option builtin_add_options[] = {
OPT_BOOL( 0 , "refresh", &refresh_only, N_("don't add, only refresh the index")),
OPT_BOOL( 0 , "ignore-errors", &ignore_add_errors, N_("just skip files which cannot be added because of errors")),
OPT_BOOL( 0 , "ignore-missing", &ignore_missing, N_("check if - even missing - files are ignored in dry run")),
+ OPT_BOOL(0, "sparse", &include_sparse, N_("allow updating entries outside of the sparse-checkout cone")),
OPT_STRING(0, "chmod", &chmod_arg, "(+|-)x",
N_("override the executable bit of the listed files")),
OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo,
@@ -419,6 +428,7 @@ static const char embedded_advice[] = N_(
static void check_embedded_repo(const char *path)
{
struct strbuf name = STRBUF_INIT;
+ static int adviced_on_embedded_repo = 0;
if (!warn_on_embedded_repo)
return;
@@ -430,10 +440,10 @@ static void check_embedded_repo(const char *path)
strbuf_strip_suffix(&name, "/");
warning(_("adding embedded git repository: %s"), name.buf);
- if (advice_add_embedded_repo) {
+ if (!adviced_on_embedded_repo &&
+ advice_enabled(ADVICE_ADD_EMBEDDED_REPO)) {
advise(embedded_advice, name.buf, name.buf);
- /* there may be multiple entries; advise only once */
- advice_add_embedded_repo = 0;
+ adviced_on_embedded_repo = 1;
}
strbuf_release(&name);
@@ -442,12 +452,13 @@ static void check_embedded_repo(const char *path)
static int add_files(struct dir_struct *dir, int flags)
{
int i, exit_status = 0;
+ struct string_list matched_sparse_paths = STRING_LIST_INIT_NODUP;
if (dir->ignored_nr) {
fprintf(stderr, _(ignore_error));
for (i = 0; i < dir->ignored_nr; i++)
fprintf(stderr, "%s\n", dir->ignored[i]->name);
- if (advice_add_ignored_file)
+ if (advice_enabled(ADVICE_ADD_IGNORED_FILE))
advise(_("Use -f if you really want to add them.\n"
"Turn this message off by running\n"
"\"git config advice.addIgnoredFile false\""));
@@ -455,6 +466,12 @@ static int add_files(struct dir_struct *dir, int flags)
}
for (i = 0; i < dir->nr; i++) {
+ if (!include_sparse &&
+ !path_in_sparse_checkout(dir->entries[i]->name, &the_index)) {
+ string_list_append(&matched_sparse_paths,
+ dir->entries[i]->name);
+ continue;
+ }
if (add_file_to_index(&the_index, dir->entries[i]->name, flags)) {
if (!ignore_add_errors)
die(_("adding files failed"));
@@ -463,6 +480,14 @@ static int add_files(struct dir_struct *dir, int flags)
check_embedded_repo(dir->entries[i]->name);
}
}
+
+ if (matched_sparse_paths.nr) {
+ advise_on_updating_sparse_paths(&matched_sparse_paths);
+ exit_status = 1;
+ }
+
+ string_list_clear(&matched_sparse_paths, 0);
+
return exit_status;
}
@@ -528,6 +553,9 @@ int cmd_add(int argc, const char **argv, const char *prefix)
add_new_files = !take_worktree_changes && !refresh_only && !add_renormalize;
require_pathspec = !(take_worktree_changes || (0 < addremove_explicit));
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+
hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
/*
@@ -553,7 +581,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
if (require_pathspec && pathspec.nr == 0) {
fprintf(stderr, _("Nothing specified, nothing added.\n"));
- if (advice_add_empty_pathspec)
+ if (advice_enabled(ADVICE_ADD_EMPTY_PATHSPEC))
advise( _("Maybe you wanted to say 'git add .'?\n"
"Turn this message off by running\n"
"\"git config advice.addEmptyPathspec false\""));
@@ -624,7 +652,8 @@ int cmd_add(int argc, const char **argv, const char *prefix)
if (seen[i])
continue;
- if (matches_skip_worktree(&pathspec, i, &skip_worktree_seen)) {
+ if (!include_sparse &&
+ matches_skip_worktree(&pathspec, i, &skip_worktree_seen)) {
string_list_append(&only_match_skip_worktree,
pathspec.items[i].original);
continue;
diff --git a/builtin/am.c b/builtin/am.c
index 0c2ad96b70..8677ea2348 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -11,6 +11,7 @@
#include "parse-options.h"
#include "dir.h"
#include "run-command.h"
+#include "hook.h"
#include "quote.h"
#include "tempfile.h"
#include "lockfile.h"
@@ -1820,7 +1821,7 @@ static void am_run(struct am_state *state, int resume)
printf_ln(_("Patch failed at %s %.*s"), msgnum(state),
linelen(state->msg), state->msg);
- if (advice_amworkdir)
+ if (advice_enabled(ADVICE_AM_WORK_DIR))
advise(_("Use 'git am --show-current-patch=diff' to see the failed patch"));
die_user_resolve(state);
@@ -1848,7 +1849,6 @@ next:
*/
if (!state->rebasing) {
am_destroy(state);
- close_object_store(the_repository->objects);
run_auto_maintenance(state->quiet);
}
}
@@ -1918,7 +1918,8 @@ static int fast_forward_to(struct tree *head, struct tree *remote, int reset)
opts.dst_index = &the_index;
opts.update = 1;
opts.merge = 1;
- opts.reset = reset;
+ opts.reset = reset ? UNPACK_RESET_PROTECT_UNTRACKED : 0;
+ opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
opts.fn = twoway_merge;
init_tree_desc(&t[0], head->buffer, head->size);
init_tree_desc(&t[1], remote->buffer, remote->size);
@@ -2106,7 +2107,8 @@ static void am_abort(struct am_state *state)
if (!has_orig_head)
oidcpy(&orig_head, the_hash_algo->empty_tree);
- clean_index(&curr_head, &orig_head);
+ if (clean_index(&curr_head, &orig_head))
+ die(_("failed to clean index"));
if (has_orig_head)
update_ref("am --abort", "HEAD", &orig_head,
diff --git a/builtin/archive.c b/builtin/archive.c
index 45d11669aa..7176b041b6 100644
--- a/builtin/archive.c
+++ b/builtin/archive.c
@@ -12,9 +12,7 @@
static void create_output_file(const char *output_file)
{
- int output_fd = open(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
- if (output_fd < 0)
- die_errno(_("could not create archive file '%s'"), output_file);
+ int output_fd = xopen(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
if (output_fd != 1) {
if (dup2(output_fd, 1) < 0)
die_errno(_("could not redirect output"));
diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c
index f184eaeac6..28a2e6a575 100644
--- a/builtin/bisect--helper.c
+++ b/builtin/bisect--helper.c
@@ -18,10 +18,10 @@ static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG")
static GIT_PATH_FUNC(git_path_head_name, "head-name")
static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES")
static GIT_PATH_FUNC(git_path_bisect_first_parent, "BISECT_FIRST_PARENT")
+static GIT_PATH_FUNC(git_path_bisect_run, "BISECT_RUN")
static const char * const git_bisect_helper_usage[] = {
N_("git bisect--helper --bisect-reset [<commit>]"),
- N_("git bisect--helper --bisect-next-check <good_term> <bad_term> [<term>]"),
N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"),
N_("git bisect--helper --bisect-start [--term-{new,bad}=<term> --term-{old,good}=<term>]"
" [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<paths>...]"),
@@ -30,6 +30,8 @@ static const char * const git_bisect_helper_usage[] = {
N_("git bisect--helper --bisect-state (good|old) [<rev>...]"),
N_("git bisect--helper --bisect-replay <filename>"),
N_("git bisect--helper --bisect-skip [(<rev>|<range>)...]"),
+ N_("git bisect--helper --bisect-visualize"),
+ N_("git bisect--helper --bisect-run <cmd>..."),
NULL
};
@@ -143,6 +145,19 @@ static int append_to_file(const char *path, const char *format, ...)
return res;
}
+static int print_file_to_stdout(const char *path)
+{
+ int fd = open(path, O_RDONLY);
+ int ret = 0;
+
+ if (fd < 0)
+ return error_errno(_("cannot open file '%s' for reading"), path);
+ if (copy_fd(fd, 1) < 0)
+ ret = error_errno(_("failed to read '%s'"), path);
+ close(fd);
+ return ret;
+}
+
static int check_term_format(const char *term, const char *orig_term)
{
int res;
@@ -1036,6 +1051,125 @@ static enum bisect_error bisect_skip(struct bisect_terms *terms, const char **ar
return res;
}
+static int bisect_visualize(struct bisect_terms *terms, const char **argv, int argc)
+{
+ struct strvec args = STRVEC_INIT;
+ int flags = RUN_COMMAND_NO_STDIN, res = 0;
+ struct strbuf sb = STRBUF_INIT;
+
+ if (bisect_next_check(terms, NULL) != 0)
+ return BISECT_FAILED;
+
+ if (!argc) {
+ if ((getenv("DISPLAY") || getenv("SESSIONNAME") || getenv("MSYSTEM") ||
+ getenv("SECURITYSESSIONID")) && exists_in_PATH("gitk")) {
+ strvec_push(&args, "gitk");
+ } else {
+ strvec_push(&args, "log");
+ flags |= RUN_GIT_CMD;
+ }
+ } else {
+ if (argv[0][0] == '-') {
+ strvec_push(&args, "log");
+ flags |= RUN_GIT_CMD;
+ } else if (strcmp(argv[0], "tig") && !starts_with(argv[0], "git"))
+ flags |= RUN_GIT_CMD;
+
+ strvec_pushv(&args, argv);
+ }
+
+ strvec_pushl(&args, "--bisect", "--", NULL);
+
+ strbuf_read_file(&sb, git_path_bisect_names(), 0);
+ sq_dequote_to_strvec(sb.buf, &args);
+ strbuf_release(&sb);
+
+ res = run_command_v_opt(args.v, flags);
+ strvec_clear(&args);
+ return res;
+}
+
+static int bisect_run(struct bisect_terms *terms, const char **argv, int argc)
+{
+ int res = BISECT_OK;
+ struct strbuf command = STRBUF_INIT;
+ struct strvec args = STRVEC_INIT;
+ struct strvec run_args = STRVEC_INIT;
+ const char *new_state;
+ int temporary_stdout_fd, saved_stdout;
+
+ if (bisect_next_check(terms, NULL))
+ return BISECT_FAILED;
+
+ if (argc)
+ sq_quote_argv(&command, argv);
+ else {
+ error(_("bisect run failed: no command provided."));
+ return BISECT_FAILED;
+ }
+
+ strvec_push(&run_args, command.buf);
+
+ while (1) {
+ strvec_clear(&args);
+
+ printf(_("running %s\n"), command.buf);
+ res = run_command_v_opt(run_args.v, RUN_USING_SHELL);
+
+ if (res < 0 || 128 <= res) {
+ error(_("bisect run failed: exit code %d from"
+ " '%s' is < 0 or >= 128"), res, command.buf);
+ strbuf_release(&command);
+ return res;
+ }
+
+ if (res == 125)
+ new_state = "skip";
+ else if (!res)
+ new_state = terms->term_good;
+ else
+ new_state = terms->term_bad;
+
+ temporary_stdout_fd = open(git_path_bisect_run(), O_CREAT | O_WRONLY | O_TRUNC, 0666);
+
+ if (temporary_stdout_fd < 0)
+ return error_errno(_("cannot open file '%s' for writing"), git_path_bisect_run());
+
+ fflush(stdout);
+ saved_stdout = dup(1);
+ dup2(temporary_stdout_fd, 1);
+
+ res = bisect_state(terms, &new_state, 1);
+
+ fflush(stdout);
+ dup2(saved_stdout, 1);
+ close(saved_stdout);
+ close(temporary_stdout_fd);
+
+ print_file_to_stdout(git_path_bisect_run());
+
+ if (res == BISECT_ONLY_SKIPPED_LEFT)
+ error(_("bisect run cannot continue any more"));
+ else if (res == BISECT_INTERNAL_SUCCESS_MERGE_BASE) {
+ printf(_("bisect run success"));
+ res = BISECT_OK;
+ } else if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {
+ printf(_("bisect found first bad commit"));
+ res = BISECT_OK;
+ } else if (res) {
+ error(_("bisect run failed: 'git bisect--helper --bisect-state"
+ " %s' exited with error code %d"), args.v[0], res);
+ } else {
+ continue;
+ }
+
+ strbuf_release(&command);
+ strvec_clear(&args);
+ strvec_clear(&run_args);
+ return res;
+ }
+}
+
int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
{
enum {
@@ -1048,7 +1182,9 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
BISECT_STATE,
BISECT_LOG,
BISECT_REPLAY,
- BISECT_SKIP
+ BISECT_SKIP,
+ BISECT_VISUALIZE,
+ BISECT_RUN,
} cmdmode = 0;
int res = 0, nolog = 0;
struct option options[] = {
@@ -1070,6 +1206,10 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
N_("replay the bisection process from the given file"), BISECT_REPLAY),
OPT_CMDMODE(0, "bisect-skip", &cmdmode,
N_("skip some commits for checkout"), BISECT_SKIP),
+ OPT_CMDMODE(0, "bisect-visualize", &cmdmode,
+ N_("visualize the bisection"), BISECT_VISUALIZE),
+ OPT_CMDMODE(0, "bisect-run", &cmdmode,
+ N_("use <cmd>... to automatically bisect."), BISECT_RUN),
OPT_BOOL(0, "no-log", &nolog,
N_("no log for BISECT_WRITE")),
OPT_END()
@@ -1089,12 +1229,6 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
return error(_("--bisect-reset requires either no argument or a commit"));
res = bisect_reset(argc ? argv[0] : NULL);
break;
- case BISECT_NEXT_CHECK:
- if (argc != 2 && argc != 3)
- return error(_("--bisect-next-check requires 2 or 3 arguments"));
- set_terms(&terms, argv[1], argv[0]);
- res = bisect_next_check(&terms, argc == 3 ? argv[2] : NULL);
- break;
case BISECT_TERMS:
if (argc > 1)
return error(_("--bisect-terms requires 0 or 1 argument"));
@@ -1131,6 +1265,16 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
get_terms(&terms);
res = bisect_skip(&terms, argv, argc);
break;
+ case BISECT_VISUALIZE:
+ get_terms(&terms);
+ res = bisect_visualize(&terms, argv, argc);
+ break;
+ case BISECT_RUN:
+ if (!argc)
+ return error(_("bisect run failed: no command provided."));
+ get_terms(&terms);
+ res = bisect_run(&terms, argv, argc);
+ break;
default:
BUG("unknown subcommand %d", cmdmode);
}
diff --git a/builtin/branch.c b/builtin/branch.c
index b23b1d1752..0b7ed82654 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -168,7 +168,7 @@ static int check_branch_commit(const char *branchname, const char *refname,
int kinds, int force)
{
struct commit *rev = lookup_commit_reference(the_repository, oid);
- if (!rev) {
+ if (!force && !rev) {
error(_("Couldn't look up commit object for '%s'"), refname);
return -1;
}
@@ -427,7 +427,7 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin
memset(&array, 0, sizeof(array));
- filter_refs(&array, filter, filter->kind | FILTER_REFS_INCLUDE_BROKEN);
+ filter_refs(&array, filter, filter->kind);
if (filter->verbose)
maxwidth = calc_maxwidth(&array, strlen(remote_prefix));
diff --git a/builtin/bugreport.c b/builtin/bugreport.c
index 9915a5841d..9de32bc96e 100644
--- a/builtin/bugreport.c
+++ b/builtin/bugreport.c
@@ -3,7 +3,8 @@
#include "strbuf.h"
#include "help.h"
#include "compat/compiler.h"
-#include "run-command.h"
+#include "hook.h"
+#include "hook-list.h"
static void get_system_info(struct strbuf *sys_info)
@@ -41,39 +42,7 @@ static void get_system_info(struct strbuf *sys_info)
static void get_populated_hooks(struct strbuf *hook_info, int nongit)
{
- /*
- * NEEDSWORK: Doesn't look like there is a list of all possible hooks;
- * so below is a transcription of `git help hooks`. Later, this should
- * be replaced with some programmatically generated list (generated from
- * doc or else taken from some library which tells us about all the
- * hooks)
- */
- static const char *hook[] = {
- "applypatch-msg",
- "pre-applypatch",
- "post-applypatch",
- "pre-commit",
- "pre-merge-commit",
- "prepare-commit-msg",
- "commit-msg",
- "post-commit",
- "pre-rebase",
- "post-checkout",
- "post-merge",
- "pre-push",
- "pre-receive",
- "update",
- "post-receive",
- "post-update",
- "push-to-checkout",
- "pre-auto-gc",
- "post-rewrite",
- "sendemail-validate",
- "fsmonitor-watchman",
- "p4-pre-submit",
- "post-index-change",
- };
- int i;
+ const char **p;
if (nongit) {
strbuf_addstr(hook_info,
@@ -81,9 +50,12 @@ static void get_populated_hooks(struct strbuf *hook_info, int nongit)
return;
}
- for (i = 0; i < ARRAY_SIZE(hook); i++)
- if (find_hook(hook[i]))
- strbuf_addf(hook_info, "%s\n", hook[i]);
+ for (p = hook_name_list; *p; p++) {
+ const char *hook = *p;
+
+ if (hook_exists(hook))
+ strbuf_addf(hook_info, "%s\n", hook);
+ }
}
static const char * const bugreport_usage[] = {
@@ -171,10 +143,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix)
get_populated_hooks(&buffer, !startup_info->have_repository);
/* fopen doesn't offer us an O_EXCL alternative, except with glibc. */
- report = open(report_path.buf, O_CREAT | O_EXCL | O_WRONLY, 0666);
-
- if (report < 0)
- die(_("couldn't create a new file at '%s'"), report_path.buf);
+ report = xopen(report_path.buf, O_CREAT | O_EXCL | O_WRONLY, 0666);
if (write_in_full(report, buffer.buf, buffer.len) < 0)
die_errno(_("unable to write to %s"), report_path.buf);
diff --git a/builtin/bundle.c b/builtin/bundle.c
index 053a51bea1..5a85d7cd0f 100644
--- a/builtin/bundle.c
+++ b/builtin/bundle.c
@@ -39,8 +39,6 @@ static const char * const builtin_bundle_unbundle_usage[] = {
NULL
};
-static int verbose;
-
static int parse_options_cmd_bundle(int argc,
const char **argv,
const char* prefix,
@@ -162,10 +160,15 @@ static int cmd_bundle_unbundle(int argc, const char **argv, const char *prefix)
struct bundle_header header = BUNDLE_HEADER_INIT;
int bundle_fd = -1;
int ret;
+ int progress = isatty(2);
+
struct option options[] = {
+ OPT_BOOL(0, "progress", &progress,
+ N_("show progress meter")),
OPT_END()
};
char *bundle_file;
+ struct strvec extra_index_pack_args = STRVEC_INIT;
argc = parse_options_cmd_bundle(argc, argv, prefix,
builtin_bundle_unbundle_usage, options, &bundle_file);
@@ -177,7 +180,11 @@ static int cmd_bundle_unbundle(int argc, const char **argv, const char *prefix)
}
if (!startup_info->have_repository)
die(_("Need a repository to unbundle."));
- ret = !!unbundle(the_repository, &header, bundle_fd, 0) ||
+ if (progress)
+ strvec_pushl(&extra_index_pack_args, "-v", "--progress-title",
+ _("Unbundling objects"), NULL);
+ ret = !!unbundle(the_repository, &header, bundle_fd,
+ &extra_index_pack_args) ||
list_bundle_refs(&header, argc, argv);
bundle_header_release(&header);
cleanup:
@@ -188,7 +195,6 @@ cleanup:
int cmd_bundle(int argc, const char **argv, const char *prefix)
{
struct option options[] = {
- OPT__VERBOSE(&verbose, N_("be verbose; must be placed before a subcommand")),
OPT_END()
};
int result;
diff --git a/builtin/checkout.c b/builtin/checkout.c
index b5d477919a..cbf73b8c9f 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -404,7 +404,7 @@ static int checkout_worktree(const struct checkout_opts *opts,
mem_pool_discard(&ce_mem_pool, should_validate_cache_entries());
remove_marked_cache_entries(&the_index, 1);
remove_scheduled_dirs();
- errs |= finish_delayed_checkout(&state, &nr_checkouts);
+ errs |= finish_delayed_checkout(&state, &nr_checkouts, opts->show_progress);
if (opts->count_checkout_paths) {
if (nr_unmerged)
@@ -646,7 +646,9 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,
opts.head_idx = -1;
opts.update = worktree;
opts.skip_unmerged = !worktree;
- opts.reset = 1;
+ opts.reset = o->force ? UNPACK_RESET_OVERWRITE_UNTRACKED :
+ UNPACK_RESET_PROTECT_UNTRACKED;
+ opts.preserve_ignored = (!o->force && !o->overwrite_ignore);
opts.merge = 1;
opts.fn = oneway_merge;
opts.verbose_update = o->show_progress;
@@ -746,11 +748,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
new_branch_info->commit ?
&new_branch_info->commit->object.oid :
&new_branch_info->oid, NULL);
- if (opts->overwrite_ignore) {
- topts.dir = xcalloc(1, sizeof(*topts.dir));
- topts.dir->flags |= DIR_SHOW_IGNORED;
- setup_standard_excludes(topts.dir);
- }
+ topts.preserve_ignored = !opts->overwrite_ignore;
tree = parse_tree_indirect(old_branch_info->commit ?
&old_branch_info->commit->object.oid :
the_hash_algo->empty_tree);
@@ -918,7 +916,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
REF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR);
if (!opts->quiet) {
if (old_branch_info->path &&
- advice_detached_head && !opts->force_detach)
+ advice_enabled(ADVICE_DETACHED_HEAD) && !opts->force_detach)
detach_advice(new_branch_info->name);
describe_detached_head(_("HEAD is now at"), new_branch_info->commit);
}
@@ -1011,7 +1009,7 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs)
sb.buf);
strbuf_release(&sb);
- if (advice_detached_head)
+ if (advice_enabled(ADVICE_DETACHED_HEAD))
fprintf(stderr,
Q_(
/* The singular version */
@@ -1182,7 +1180,7 @@ static const char *parse_remote_branch(const char *arg,
}
if (!remote && num_matches > 1) {
- if (advice_checkout_ambiguous_remote_branch_name) {
+ if (advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME)) {
advise(_("If you meant to check out a remote tracking branch on, e.g. 'origin',\n"
"you can do so by fully qualifying the name with the --track option:\n"
"\n"
diff --git a/builtin/clone.c b/builtin/clone.c
index 66fe66679c..559acf9e03 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -217,120 +217,6 @@ static char *get_repo_path(const char *repo, int *is_bundle)
return canon;
}
-static char *guess_dir_name(const char *repo, int is_bundle, int is_bare)
-{
- const char *end = repo + strlen(repo), *start, *ptr;
- size_t len;
- char *dir;
-
- /*
- * Skip scheme.
- */
- start = strstr(repo, "://");
- if (start == NULL)
- start = repo;
- else
- start += 3;
-
- /*
- * Skip authentication data. The stripping does happen
- * greedily, such that we strip up to the last '@' inside
- * the host part.
- */
- for (ptr = start; ptr < end && !is_dir_sep(*ptr); ptr++) {
- if (*ptr == '@')
- start = ptr + 1;
- }
-
- /*
- * Strip trailing spaces, slashes and /.git
- */
- while (start < end && (is_dir_sep(end[-1]) || isspace(end[-1])))
- end--;
- if (end - start > 5 && is_dir_sep(end[-5]) &&
- !strncmp(end - 4, ".git", 4)) {
- end -= 5;
- while (start < end && is_dir_sep(end[-1]))
- end--;
- }
-
- /*
- * Strip trailing port number if we've got only a
- * hostname (that is, there is no dir separator but a
- * colon). This check is required such that we do not
- * strip URI's like '/foo/bar:2222.git', which should
- * result in a dir '2222' being guessed due to backwards
- * compatibility.
- */
- if (memchr(start, '/', end - start) == NULL
- && memchr(start, ':', end - start) != NULL) {
- ptr = end;
- while (start < ptr && isdigit(ptr[-1]) && ptr[-1] != ':')
- ptr--;
- if (start < ptr && ptr[-1] == ':')
- end = ptr - 1;
- }
-
- /*
- * Find last component. To remain backwards compatible we
- * also regard colons as path separators, such that
- * cloning a repository 'foo:bar.git' would result in a
- * directory 'bar' being guessed.
- */
- ptr = end;
- while (start < ptr && !is_dir_sep(ptr[-1]) && ptr[-1] != ':')
- ptr--;
- start = ptr;
-
- /*
- * Strip .{bundle,git}.
- */
- len = end - start;
- strip_suffix_mem(start, &len, is_bundle ? ".bundle" : ".git");
-
- if (!len || (len == 1 && *start == '/'))
- die(_("No directory name could be guessed.\n"
- "Please specify a directory on the command line"));
-
- if (is_bare)
- dir = xstrfmt("%.*s.git", (int)len, start);
- else
- dir = xstrndup(start, len);
- /*
- * Replace sequences of 'control' characters and whitespace
- * with one ascii space, remove leading and trailing spaces.
- */
- if (*dir) {
- char *out = dir;
- int prev_space = 1 /* strip leading whitespace */;
- for (end = dir; *end; ++end) {
- char ch = *end;
- if ((unsigned char)ch < '\x20')
- ch = '\x20';
- if (isspace(ch)) {
- if (prev_space)
- continue;
- prev_space = 1;
- } else
- prev_space = 0;
- *out++ = ch;
- }
- *out = '\0';
- if (out > dir && prev_space)
- out[-1] = '\0';
- }
- return dir;
-}
-
-static void strip_trailing_slashes(char *dir)
-{
- char *end = dir + strlen(dir);
-
- while (dir < end - 1 && is_dir_sep(end[-1]))
- end--;
- *end = '\0';
-}
-
static int add_one_reference(struct string_list_item *item, void *cb_data)
{
struct strbuf err = STRBUF_INIT;
@@ -657,7 +543,7 @@ static void write_followtags(const struct ref *refs, const char *msg)
}
}
-static int iterate_ref_map(void *cb_data, struct object_id *oid)
+static const struct object_id *iterate_ref_map(void *cb_data)
{
struct ref **rm = cb_data;
struct ref *ref = *rm;
@@ -668,13 +554,11 @@ static int iterate_ref_map(void *cb_data, struct object_id *oid)
*/
while (ref && !ref->peer_ref)
ref = ref->next;
- /* Returning -1 notes "end of list" to the caller. */
if (!ref)
- return -1;
+ return NULL;
- oidcpy(oid, &ref->old_oid);
*rm = ref->next;
- return 0;
+ return &ref->old_oid;
}
static void update_remote_refs(const struct ref *refs,
@@ -786,7 +670,7 @@ static int checkout(int submodule_progress)
return 0;
}
if (!strcmp(head, "HEAD")) {
- if (advice_detached_head)
+ if (advice_enabled(ADVICE_DETACHED_HEAD))
detach_advice(oid_to_hex(&oid));
FREE_AND_NULL(head);
} else {
@@ -803,6 +687,7 @@ static int checkout(int submodule_progress)
opts.update = 1;
opts.merge = 1;
opts.clone = 1;
+ opts.preserve_ignored = 0;
opts.fn = oneway_merge;
opts.verbose_update = (option_verbosity >= 0);
opts.src_index = &the_index;
@@ -1041,8 +926,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (argc == 2)
dir = xstrdup(argv[1]);
else
- dir = guess_dir_name(repo_name, is_bundle, option_bare);
- strip_trailing_slashes(dir);
+ dir = git_url_basename(repo_name, is_bundle, option_bare);
+ strip_dir_trailing_slashes(dir);
dest_exists = path_exists(dir);
if (dest_exists && !is_empty_dir(dir))
@@ -1114,6 +999,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (option_recurse_submodules.nr > 0) {
struct string_list_item *item;
struct strbuf sb = STRBUF_INIT;
+ int val;
/* remove duplicates */
string_list_sort(&option_recurse_submodules);
@@ -1130,6 +1016,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
strbuf_detach(&sb, NULL));
}
+ if (!git_config_get_bool("submodule.stickyRecursiveClone", &val) &&
+ val)
+ string_list_append(&option_config, "submodule.recurse=true");
+
if (option_required_reference.nr &&
option_optional_reference.nr)
die(_("clone --recursive is not compatible with "
@@ -1340,6 +1230,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
our_head_points_at = remote_head_points_at;
}
else {
+ const char *branch;
+ char *ref;
+
if (option_branch)
die(_("Remote branch %s not found in upstream %s"),
option_branch, remote_name);
@@ -1350,24 +1243,22 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
remote_head_points_at = NULL;
remote_head = NULL;
option_no_checkout = 1;
- if (!option_bare) {
- const char *branch;
- char *ref;
-
- if (transport_ls_refs_options.unborn_head_target &&
- skip_prefix(transport_ls_refs_options.unborn_head_target,
- "refs/heads/", &branch)) {
- ref = transport_ls_refs_options.unborn_head_target;
- transport_ls_refs_options.unborn_head_target = NULL;
- create_symref("HEAD", ref, reflog_msg.buf);
- } else {
- branch = git_default_branch_name(0);
- ref = xstrfmt("refs/heads/%s", branch);
- }
- install_branch_config(0, branch, remote_name, ref);
- free(ref);
+ if (transport_ls_refs_options.unborn_head_target &&
+ skip_prefix(transport_ls_refs_options.unborn_head_target,
+ "refs/heads/", &branch)) {
+ ref = transport_ls_refs_options.unborn_head_target;
+ transport_ls_refs_options.unborn_head_target = NULL;
+ create_symref("HEAD", ref, reflog_msg.buf);
+ } else {
+ branch = git_default_branch_name(0);
+ ref = xstrfmt("refs/heads/%s", branch);
}
+
+ if (!option_bare)
+ install_branch_config(0, branch, remote_name, ref);
+
+ free(ref);
}
write_refspec_config(src_ref_prefix, our_head_points_at,
diff --git a/builtin/column.c b/builtin/column.c
index 40d4b3bee2..158fdf53d9 100644
--- a/builtin/column.c
+++ b/builtin/column.c
@@ -29,7 +29,7 @@ int cmd_column(int argc, const char **argv, const char *prefix)
OPT_INTEGER(0, "raw-mode", &colopts, N_("layout to use")),
OPT_INTEGER(0, "width", &copts.width, N_("maximum width")),
OPT_STRING(0, "indent", &copts.indent, N_("string"), N_("padding space on left border")),
- OPT_INTEGER(0, "nl", &copts.nl, N_("padding space on right border")),
+ OPT_STRING(0, "nl", &copts.nl, N_("string"), N_("padding space on right border")),
OPT_INTEGER(0, "padding", &copts.padding, N_("padding space between columns")),
OPT_END()
};
diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c
index 003eaaac5c..3c3de3a156 100644
--- a/builtin/commit-graph.c
+++ b/builtin/commit-graph.c
@@ -9,26 +9,29 @@
#include "progress.h"
#include "tag.h"
-static char const * const builtin_commit_graph_usage[] = {
- N_("git commit-graph verify [--object-dir <objdir>] [--shallow] [--[no-]progress]"),
- N_("git commit-graph write [--object-dir <objdir>] [--append] "
- "[--split[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] "
- "[--changed-paths] [--[no-]max-new-filters <n>] [--[no-]progress] "
- "<split options>"),
+#define BUILTIN_COMMIT_GRAPH_VERIFY_USAGE \
+ N_("git commit-graph verify [--object-dir <objdir>] [--shallow] [--[no-]progress]")
+
+#define BUILTIN_COMMIT_GRAPH_WRITE_USAGE \
+ N_("git commit-graph write [--object-dir <objdir>] [--append] " \
+ "[--split[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] " \
+ "[--changed-paths] [--[no-]max-new-filters <n>] [--[no-]progress] " \
+ "<split options>")
+
+static const char * builtin_commit_graph_verify_usage[] = {
+ BUILTIN_COMMIT_GRAPH_VERIFY_USAGE,
NULL
};
-static const char * const builtin_commit_graph_verify_usage[] = {
- N_("git commit-graph verify [--object-dir <objdir>] [--shallow] [--[no-]progress]"),
+static const char * builtin_commit_graph_write_usage[] = {
+ BUILTIN_COMMIT_GRAPH_WRITE_USAGE,
NULL
};
-static const char * const builtin_commit_graph_write_usage[] = {
- N_("git commit-graph write [--object-dir <objdir>] [--append] "
- "[--split[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] "
- "[--changed-paths] [--[no-]max-new-filters <n>] [--[no-]progress] "
- "<split options>"),
- NULL
+static char const * const builtin_commit_graph_usage[] = {
+ BUILTIN_COMMIT_GRAPH_VERIFY_USAGE,
+ BUILTIN_COMMIT_GRAPH_WRITE_USAGE,
+ NULL,
};
static struct opts_commit_graph {
@@ -43,6 +46,18 @@ static struct opts_commit_graph {
int enable_changed_paths;
} opts;
+static struct option common_opts[] = {
+ OPT_STRING(0, "object-dir", &opts.obj_dir,
+ N_("dir"),
+ N_("the object directory to store the graph")),
+ OPT_END()
+};
+
+static struct option *add_common_options(struct option *to)
+{
+ return parse_options_concat(common_opts, to);
+}
+
static int graph_verify(int argc, const char **argv)
{
struct commit_graph *graph = NULL;
@@ -54,21 +69,22 @@ static int graph_verify(int argc, const char **argv)
int flags = 0;
static struct option builtin_commit_graph_verify_options[] = {
- OPT_STRING(0, "object-dir", &opts.obj_dir,
- N_("dir"),
- N_("the object directory to store the graph")),
OPT_BOOL(0, "shallow", &opts.shallow,
N_("if the commit-graph is split, only verify the tip file")),
- OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")),
+ OPT_BOOL(0, "progress", &opts.progress,
+ N_("force progress reporting")),
OPT_END(),
};
+ struct option *options = add_common_options(builtin_commit_graph_verify_options);
trace2_cmd_mode("verify");
opts.progress = isatty(2);
argc = parse_options(argc, argv, NULL,
- builtin_commit_graph_verify_options,
+ options,
builtin_commit_graph_verify_usage, 0);
+ if (argc)
+ usage_with_options(builtin_commit_graph_verify_usage, options);
if (!opts.obj_dir)
opts.obj_dir = get_object_directory();
@@ -84,6 +100,7 @@ static int graph_verify(int argc, const char **argv)
die_errno(_("Could not open commit-graph '%s'"), graph_name);
FREE_AND_NULL(graph_name);
+ FREE_AND_NULL(options);
if (open_ok)
graph = load_commit_graph_one_fd_st(the_repository, fd, &st, odb);
@@ -184,9 +201,6 @@ static int graph_write(int argc, const char **argv)
struct progress *progress = NULL;
static struct option builtin_commit_graph_write_options[] = {
- OPT_STRING(0, "object-dir", &opts.obj_dir,
- N_("dir"),
- N_("the object directory to store the graph")),
OPT_BOOL(0, "reachable", &opts.reachable,
N_("start walk at all refs")),
OPT_BOOL(0, "stdin-packs", &opts.stdin_packs,
@@ -197,7 +211,6 @@ static int graph_write(int argc, const char **argv)
N_("include all commits already in the commit-graph file")),
OPT_BOOL(0, "changed-paths", &opts.enable_changed_paths,
N_("enable computation for changed paths")),
- OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")),
OPT_CALLBACK_F(0, "split", &write_opts.split_flags, NULL,
N_("allow writing an incremental commit-graph file"),
PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
@@ -211,8 +224,11 @@ static int graph_write(int argc, const char **argv)
OPT_CALLBACK_F(0, "max-new-filters", &write_opts.max_new_filters,
NULL, N_("maximum number of changed-path Bloom filters to compute"),
0, write_option_max_new_filters),
+ OPT_BOOL(0, "progress", &opts.progress,
+ N_("force progress reporting")),
OPT_END(),
};
+ struct option *options = add_common_options(builtin_commit_graph_write_options);
opts.progress = isatty(2);
opts.enable_changed_paths = -1;
@@ -226,8 +242,10 @@ static int graph_write(int argc, const char **argv)
git_config(git_commit_graph_write_config, &opts);
argc = parse_options(argc, argv, NULL,
- builtin_commit_graph_write_options,
+ options,
builtin_commit_graph_write_usage, 0);
+ if (argc)
+ usage_with_options(builtin_commit_graph_write_usage, options);
if (opts.reachable + opts.stdin_packs + opts.stdin_commits > 1)
die(_("use at most one of --reachable, --stdin-commits, or --stdin-packs"));
@@ -282,6 +300,7 @@ static int graph_write(int argc, const char **argv)
result = 1;
cleanup:
+ FREE_AND_NULL(options);
string_list_clear(&pack_indexes, 0);
strbuf_release(&buf);
return result;
@@ -289,32 +308,25 @@ cleanup:
int cmd_commit_graph(int argc, const char **argv, const char *prefix)
{
- static struct option builtin_commit_graph_options[] = {
- OPT_STRING(0, "object-dir", &opts.obj_dir,
- N_("dir"),
- N_("the object directory to store the graph")),
- OPT_END(),
- };
-
- if (argc == 2 && !strcmp(argv[1], "-h"))
- usage_with_options(builtin_commit_graph_usage,
- builtin_commit_graph_options);
+ struct option *builtin_commit_graph_options = common_opts;
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix,
builtin_commit_graph_options,
builtin_commit_graph_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
+ if (!argc)
+ goto usage;
save_commit_buffer = 0;
- if (argc > 0) {
- if (!strcmp(argv[0], "verify"))
- return graph_verify(argc, argv);
- if (!strcmp(argv[0], "write"))
- return graph_write(argc, argv);
- }
+ if (!strcmp(argv[0], "verify"))
+ return graph_verify(argc, argv);
+ else if (argc && !strcmp(argv[0], "write"))
+ return graph_write(argc, argv);
+ error(_("unrecognized subcommand: %s"), argv[0]);
+usage:
usage_with_options(builtin_commit_graph_usage,
builtin_commit_graph_options);
}
diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c
index 1031b9a491..63ea322933 100644
--- a/builtin/commit-tree.c
+++ b/builtin/commit-tree.c
@@ -88,9 +88,7 @@ static int parse_file_arg_callback(const struct option *opt,
if (!strcmp(arg, "-"))
fd = 0;
else {
- fd = open(arg, O_RDONLY);
- if (fd < 0)
- die_errno(_("git commit-tree: failed to open '%s'"), arg);
+ fd = xopen(arg, O_RDONLY);
}
if (strbuf_read(buf, fd, 0) < 0)
die_errno(_("git commit-tree: failed to read '%s'"), arg);
diff --git a/builtin/commit.c b/builtin/commit.c
index 243c626307..883c16256c 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -19,6 +19,7 @@
#include "revision.h"
#include "wt-status.h"
#include "run-command.h"
+#include "hook.h"
#include "refs.h"
#include "log-tree.h"
#include "strbuf.h"
@@ -203,7 +204,7 @@ static void status_init_config(struct wt_status *s, config_fn_t fn)
init_diff_ui_defaults();
git_config(fn, s);
determine_whence(s);
- s->hints = advice_status_hints; /* must come after git_config() */
+ s->hints = advice_enabled(ADVICE_STATUS_HINTS); /* must come after git_config() */
}
static void rollback_index_files(void)
@@ -1033,7 +1034,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
*/
if (!committable && whence != FROM_MERGE && !allow_empty &&
!(amend && is_a_merge(current_head))) {
- s->hints = advice_status_hints;
+ s->hints = advice_enabled(ADVICE_STATUS_HINTS);
s->display_comment_prefix = old_display_comment_prefix;
run_status(stdout, index_file, prefix, 0, s);
if (amend)
@@ -1051,7 +1052,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
return 0;
}
- if (!no_verify && find_hook("pre-commit")) {
+ if (!no_verify && hook_exists("pre-commit")) {
/*
* Re-read the index as pre-commit hook could have updated it,
* and write it out as a tree. We must do this before we invoke
@@ -1253,8 +1254,6 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (logfile || have_option_m || use_message)
use_editor = 0;
- if (0 <= edit_flag)
- use_editor = edit_flag;
/* Sanity check options */
if (amend && !current_head)
@@ -1344,6 +1343,9 @@ static int parse_and_validate_options(int argc, const char *argv[],
}
}
+ if (0 <= edit_flag)
+ use_editor = edit_flag;
+
cleanup_mode = get_cleanup_mode(cleanup_arg, use_editor);
handle_untracked_files_arg(s);
diff --git a/builtin/credential-cache.c b/builtin/credential-cache.c
index 76a6ba3722..78c02ad531 100644
--- a/builtin/credential-cache.c
+++ b/builtin/credential-cache.c
@@ -11,6 +11,32 @@
#define FLAG_SPAWN 0x1
#define FLAG_RELAY 0x2
+#ifdef GIT_WINDOWS_NATIVE
+
+static int connection_closed(int error)
+{
+ return (error == EINVAL);
+}
+
+static int connection_fatally_broken(int error)
+{
+ return (error != ENOENT) && (error != ENETDOWN);
+}
+
+#else
+
+static int connection_closed(int error)
+{
+ return (error == ECONNRESET);
+}
+
+static int connection_fatally_broken(int error)
+{
+ return (error != ENOENT) && (error != ECONNREFUSED);
+}
+
+#endif
+
static int send_request(const char *socket, const struct strbuf *out)
{
int got_data = 0;
@@ -28,7 +54,7 @@ static int send_request(const char *socket, const struct strbuf *out)
int r;
r = read_in_full(fd, in, sizeof(in));
- if (r == 0 || (r < 0 && errno == ECONNRESET))
+ if (r == 0 || (r < 0 && connection_closed(errno)))
break;
if (r < 0)
die_errno("read error from cache daemon");
@@ -75,7 +101,7 @@ static void do_cache(const char *socket, const char *action, int timeout,
}
if (send_request(socket, &buf) < 0) {
- if (errno != ENOENT && errno != ECONNREFUSED)
+ if (connection_fatally_broken(errno))
die_errno("unable to connect to cache daemon");
if (flags & FLAG_SPAWN) {
spawn_daemon(socket);
@@ -90,7 +116,7 @@ static char *get_socket_path(void)
{
struct stat sb;
char *old_dir, *socket;
- old_dir = expand_user_path("~/.git-credential-cache", 0);
+ old_dir = interpolate_path("~/.git-credential-cache", 0);
if (old_dir && !stat(old_dir, &sb) && S_ISDIR(sb.st_mode))
socket = xstrfmt("%s/socket", old_dir);
else
diff --git a/builtin/credential-store.c b/builtin/credential-store.c
index ae3c1ba75f..62a4f3c265 100644
--- a/builtin/credential-store.c
+++ b/builtin/credential-store.c
@@ -173,7 +173,7 @@ int cmd_credential_store(int argc, const char **argv, const char *prefix)
if (file) {
string_list_append(&fns, file);
} else {
- if ((file = expand_user_path("~/.git-credentials", 0)))
+ if ((file = interpolate_path("~/.git-credentials", 0)))
string_list_append_nodup(&fns, file);
file = xdg_config_home("credentials");
if (file)
diff --git a/builtin/diff-index.c b/builtin/diff-index.c
index cf09559e42..5fd23ab5b6 100644
--- a/builtin/diff-index.c
+++ b/builtin/diff-index.c
@@ -29,10 +29,10 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
prefix = precompose_argv_prefix(argc, argv, prefix);
/*
- * We need no diff for merges options, and we need to avoid conflict
- * with our own meaning of "-m".
+ * We need (some of) diff for merges options (e.g., --cc), and we need
+ * to avoid conflict with our own meaning of "-m".
*/
- diff_merges_suppress_options_parsing();
+ diff_merges_suppress_m_parsing();
argc = setup_revisions(argc, argv, &rev, NULL);
for (i = 1; i < argc; i++) {
diff --git a/builtin/difftool.c b/builtin/difftool.c
index 6a9242a803..4931c10845 100644
--- a/builtin/difftool.c
+++ b/builtin/difftool.c
@@ -252,16 +252,6 @@ static void changed_files(struct hashmap *result, const char *index_path,
strbuf_release(&buf);
}
-static NORETURN void exit_cleanup(const char *tmpdir, int exit_code)
-{
- struct strbuf buf = STRBUF_INIT;
- strbuf_addstr(&buf, tmpdir);
- remove_dir_recursively(&buf, 0);
- if (exit_code)
- warning(_("failed: %d"), exit_code);
- exit(exit_code);
-}
-
static int ensure_leading_directories(char *path)
{
switch (safe_create_leading_directories(path)) {
@@ -330,19 +320,44 @@ static int checkout_path(unsigned mode, struct object_id *oid,
return ret;
}
+static void write_file_in_directory(struct strbuf *dir, size_t dir_len,
+ const char *path, const char *content)
+{
+ add_path(dir, dir_len, path);
+ ensure_leading_directories(dir->buf);
+ unlink(dir->buf);
+ write_file(dir->buf, "%s", content);
+}
+
+/* Write the file contents for the left and right sides of the difftool
+ * dir-diff representation for submodules and symlinks. Symlinks and submodules
+ * are written as regular text files so that external diff tools can diff them
+ * as text files, resulting in behavior that is analogous to to what "git diff"
+ * displays for symlink and submodule diffs.
+ */
+static void write_standin_files(struct pair_entry *entry,
+ struct strbuf *ldir, size_t ldir_len,
+ struct strbuf *rdir, size_t rdir_len)
+{
+ if (*entry->left)
+ write_file_in_directory(ldir, ldir_len, entry->path, entry->left);
+ if (*entry->right)
+ write_file_in_directory(rdir, rdir_len, entry->path, entry->right);
+}
+
static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
- int argc, const char **argv)
+ struct child_process *child)
{
- char tmpdir[PATH_MAX];
struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT;
struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;
struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;
struct strbuf wtdir = STRBUF_INIT;
- char *lbase_dir, *rbase_dir;
+ struct strbuf tmpdir = STRBUF_INIT;
+ char *lbase_dir = NULL, *rbase_dir = NULL;
size_t ldir_len, rdir_len, wtdir_len;
const char *workdir, *tmp;
int ret = 0, i;
- FILE *fp;
+ FILE *fp = NULL;
struct hashmap working_tree_dups = HASHMAP_INIT(working_tree_entry_cmp,
NULL);
struct hashmap submodules = HASHMAP_INIT(pair_cmp, NULL);
@@ -351,8 +366,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
struct pair_entry *entry;
struct index_state wtindex;
struct checkout lstate, rstate;
- int rc, flags = RUN_GIT_CMD, err = 0;
- struct child_process child = CHILD_PROCESS_INIT;
+ int flags = RUN_GIT_CMD, err = 0;
const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL };
struct hashmap wt_modified, tmp_modified;
int indices_loaded = 0;
@@ -361,11 +375,15 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
/* Setup temp directories */
tmp = getenv("TMPDIR");
- xsnprintf(tmpdir, sizeof(tmpdir), "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp");
- if (!mkdtemp(tmpdir))
- return error("could not create '%s'", tmpdir);
- strbuf_addf(&ldir, "%s/left/", tmpdir);
- strbuf_addf(&rdir, "%s/right/", tmpdir);
+ strbuf_add_absolute_path(&tmpdir, tmp ? tmp : "/tmp");
+ strbuf_trim_trailing_dir_sep(&tmpdir);
+ strbuf_addstr(&tmpdir, "/git-difftool.XXXXXX");
+ if (!mkdtemp(tmpdir.buf)) {
+ ret = error("could not create '%s'", tmpdir.buf);
+ goto finish;
+ }
+ strbuf_addf(&ldir, "%s/left/", tmpdir.buf);
+ strbuf_addf(&rdir, "%s/right/", tmpdir.buf);
strbuf_addstr(&wtdir, workdir);
if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1]))
strbuf_addch(&wtdir, '/');
@@ -387,19 +405,15 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
rdir_len = rdir.len;
wtdir_len = wtdir.len;
- child.no_stdin = 1;
- child.git_cmd = 1;
- child.use_shell = 0;
- child.clean_on_exit = 1;
- child.dir = prefix;
- child.out = -1;
- strvec_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z",
- NULL);
- for (i = 0; i < argc; i++)
- strvec_push(&child.args, argv[i]);
- if (start_command(&child))
+ child->no_stdin = 1;
+ child->git_cmd = 1;
+ child->use_shell = 0;
+ child->clean_on_exit = 1;
+ child->dir = prefix;
+ child->out = -1;
+ if (start_command(child))
die("could not obtain raw diff");
- fp = xfdopen(child.out, "r");
+ fp = xfdopen(child->out, "r");
/* Build index info for left and right sides of the diff */
i = 0;
@@ -410,9 +424,9 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
const char *src_path, *dst_path;
if (starts_with(info.buf, "::"))
- die(N_("combined diff formats('-c' and '--cc') are "
+ die(N_("combined diff formats ('-c' and '--cc') are "
"not supported in\n"
- "directory diff mode('-d' and '--dir-diff')."));
+ "directory diff mode ('-d' and '--dir-diff')."));
if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid,
&status))
@@ -525,7 +539,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
fclose(fp);
fp = NULL;
- if (finish_command(&child)) {
+ if (finish_command(child)) {
ret = error("error occurred running diff --raw");
goto finish;
}
@@ -540,38 +554,19 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
*/
hashmap_for_each_entry(&submodules, &iter, entry,
entry /* member name */) {
- if (*entry->left) {
- add_path(&ldir, ldir_len, entry->path);
- ensure_leading_directories(ldir.buf);
- write_file(ldir.buf, "%s", entry->left);
- }
- if (*entry->right) {
- add_path(&rdir, rdir_len, entry->path);
- ensure_leading_directories(rdir.buf);
- write_file(rdir.buf, "%s", entry->right);
- }
+ write_standin_files(entry, &ldir, ldir_len, &rdir, rdir_len);
}
/*
- * Symbolic links require special treatment.The standard "git diff"
+ * Symbolic links require special treatment. The standard "git diff"
* shows only the link itself, not the contents of the link target.
* This loop replicates that behavior.
*/
hashmap_for_each_entry(&symlinks2, &iter, entry,
entry /* member name */) {
- if (*entry->left) {
- add_path(&ldir, ldir_len, entry->path);
- ensure_leading_directories(ldir.buf);
- write_file(ldir.buf, "%s", entry->left);
- }
- if (*entry->right) {
- add_path(&rdir, rdir_len, entry->path);
- ensure_leading_directories(rdir.buf);
- write_file(rdir.buf, "%s", entry->right);
- }
- }
- strbuf_release(&buf);
+ write_standin_files(entry, &ldir, ldir_len, &rdir, rdir_len);
+ }
strbuf_setlen(&ldir, ldir_len);
helper_argv[1] = ldir.buf;
@@ -583,7 +578,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
flags = 0;
} else
setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1);
- rc = run_command_v_opt(helper_argv, flags);
+ ret = run_command_v_opt(helper_argv, flags);
/* TODO: audit for interaction with sparse-index. */
ensure_full_index(&wtindex);
@@ -617,7 +612,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
if (!indices_loaded) {
struct lock_file lock = LOCK_INIT;
strbuf_reset(&buf);
- strbuf_addf(&buf, "%s/wtindex", tmpdir);
+ strbuf_addf(&buf, "%s/wtindex", tmpdir.buf);
if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||
write_locked_index(&wtindex, &lock, COMMIT_LOCK)) {
ret = error("could not write %s", buf.buf);
@@ -647,11 +642,14 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
}
if (err) {
- warning(_("temporary files exist in '%s'."), tmpdir);
+ warning(_("temporary files exist in '%s'."), tmpdir.buf);
warning(_("you may want to cleanup or recover these."));
- exit(1);
- } else
- exit_cleanup(tmpdir, rc);
+ ret = 1;
+ } else {
+ remove_dir_recursively(&tmpdir, 0);
+ if (ret)
+ warning(_("failed: %d"), ret);
+ }
finish:
if (fp)
@@ -663,30 +661,29 @@ finish:
strbuf_release(&rdir);
strbuf_release(&wtdir);
strbuf_release(&buf);
+ strbuf_release(&tmpdir);
- return ret;
+ return (ret < 0) ? 1 : ret;
}
static int run_file_diff(int prompt, const char *prefix,
- int argc, const char **argv)
+ struct child_process *child)
{
- struct strvec args = STRVEC_INIT;
const char *env[] = {
"GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL,
NULL
};
- int i;
if (prompt > 0)
env[2] = "GIT_DIFFTOOL_PROMPT=true";
else if (!prompt)
env[2] = "GIT_DIFFTOOL_NO_PROMPT=true";
+ child->git_cmd = 1;
+ child->dir = prefix;
+ strvec_pushv(&child->env_array, env);
- strvec_push(&args, "diff");
- for (i = 0; i < argc; i++)
- strvec_push(&args, argv[i]);
- return run_command_v_opt_cd_env(args.v, RUN_GIT_CMD, prefix, env);
+ return run_command(child);
}
int cmd_difftool(int argc, const char **argv, const char *prefix)
@@ -713,12 +710,13 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)
"`--tool`")),
OPT_BOOL(0, "trust-exit-code", &trust_exit_code,
N_("make 'git-difftool' exit when an invoked diff "
- "tool returns a non - zero exit code")),
+ "tool returns a non-zero exit code")),
OPT_STRING('x', "extcmd", &extcmd, N_("command"),
N_("specify a custom command for viewing diffs")),
- OPT_ARGUMENT("no-index", &no_index, N_("passed to `diff`")),
+ OPT_BOOL(0, "no-index", &no_index, N_("passed to `diff`")),
OPT_END()
};
+ struct child_process child = CHILD_PROCESS_INIT;
git_config(difftool_config, NULL);
symlinks = has_symlinks;
@@ -768,7 +766,14 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)
* will invoke a separate instance of 'git-difftool--helper' for
* each file that changed.
*/
+ strvec_push(&child.args, "diff");
+ if (no_index)
+ strvec_push(&child.args, "--no-index");
+ if (dir_diff)
+ strvec_pushl(&child.args, "--raw", "--no-abbrev", "-z", NULL);
+ strvec_pushv(&child.args, argv);
+
if (dir_diff)
- return run_dir_diff(extcmd, symlinks, prefix, argc, argv);
- return run_file_diff(prompt, prefix, argc, argv);
+ return run_dir_diff(extcmd, symlinks, prefix, &child);
+ return run_file_diff(prompt, prefix, &child);
}
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index 3c20f164f0..95e8e89e81 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -821,6 +821,7 @@ static void handle_tag(const char *name, struct tag *tag)
static struct hashmap tags;
message = anonymize_str(&tags, anonymize_tag,
message, message_size, NULL);
+ message_size = strlen(message);
}
}
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 25740c13df..f7abbc31ff 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -712,7 +712,7 @@ static void adjust_refcol_width(const struct ref *ref)
int max, rlen, llen, len;
/* uptodate lines are only shown on high verbosity level */
- if (!verbosity && oideq(&ref->peer_ref->old_oid, &ref->old_oid))
+ if (verbosity <= 0 && oideq(&ref->peer_ref->old_oid, &ref->old_oid))
return;
max = term_columns();
@@ -748,6 +748,9 @@ static void prepare_format_display(struct ref *ref_map)
struct ref *rm;
const char *format = "full";
+ if (verbosity < 0)
+ return;
+
git_config_get_string_tmp("fetch.output", &format);
if (!strcasecmp(format, "full"))
compact_format = 0;
@@ -827,7 +830,12 @@ static void format_display(struct strbuf *display, char code,
const char *remote, const char *local,
int summary_width)
{
- int width = (summary_width + strlen(summary) - gettext_width(summary));
+ int width;
+
+ if (verbosity < 0)
+ return;
+
+ width = (summary_width + strlen(summary) - gettext_width(summary));
strbuf_addf(display, "%c %-*s ", code, width, summary);
if (!compact_format)
@@ -846,13 +854,11 @@ static int update_local_ref(struct ref *ref,
int summary_width)
{
struct commit *current = NULL, *updated;
- enum object_type type;
struct branch *current_branch = branch_get(NULL);
const char *pretty_ref = prettify_refname(ref->name);
int fast_forward = 0;
- type = oid_object_info(the_repository, &ref->new_oid, NULL);
- if (type < 0)
+ if (!repo_has_object_file(the_repository, &ref->new_oid))
die(_("object %s not found"), oid_to_hex(&ref->new_oid));
if (oideq(&ref->old_oid, &ref->new_oid)) {
@@ -964,7 +970,7 @@ static int update_local_ref(struct ref *ref,
}
}
-static int iterate_ref_map(void *cb_data, struct object_id *oid)
+static const struct object_id *iterate_ref_map(void *cb_data)
{
struct ref **rm = cb_data;
struct ref *ref = *rm;
@@ -972,10 +978,9 @@ static int iterate_ref_map(void *cb_data, struct object_id *oid)
while (ref && ref->status == REF_STATUS_REJECT_SHALLOW)
ref = ref->next;
if (!ref)
- return -1; /* end of the list */
+ return NULL;
*rm = ref->next;
- oidcpy(oid, &ref->old_oid);
- return 0;
+ return &ref->old_oid;
}
struct fetch_head {
@@ -1074,7 +1079,6 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
int connectivity_checked, struct ref *ref_map)
{
struct fetch_head fetch_head;
- struct commit *commit;
int url_len, i, rc = 0;
struct strbuf note = STRBUF_INIT, err = STRBUF_INIT;
struct ref_transaction *transaction = NULL;
@@ -1122,6 +1126,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
want_status <= FETCH_HEAD_IGNORE;
want_status++) {
for (rm = ref_map; rm; rm = rm->next) {
+ struct commit *commit = NULL;
struct ref *ref = NULL;
if (rm->status == REF_STATUS_REJECT_SHALLOW) {
@@ -1131,11 +1136,23 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
continue;
}
- commit = lookup_commit_reference_gently(the_repository,
- &rm->old_oid,
- 1);
- if (!commit)
- rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE;
+ /*
+ * References in "refs/tags/" are often going to point
+ * to annotated tags, which are not part of the
+ * commit-graph. We thus only try to look up refs in
+ * the graph which are not in that namespace to not
+ * regress performance in repositories with many
+ * annotated tags.
+ */
+ if (!starts_with(rm->name, "refs/tags/"))
+ commit = lookup_commit_in_graph(the_repository, &rm->old_oid);
+ if (!commit) {
+ commit = lookup_commit_reference_gently(the_repository,
+ &rm->old_oid,
+ 1);
+ if (!commit)
+ rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE;
+ }
if (rm->fetch_head_status != want_status)
continue;
@@ -1202,13 +1219,12 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
"FETCH_HEAD", summary_width);
}
if (note.len) {
- if (verbosity >= 0 && !shown_url) {
+ if (!shown_url) {
fprintf(stderr, _("From %.*s\n"),
url_len, url);
shown_url = 1;
}
- if (verbosity >= 0)
- fprintf(stderr, " %s\n", note.buf);
+ fprintf(stderr, " %s\n", note.buf);
}
}
}
@@ -1229,7 +1245,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
" 'git remote prune %s' to remove any old, conflicting "
"branches"), remote_name);
- if (advice_fetch_show_forced_updates) {
+ if (advice_enabled(ADVICE_FETCH_SHOW_FORCED_UPDATES)) {
if (!fetch_show_forced_updates) {
warning(_(warn_show_forced_updates));
} else if (forced_updates_ms > FORCED_UPDATES_DELAY_WARNING_IN_MS) {
@@ -1282,37 +1298,35 @@ static int check_exist_and_connected(struct ref *ref_map)
return check_connected(iterate_ref_map, &rm, &opt);
}
-static int fetch_refs(struct transport *transport, struct ref *ref_map)
+static int fetch_and_consume_refs(struct transport *transport, struct ref *ref_map)
{
- int ret = check_exist_and_connected(ref_map);
+ int connectivity_checked = 1;
+ int ret;
+
+ /*
+ * We don't need to perform a fetch in case we can already satisfy all
+ * refs.
+ */
+ ret = check_exist_and_connected(ref_map);
if (ret) {
trace2_region_enter("fetch", "fetch_refs", the_repository);
ret = transport_fetch_refs(transport, ref_map);
trace2_region_leave("fetch", "fetch_refs", the_repository);
+ if (ret)
+ goto out;
+ connectivity_checked = transport->smart_options ?
+ transport->smart_options->connectivity_checked : 0;
}
- if (!ret)
- /*
- * Keep the new pack's ".keep" file around to allow the caller
- * time to update refs to reference the new objects.
- */
- return 0;
- transport_unlock_pack(transport);
- return ret;
-}
-/* Update local refs based on the ref values fetched from a remote */
-static int consume_refs(struct transport *transport, struct ref *ref_map)
-{
- int connectivity_checked = transport->smart_options
- ? transport->smart_options->connectivity_checked : 0;
- int ret;
trace2_region_enter("fetch", "consume_refs", the_repository);
ret = store_updated_refs(transport->url,
transport->remote->name,
connectivity_checked,
ref_map);
- transport_unlock_pack(transport);
trace2_region_leave("fetch", "consume_refs", the_repository);
+
+out:
+ transport_unlock_pack(transport);
return ret;
}
@@ -1428,7 +1442,9 @@ static void add_negotiation_tips(struct git_transport_options *smart_options)
if (!has_glob_specials(s)) {
struct object_id oid;
if (get_oid(s, &oid))
- die("%s is not a valid object", s);
+ die(_("%s is not a valid object"), s);
+ if (!has_object(the_repository, &oid, 0))
+ die(_("the object %s does not exist"), s);
oid_array_append(oids, &oid);
continue;
}
@@ -1499,8 +1515,7 @@ static void backfill_tags(struct transport *transport, struct ref *ref_map)
transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
transport_set_option(transport, TRANS_OPT_DEPTH, "0");
transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL);
- if (!fetch_refs(transport, ref_map))
- consume_refs(transport, ref_map);
+ fetch_and_consume_refs(transport, ref_map);
if (gsecondary) {
transport_disconnect(gsecondary);
@@ -1591,7 +1606,7 @@ static int do_fetch(struct transport *transport,
transport->url);
}
}
- if (fetch_refs(transport, ref_map) || consume_refs(transport, ref_map)) {
+ if (fetch_and_consume_refs(transport, ref_map)) {
free_refs(ref_map);
retcode = 1;
goto cleanup;
@@ -2133,8 +2148,6 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
NULL);
}
- close_object_store(the_repository->objects);
-
if (enable_auto_gc)
run_auto_maintenance(verbosity < 0);
diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index 89cb6307d4..642b4b888f 100644
--- a/builtin/for-each-ref.c
+++ b/builtin/for-each-ref.c
@@ -77,7 +77,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
filter.name_patterns = argv;
filter.match_as_path = 1;
- filter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN);
+ filter_refs(&array, &filter, FILTER_REFS_ALL);
ref_array_sort(sorting, &array);
if (!maxcount || array.nr < maxcount)
diff --git a/builtin/gc.c b/builtin/gc.c
index f05d2f0a1a..6b3de3dd51 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -502,7 +502,7 @@ static int report_last_gc_error(void)
*/
warning(_("The last gc run reported the following. "
"Please correct the root cause\n"
- "and remove %s.\n"
+ "and remove %s\n"
"Automatic cleanup will not be performed "
"until the file is removed.\n\n"
"%s"),
@@ -663,8 +663,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
gc_before_repack();
if (!repository_format_precious_objects) {
- close_object_store(the_repository->objects);
- if (run_command_v_opt(repack.v, RUN_GIT_CMD))
+ if (run_command_v_opt(repack.v,
+ RUN_GIT_CMD | RUN_CLOSE_OBJECT_STORE))
die(FAILED_RUN, repack.v[0]);
if (prune_expire) {
@@ -848,7 +848,7 @@ static int run_write_commit_graph(struct maintenance_run_opts *opts)
{
struct child_process child = CHILD_PROCESS_INIT;
- child.git_cmd = 1;
+ child.git_cmd = child.close_object_store = 1;
strvec_pushl(&child.args, "commit-graph", "write",
"--split", "--reachable", NULL);
@@ -864,7 +864,6 @@ static int maintenance_task_commit_graph(struct maintenance_run_opts *opts)
if (!the_repository->settings.core_commit_graph)
return 0;
- close_object_store(the_repository->objects);
if (run_write_commit_graph(opts)) {
error(_("failed to write commit-graph"));
return 1;
@@ -913,7 +912,7 @@ static int maintenance_task_gc(struct maintenance_run_opts *opts)
{
struct child_process child = CHILD_PROCESS_INIT;
- child.git_cmd = 1;
+ child.git_cmd = child.close_object_store = 1;
strvec_push(&child.args, "gc");
if (opts->auto_flag)
@@ -923,7 +922,6 @@ static int maintenance_task_gc(struct maintenance_run_opts *opts)
else
strvec_push(&child.args, "--no-quiet");
- close_object_store(the_repository->objects);
return run_command(&child);
}
@@ -1097,14 +1095,12 @@ static int multi_pack_index_expire(struct maintenance_run_opts *opts)
{
struct child_process child = CHILD_PROCESS_INIT;
- child.git_cmd = 1;
+ child.git_cmd = child.close_object_store = 1;
strvec_pushl(&child.args, "multi-pack-index", "expire", NULL);
if (opts->quiet)
strvec_push(&child.args, "--no-progress");
- close_object_store(the_repository->objects);
-
if (run_command(&child))
return error(_("'git multi-pack-index expire' failed"));
@@ -1155,7 +1151,7 @@ static int multi_pack_index_repack(struct maintenance_run_opts *opts)
{
struct child_process child = CHILD_PROCESS_INIT;
- child.git_cmd = 1;
+ child.git_cmd = child.close_object_store = 1;
strvec_pushl(&child.args, "multi-pack-index", "repack", NULL);
if (opts->quiet)
@@ -1164,8 +1160,6 @@ static int multi_pack_index_repack(struct maintenance_run_opts *opts)
strvec_pushf(&child.args, "--batch-size=%"PRIuMAX,
(uintmax_t)get_auto_pack_size());
- close_object_store(the_repository->objects);
-
if (run_command(&child))
return error(_("'git multi-pack-index repack' failed"));
@@ -1529,6 +1523,93 @@ static const char *get_frequency(enum schedule_priority schedule)
}
}
+/*
+ * get_schedule_cmd` reads the GIT_TEST_MAINT_SCHEDULER environment variable
+ * to mock the schedulers that `git maintenance start` rely on.
+ *
+ * For test purpose, GIT_TEST_MAINT_SCHEDULER can be set to a comma-separated
+ * list of colon-separated key/value pairs where each pair contains a scheduler
+ * and its corresponding mock.
+ *
+ * * If $GIT_TEST_MAINT_SCHEDULER is not set, return false and leave the
+ * arguments unmodified.
+ *
+ * * If $GIT_TEST_MAINT_SCHEDULER is set, return true.
+ * In this case, the *cmd value is read as input.
+ *
+ * * if the input value *cmd is the key of one of the comma-separated list
+ * item, then *is_available is set to true and *cmd is modified and becomes
+ * the mock command.
+ *
+ * * if the input value *cmd isn’t the key of any of the comma-separated list
+ * item, then *is_available is set to false.
+ *
+ * Ex.:
+ * GIT_TEST_MAINT_SCHEDULER not set
+ * +-------+-------------------------------------------------+
+ * | Input | Output |
+ * | *cmd | return code | *cmd | *is_available |
+ * +-------+-------------+-------------------+---------------+
+ * | "foo" | false | "foo" (unchanged) | (unchanged) |
+ * +-------+-------------+-------------------+---------------+
+ *
+ * GIT_TEST_MAINT_SCHEDULER set to “foo:./mock_foo.sh,bar:./mock_bar.sh”
+ * +-------+-------------------------------------------------+
+ * | Input | Output |
+ * | *cmd | return code | *cmd | *is_available |
+ * +-------+-------------+-------------------+---------------+
+ * | "foo" | true | "./mock.foo.sh" | true |
+ * | "qux" | true | "qux" (unchanged) | false |
+ * +-------+-------------+-------------------+---------------+
+ */
+static int get_schedule_cmd(const char **cmd, int *is_available)
+{
+ char *testing = xstrdup_or_null(getenv("GIT_TEST_MAINT_SCHEDULER"));
+ struct string_list_item *item;
+ struct string_list list = STRING_LIST_INIT_NODUP;
+
+ if (!testing)
+ return 0;
+
+ if (is_available)
+ *is_available = 0;
+
+ string_list_split_in_place(&list, testing, ',', -1);
+ for_each_string_list_item(item, &list) {
+ struct string_list pair = STRING_LIST_INIT_NODUP;
+
+ if (string_list_split_in_place(&pair, item->string, ':', 2) != 2)
+ continue;
+
+ if (!strcmp(*cmd, pair.items[0].string)) {
+ *cmd = pair.items[1].string;
+ if (is_available)
+ *is_available = 1;
+ string_list_clear(&list, 0);
+ UNLEAK(testing);
+ return 1;
+ }
+ }
+
+ string_list_clear(&list, 0);
+ free(testing);
+ return 1;
+}
+
+static int is_launchctl_available(void)
+{
+ const char *cmd = "launchctl";
+ int is_available;
+ if (get_schedule_cmd(&cmd, &is_available))
+ return is_available;
+
+#ifdef __APPLE__
+ return 1;
+#else
+ return 0;
+#endif
+}
+
static char *launchctl_service_name(const char *frequency)
{
struct strbuf label = STRBUF_INIT;
@@ -1542,7 +1623,7 @@ static char *launchctl_service_filename(const char *name)
struct strbuf filename = STRBUF_INIT;
strbuf_addf(&filename, "~/Library/LaunchAgents/%s.plist", name);
- expanded = expand_user_path(filename.buf, 1);
+ expanded = interpolate_path(filename.buf, 1);
if (!expanded)
die(_("failed to expand path '%s'"), filename.buf);
@@ -1555,19 +1636,17 @@ static char *launchctl_get_uid(void)
return xstrfmt("gui/%d", getuid());
}
-static int launchctl_boot_plist(int enable, const char *filename, const char *cmd)
+static int launchctl_boot_plist(int enable, const char *filename)
{
+ const char *cmd = "launchctl";
int result;
struct child_process child = CHILD_PROCESS_INIT;
char *uid = launchctl_get_uid();
+ get_schedule_cmd(&cmd, NULL);
strvec_split(&child.args, cmd);
- if (enable)
- strvec_push(&child.args, "bootstrap");
- else
- strvec_push(&child.args, "bootout");
- strvec_push(&child.args, uid);
- strvec_push(&child.args, filename);
+ strvec_pushl(&child.args, enable ? "bootstrap" : "bootout", uid,
+ filename, NULL);
child.no_stderr = 1;
child.no_stdout = 1;
@@ -1581,38 +1660,56 @@ static int launchctl_boot_plist(int enable, const char *filename, const char *cm
return result;
}
-static int launchctl_remove_plist(enum schedule_priority schedule, const char *cmd)
+static int launchctl_remove_plist(enum schedule_priority schedule)
{
const char *frequency = get_frequency(schedule);
char *name = launchctl_service_name(frequency);
char *filename = launchctl_service_filename(name);
- int result = launchctl_boot_plist(0, filename, cmd);
+ int result = launchctl_boot_plist(0, filename);
unlink(filename);
free(filename);
free(name);
return result;
}
-static int launchctl_remove_plists(const char *cmd)
+static int launchctl_remove_plists(void)
{
- return launchctl_remove_plist(SCHEDULE_HOURLY, cmd) ||
- launchctl_remove_plist(SCHEDULE_DAILY, cmd) ||
- launchctl_remove_plist(SCHEDULE_WEEKLY, cmd);
+ return launchctl_remove_plist(SCHEDULE_HOURLY) ||
+ launchctl_remove_plist(SCHEDULE_DAILY) ||
+ launchctl_remove_plist(SCHEDULE_WEEKLY);
}
-static int launchctl_schedule_plist(const char *exec_path, enum schedule_priority schedule, const char *cmd)
+static int launchctl_list_contains_plist(const char *name, const char *cmd)
{
- FILE *plist;
- int i;
+ struct child_process child = CHILD_PROCESS_INIT;
+
+ strvec_split(&child.args, cmd);
+ strvec_pushl(&child.args, "list", name, NULL);
+
+ child.no_stderr = 1;
+ child.no_stdout = 1;
+
+ if (start_command(&child))
+ die(_("failed to start launchctl"));
+
+ /* Returns failure if 'name' doesn't exist. */
+ return !finish_command(&child);
+}
+
+static int launchctl_schedule_plist(const char *exec_path, enum schedule_priority schedule)
+{
+ int i, fd;
const char *preamble, *repeat;
const char *frequency = get_frequency(schedule);
char *name = launchctl_service_name(frequency);
char *filename = launchctl_service_filename(name);
+ struct lock_file lk = LOCK_INIT;
+ static unsigned long lock_file_timeout_ms = ULONG_MAX;
+ struct strbuf plist = STRBUF_INIT, plist2 = STRBUF_INIT;
+ struct stat st;
+ const char *cmd = "launchctl";
- if (safe_create_leading_directories(filename))
- die(_("failed to create directories for '%s'"), filename);
- plist = xfopen(filename, "w");
-
+ get_schedule_cmd(&cmd, NULL);
preamble = "<?xml version=\"1.0\"?>\n"
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
"<plist version=\"1.0\">"
@@ -1630,7 +1727,7 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
"</array>\n"
"<key>StartCalendarInterval</key>\n"
"<array>\n";
- fprintf(plist, preamble, name, exec_path, exec_path, frequency);
+ strbuf_addf(&plist, preamble, name, exec_path, exec_path, frequency);
switch (schedule) {
case SCHEDULE_HOURLY:
@@ -1639,7 +1736,7 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
"<key>Minute</key><integer>0</integer>\n"
"</dict>\n";
for (i = 1; i <= 23; i++)
- fprintf(plist, repeat, i);
+ strbuf_addf(&plist, repeat, i);
break;
case SCHEDULE_DAILY:
@@ -1649,50 +1746,91 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
"<key>Minute</key><integer>0</integer>\n"
"</dict>\n";
for (i = 1; i <= 6; i++)
- fprintf(plist, repeat, i);
+ strbuf_addf(&plist, repeat, i);
break;
case SCHEDULE_WEEKLY:
- fprintf(plist,
- "<dict>\n"
- "<key>Day</key><integer>0</integer>\n"
- "<key>Hour</key><integer>0</integer>\n"
- "<key>Minute</key><integer>0</integer>\n"
- "</dict>\n");
+ strbuf_addstr(&plist,
+ "<dict>\n"
+ "<key>Day</key><integer>0</integer>\n"
+ "<key>Hour</key><integer>0</integer>\n"
+ "<key>Minute</key><integer>0</integer>\n"
+ "</dict>\n");
break;
default:
/* unreachable */
break;
}
- fprintf(plist, "</array>\n</dict>\n</plist>\n");
- fclose(plist);
+ strbuf_addstr(&plist, "</array>\n</dict>\n</plist>\n");
+
+ if (safe_create_leading_directories(filename))
+ die(_("failed to create directories for '%s'"), filename);
- /* bootout might fail if not already running, so ignore */
- launchctl_boot_plist(0, filename, cmd);
- if (launchctl_boot_plist(1, filename, cmd))
- die(_("failed to bootstrap service %s"), filename);
+ if ((long)lock_file_timeout_ms < 0 &&
+ git_config_get_ulong("gc.launchctlplistlocktimeoutms",
+ &lock_file_timeout_ms))
+ lock_file_timeout_ms = 150;
+
+ fd = hold_lock_file_for_update_timeout(&lk, filename, LOCK_DIE_ON_ERROR,
+ lock_file_timeout_ms);
+
+ /*
+ * Does this file already exist? With the intended contents? Is it
+ * registered already? Then it does not need to be re-registered.
+ */
+ if (!stat(filename, &st) && st.st_size == plist.len &&
+ strbuf_read_file(&plist2, filename, plist.len) == plist.len &&
+ !strbuf_cmp(&plist, &plist2) &&
+ launchctl_list_contains_plist(name, cmd))
+ rollback_lock_file(&lk);
+ else {
+ if (write_in_full(fd, plist.buf, plist.len) < 0 ||
+ commit_lock_file(&lk))
+ die_errno(_("could not write '%s'"), filename);
+
+ /* bootout might fail if not already running, so ignore */
+ launchctl_boot_plist(0, filename);
+ if (launchctl_boot_plist(1, filename))
+ die(_("failed to bootstrap service %s"), filename);
+ }
free(filename);
free(name);
+ strbuf_release(&plist);
+ strbuf_release(&plist2);
return 0;
}
-static int launchctl_add_plists(const char *cmd)
+static int launchctl_add_plists(void)
{
const char *exec_path = git_exec_path();
- return launchctl_schedule_plist(exec_path, SCHEDULE_HOURLY, cmd) ||
- launchctl_schedule_plist(exec_path, SCHEDULE_DAILY, cmd) ||
- launchctl_schedule_plist(exec_path, SCHEDULE_WEEKLY, cmd);
+ return launchctl_schedule_plist(exec_path, SCHEDULE_HOURLY) ||
+ launchctl_schedule_plist(exec_path, SCHEDULE_DAILY) ||
+ launchctl_schedule_plist(exec_path, SCHEDULE_WEEKLY);
}
-static int launchctl_update_schedule(int run_maintenance, int fd, const char *cmd)
+static int launchctl_update_schedule(int run_maintenance, int fd)
{
if (run_maintenance)
- return launchctl_add_plists(cmd);
+ return launchctl_add_plists();
else
- return launchctl_remove_plists(cmd);
+ return launchctl_remove_plists();
+}
+
+static int is_schtasks_available(void)
+{
+ const char *cmd = "schtasks";
+ int is_available;
+ if (get_schedule_cmd(&cmd, &is_available))
+ return is_available;
+
+#ifdef GIT_WINDOWS_NATIVE
+ return 1;
+#else
+ return 0;
+#endif
}
static char *schtasks_task_name(const char *frequency)
@@ -1702,13 +1840,15 @@ static char *schtasks_task_name(const char *frequency)
return strbuf_detach(&label, NULL);
}
-static int schtasks_remove_task(enum schedule_priority schedule, const char *cmd)
+static int schtasks_remove_task(enum schedule_priority schedule)
{
+ const char *cmd = "schtasks";
int result;
struct strvec args = STRVEC_INIT;
const char *frequency = get_frequency(schedule);
char *name = schtasks_task_name(frequency);
+ get_schedule_cmd(&cmd, NULL);
strvec_split(&args, cmd);
strvec_pushl(&args, "/delete", "/tn", name, "/f", NULL);
@@ -1719,15 +1859,16 @@ static int schtasks_remove_task(enum schedule_priority schedule, const char *cmd
return result;
}
-static int schtasks_remove_tasks(const char *cmd)
+static int schtasks_remove_tasks(void)
{
- return schtasks_remove_task(SCHEDULE_HOURLY, cmd) ||
- schtasks_remove_task(SCHEDULE_DAILY, cmd) ||
- schtasks_remove_task(SCHEDULE_WEEKLY, cmd);
+ return schtasks_remove_task(SCHEDULE_HOURLY) ||
+ schtasks_remove_task(SCHEDULE_DAILY) ||
+ schtasks_remove_task(SCHEDULE_WEEKLY);
}
-static int schtasks_schedule_task(const char *exec_path, enum schedule_priority schedule, const char *cmd)
+static int schtasks_schedule_task(const char *exec_path, enum schedule_priority schedule)
{
+ const char *cmd = "schtasks";
int result;
struct child_process child = CHILD_PROCESS_INIT;
const char *xml;
@@ -1736,6 +1877,8 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority
char *name = schtasks_task_name(frequency);
struct strbuf tfilename = STRBUF_INIT;
+ get_schedule_cmd(&cmd, NULL);
+
strbuf_addf(&tfilename, "%s/schedule_%s_XXXXXX",
get_git_common_dir(), frequency);
tfile = xmks_tempfile(tfilename.buf);
@@ -1840,28 +1983,52 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority
return result;
}
-static int schtasks_schedule_tasks(const char *cmd)
+static int schtasks_schedule_tasks(void)
{
const char *exec_path = git_exec_path();
- return schtasks_schedule_task(exec_path, SCHEDULE_HOURLY, cmd) ||
- schtasks_schedule_task(exec_path, SCHEDULE_DAILY, cmd) ||
- schtasks_schedule_task(exec_path, SCHEDULE_WEEKLY, cmd);
+ return schtasks_schedule_task(exec_path, SCHEDULE_HOURLY) ||
+ schtasks_schedule_task(exec_path, SCHEDULE_DAILY) ||
+ schtasks_schedule_task(exec_path, SCHEDULE_WEEKLY);
}
-static int schtasks_update_schedule(int run_maintenance, int fd, const char *cmd)
+static int schtasks_update_schedule(int run_maintenance, int fd)
{
if (run_maintenance)
- return schtasks_schedule_tasks(cmd);
+ return schtasks_schedule_tasks();
else
- return schtasks_remove_tasks(cmd);
+ return schtasks_remove_tasks();
+}
+
+static int is_crontab_available(void)
+{
+ const char *cmd = "crontab";
+ int is_available;
+ struct child_process child = CHILD_PROCESS_INIT;
+
+ if (get_schedule_cmd(&cmd, &is_available))
+ return is_available;
+
+ strvec_split(&child.args, cmd);
+ strvec_push(&child.args, "-l");
+ child.no_stdin = 1;
+ child.no_stdout = 1;
+ child.no_stderr = 1;
+ child.silent_exec_failure = 1;
+
+ if (start_command(&child))
+ return 0;
+ /* Ignore exit code, as an empty crontab will return error. */
+ finish_command(&child);
+ return 1;
}
#define BEGIN_LINE "# BEGIN GIT MAINTENANCE SCHEDULE"
#define END_LINE "# END GIT MAINTENANCE SCHEDULE"
-static int crontab_update_schedule(int run_maintenance, int fd, const char *cmd)
+static int crontab_update_schedule(int run_maintenance, int fd)
{
+ const char *cmd = "crontab";
int result = 0;
int in_old_region = 0;
struct child_process crontab_list = CHILD_PROCESS_INIT;
@@ -1869,6 +2036,7 @@ static int crontab_update_schedule(int run_maintenance, int fd, const char *cmd)
FILE *cron_list, *cron_in;
struct strbuf line = STRBUF_INIT;
+ get_schedule_cmd(&cmd, NULL);
strvec_split(&crontab_list.args, cmd);
strvec_push(&crontab_list.args, "-l");
crontab_list.in = -1;
@@ -1945,66 +2113,376 @@ done_editing:
return result;
}
+static int real_is_systemd_timer_available(void)
+{
+ struct child_process child = CHILD_PROCESS_INIT;
+
+ strvec_pushl(&child.args, "systemctl", "--user", "list-timers", NULL);
+ child.no_stdin = 1;
+ child.no_stdout = 1;
+ child.no_stderr = 1;
+ child.silent_exec_failure = 1;
+
+ if (start_command(&child))
+ return 0;
+ if (finish_command(&child))
+ return 0;
+ return 1;
+}
+
+static int is_systemd_timer_available(void)
+{
+ const char *cmd = "systemctl";
+ int is_available;
+
+ if (get_schedule_cmd(&cmd, &is_available))
+ return is_available;
+
+ return real_is_systemd_timer_available();
+}
+
+static char *xdg_config_home_systemd(const char *filename)
+{
+ return xdg_config_home_for("systemd/user", filename);
+}
+
+static int systemd_timer_enable_unit(int enable,
+ enum schedule_priority schedule)
+{
+ const char *cmd = "systemctl";
+ struct child_process child = CHILD_PROCESS_INIT;
+ const char *frequency = get_frequency(schedule);
+
+ /*
+ * Disabling the systemd unit while it is already disabled makes
+ * systemctl print an error.
+ * Let's ignore it since it means we already are in the expected state:
+ * the unit is disabled.
+ *
+ * On the other hand, enabling a systemd unit which is already enabled
+ * produces no error.
+ */
+ if (!enable)
+ child.no_stderr = 1;
+
+ get_schedule_cmd(&cmd, NULL);
+ strvec_split(&child.args, cmd);
+ strvec_pushl(&child.args, "--user", enable ? "enable" : "disable",
+ "--now", NULL);
+ strvec_pushf(&child.args, "git-maintenance@%s.timer", frequency);
+
+ if (start_command(&child))
+ return error(_("failed to start systemctl"));
+ if (finish_command(&child))
+ /*
+ * Disabling an already disabled systemd unit makes
+ * systemctl fail.
+ * Let's ignore this failure.
+ *
+ * Enabling an enabled systemd unit doesn't fail.
+ */
+ if (enable)
+ return error(_("failed to run systemctl"));
+ return 0;
+}
+
+static int systemd_timer_delete_unit_templates(void)
+{
+ int ret = 0;
+ char *filename = xdg_config_home_systemd("git-maintenance@.timer");
+ if (unlink(filename) && !is_missing_file_error(errno))
+ ret = error_errno(_("failed to delete '%s'"), filename);
+ FREE_AND_NULL(filename);
+
+ filename = xdg_config_home_systemd("git-maintenance@.service");
+ if (unlink(filename) && !is_missing_file_error(errno))
+ ret = error_errno(_("failed to delete '%s'"), filename);
+
+ free(filename);
+ return ret;
+}
+
+static int systemd_timer_delete_units(void)
+{
+ return systemd_timer_enable_unit(0, SCHEDULE_HOURLY) ||
+ systemd_timer_enable_unit(0, SCHEDULE_DAILY) ||
+ systemd_timer_enable_unit(0, SCHEDULE_WEEKLY) ||
+ systemd_timer_delete_unit_templates();
+}
+
+static int systemd_timer_write_unit_templates(const char *exec_path)
+{
+ char *filename;
+ FILE *file;
+ const char *unit;
+
+ filename = xdg_config_home_systemd("git-maintenance@.timer");
+ if (safe_create_leading_directories(filename)) {
+ error(_("failed to create directories for '%s'"), filename);
+ goto error;
+ }
+ file = fopen_or_warn(filename, "w");
+ if (file == NULL)
+ goto error;
+
+ unit = "# This file was created and is maintained by Git.\n"
+ "# Any edits made in this file might be replaced in the future\n"
+ "# by a Git command.\n"
+ "\n"
+ "[Unit]\n"
+ "Description=Optimize Git repositories data\n"
+ "\n"
+ "[Timer]\n"
+ "OnCalendar=%i\n"
+ "Persistent=true\n"
+ "\n"
+ "[Install]\n"
+ "WantedBy=timers.target\n";
+ if (fputs(unit, file) == EOF) {
+ error(_("failed to write to '%s'"), filename);
+ fclose(file);
+ goto error;
+ }
+ if (fclose(file) == EOF) {
+ error_errno(_("failed to flush '%s'"), filename);
+ goto error;
+ }
+ free(filename);
+
+ filename = xdg_config_home_systemd("git-maintenance@.service");
+ file = fopen_or_warn(filename, "w");
+ if (file == NULL)
+ goto error;
+
+ unit = "# This file was created and is maintained by Git.\n"
+ "# Any edits made in this file might be replaced in the future\n"
+ "# by a Git command.\n"
+ "\n"
+ "[Unit]\n"
+ "Description=Optimize Git repositories data\n"
+ "\n"
+ "[Service]\n"
+ "Type=oneshot\n"
+ "ExecStart=\"%s/git\" --exec-path=\"%s\" for-each-repo --config=maintenance.repo maintenance run --schedule=%%i\n"
+ "LockPersonality=yes\n"
+ "MemoryDenyWriteExecute=yes\n"
+ "NoNewPrivileges=yes\n"
+ "RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6\n"
+ "RestrictNamespaces=yes\n"
+ "RestrictRealtime=yes\n"
+ "RestrictSUIDSGID=yes\n"
+ "SystemCallArchitectures=native\n"
+ "SystemCallFilter=@system-service\n";
+ if (fprintf(file, unit, exec_path, exec_path) < 0) {
+ error(_("failed to write to '%s'"), filename);
+ fclose(file);
+ goto error;
+ }
+ if (fclose(file) == EOF) {
+ error_errno(_("failed to flush '%s'"), filename);
+ goto error;
+ }
+ free(filename);
+ return 0;
+
+error:
+ free(filename);
+ systemd_timer_delete_unit_templates();
+ return -1;
+}
+
+static int systemd_timer_setup_units(void)
+{
+ const char *exec_path = git_exec_path();
+
+ int ret = systemd_timer_write_unit_templates(exec_path) ||
+ systemd_timer_enable_unit(1, SCHEDULE_HOURLY) ||
+ systemd_timer_enable_unit(1, SCHEDULE_DAILY) ||
+ systemd_timer_enable_unit(1, SCHEDULE_WEEKLY);
+ if (ret)
+ systemd_timer_delete_units();
+ return ret;
+}
+
+static int systemd_timer_update_schedule(int run_maintenance, int fd)
+{
+ if (run_maintenance)
+ return systemd_timer_setup_units();
+ else
+ return systemd_timer_delete_units();
+}
+
+enum scheduler {
+ SCHEDULER_INVALID = -1,
+ SCHEDULER_AUTO,
+ SCHEDULER_CRON,
+ SCHEDULER_SYSTEMD,
+ SCHEDULER_LAUNCHCTL,
+ SCHEDULER_SCHTASKS,
+};
+
+static const struct {
+ const char *name;
+ int (*is_available)(void);
+ int (*update_schedule)(int run_maintenance, int fd);
+} scheduler_fn[] = {
+ [SCHEDULER_CRON] = {
+ .name = "crontab",
+ .is_available = is_crontab_available,
+ .update_schedule = crontab_update_schedule,
+ },
+ [SCHEDULER_SYSTEMD] = {
+ .name = "systemctl",
+ .is_available = is_systemd_timer_available,
+ .update_schedule = systemd_timer_update_schedule,
+ },
+ [SCHEDULER_LAUNCHCTL] = {
+ .name = "launchctl",
+ .is_available = is_launchctl_available,
+ .update_schedule = launchctl_update_schedule,
+ },
+ [SCHEDULER_SCHTASKS] = {
+ .name = "schtasks",
+ .is_available = is_schtasks_available,
+ .update_schedule = schtasks_update_schedule,
+ },
+};
+
+static enum scheduler parse_scheduler(const char *value)
+{
+ if (!value)
+ return SCHEDULER_INVALID;
+ else if (!strcasecmp(value, "auto"))
+ return SCHEDULER_AUTO;
+ else if (!strcasecmp(value, "cron") || !strcasecmp(value, "crontab"))
+ return SCHEDULER_CRON;
+ else if (!strcasecmp(value, "systemd") ||
+ !strcasecmp(value, "systemd-timer"))
+ return SCHEDULER_SYSTEMD;
+ else if (!strcasecmp(value, "launchctl"))
+ return SCHEDULER_LAUNCHCTL;
+ else if (!strcasecmp(value, "schtasks"))
+ return SCHEDULER_SCHTASKS;
+ else
+ return SCHEDULER_INVALID;
+}
+
+static int maintenance_opt_scheduler(const struct option *opt, const char *arg,
+ int unset)
+{
+ enum scheduler *scheduler = opt->value;
+
+ BUG_ON_OPT_NEG(unset);
+
+ *scheduler = parse_scheduler(arg);
+ if (*scheduler == SCHEDULER_INVALID)
+ return error(_("unrecognized --scheduler argument '%s'"), arg);
+ return 0;
+}
+
+struct maintenance_start_opts {
+ enum scheduler scheduler;
+};
+
+static enum scheduler resolve_scheduler(enum scheduler scheduler)
+{
+ if (scheduler != SCHEDULER_AUTO)
+ return scheduler;
+
#if defined(__APPLE__)
-static const char platform_scheduler[] = "launchctl";
+ return SCHEDULER_LAUNCHCTL;
+
#elif defined(GIT_WINDOWS_NATIVE)
-static const char platform_scheduler[] = "schtasks";
+ return SCHEDULER_SCHTASKS;
+
+#elif defined(__linux__)
+ if (is_systemd_timer_available())
+ return SCHEDULER_SYSTEMD;
+ else if (is_crontab_available())
+ return SCHEDULER_CRON;
+ else
+ die(_("neither systemd timers nor crontab are available"));
+
#else
-static const char platform_scheduler[] = "crontab";
+ return SCHEDULER_CRON;
#endif
+}
-static int update_background_schedule(int enable)
+static void validate_scheduler(enum scheduler scheduler)
{
- int result;
- const char *scheduler = platform_scheduler;
- const char *cmd = scheduler;
- char *testing;
+ if (scheduler == SCHEDULER_INVALID)
+ BUG("invalid scheduler");
+ if (scheduler == SCHEDULER_AUTO)
+ BUG("resolve_scheduler should have been called before");
+
+ if (!scheduler_fn[scheduler].is_available())
+ die(_("%s scheduler is not available"),
+ scheduler_fn[scheduler].name);
+}
+
+static int update_background_schedule(const struct maintenance_start_opts *opts,
+ int enable)
+{
+ unsigned int i;
+ int result = 0;
struct lock_file lk;
char *lock_path = xstrfmt("%s/schedule", the_repository->objects->odb->path);
- testing = xstrdup_or_null(getenv("GIT_TEST_MAINT_SCHEDULER"));
- if (testing) {
- char *sep = strchr(testing, ':');
- if (!sep)
- die("GIT_TEST_MAINT_SCHEDULER unparseable: %s", testing);
- *sep = '\0';
- scheduler = testing;
- cmd = sep + 1;
+ if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {
+ free(lock_path);
+ return error(_("another process is scheduling background maintenance"));
}
- if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {
- result = error(_("another process is scheduling background maintenance"));
- goto cleanup;
+ for (i = 1; i < ARRAY_SIZE(scheduler_fn); i++) {
+ if (enable && opts->scheduler == i)
+ continue;
+ if (!scheduler_fn[i].is_available())
+ continue;
+ scheduler_fn[i].update_schedule(0, get_lock_file_fd(&lk));
}
- if (!strcmp(scheduler, "launchctl"))
- result = launchctl_update_schedule(enable, get_lock_file_fd(&lk), cmd);
- else if (!strcmp(scheduler, "schtasks"))
- result = schtasks_update_schedule(enable, get_lock_file_fd(&lk), cmd);
- else if (!strcmp(scheduler, "crontab"))
- result = crontab_update_schedule(enable, get_lock_file_fd(&lk), cmd);
- else
- die("unknown background scheduler: %s", scheduler);
+ if (enable)
+ result = scheduler_fn[opts->scheduler].update_schedule(
+ 1, get_lock_file_fd(&lk));
rollback_lock_file(&lk);
-cleanup:
free(lock_path);
- free(testing);
return result;
}
-static int maintenance_start(void)
+static const char *const builtin_maintenance_start_usage[] = {
+ N_("git maintenance start [--scheduler=<scheduler>]"),
+ NULL
+};
+
+static int maintenance_start(int argc, const char **argv, const char *prefix)
{
+ struct maintenance_start_opts opts = { 0 };
+ struct option options[] = {
+ OPT_CALLBACK_F(
+ 0, "scheduler", &opts.scheduler, N_("scheduler"),
+ N_("scheduler to trigger git maintenance run"),
+ PARSE_OPT_NONEG, maintenance_opt_scheduler),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, options,
+ builtin_maintenance_start_usage, 0);
+ if (argc)
+ usage_with_options(builtin_maintenance_start_usage, options);
+
+ opts.scheduler = resolve_scheduler(opts.scheduler);
+ validate_scheduler(opts.scheduler);
+
if (maintenance_register())
warning(_("failed to add repo to global config"));
-
- return update_background_schedule(1);
+ return update_background_schedule(&opts, 1);
}
static int maintenance_stop(void)
{
- return update_background_schedule(0);
+ return update_background_schedule(NULL, 0);
}
static const char builtin_maintenance_usage[] = N_("git maintenance <subcommand> [<options>]");
@@ -2018,7 +2496,7 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix)
if (!strcmp(argv[1], "run"))
return maintenance_run(argc - 1, argv + 1, prefix);
if (!strcmp(argv[1], "start"))
- return maintenance_start();
+ return maintenance_start(argc - 1, argv + 1, prefix);
if (!strcmp(argv[1], "stop"))
return maintenance_stop();
if (!strcmp(argv[1], "register"))
diff --git a/builtin/grep.c b/builtin/grep.c
index 7d2f8e5adb..8af5249a7b 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -65,6 +65,9 @@ static int todo_done;
/* Has all work items been added? */
static int all_work_added;
+static struct repository **repos_to_free;
+static size_t repos_to_free_nr, repos_to_free_alloc;
+
/* This lock protects all the variables above. */
static pthread_mutex_t grep_mutex;
@@ -168,6 +171,19 @@ static void work_done(struct work_item *w)
grep_unlock();
}
+static void free_repos(void)
+{
+ int i;
+
+ for (i = 0; i < repos_to_free_nr; i++) {
+ repo_clear(repos_to_free[i]);
+ free(repos_to_free[i]);
+ }
+ FREE_AND_NULL(repos_to_free);
+ repos_to_free_nr = 0;
+ repos_to_free_alloc = 0;
+}
+
static void *run(void *arg)
{
int hit = 0;
@@ -333,7 +349,7 @@ static int grep_oid(struct grep_opt *opt, const struct object_id *oid,
struct grep_source gs;
grep_source_name(opt, filename, tree_name_len, &pathbuf);
- grep_source_init(&gs, GREP_SOURCE_OID, pathbuf.buf, path, oid);
+ grep_source_init_oid(&gs, pathbuf.buf, path, oid, opt->repo);
strbuf_release(&pathbuf);
if (num_threads > 1) {
@@ -359,7 +375,7 @@ static int grep_file(struct grep_opt *opt, const char *filename)
struct grep_source gs;
grep_source_name(opt, filename, 0, &buf);
- grep_source_init(&gs, GREP_SOURCE_FILE, buf.buf, filename, filename);
+ grep_source_init_file(&gs, buf.buf, filename);
strbuf_release(&buf);
if (num_threads > 1) {
@@ -415,19 +431,21 @@ static int grep_submodule(struct grep_opt *opt,
const struct object_id *oid,
const char *filename, const char *path, int cached)
{
- struct repository subrepo;
+ struct repository *subrepo;
struct repository *superproject = opt->repo;
- const struct submodule *sub;
struct grep_opt subopt;
- int hit;
-
- sub = submodule_from_path(superproject, null_oid(), path);
+ int hit = 0;
if (!is_submodule_active(superproject, path))
return 0;
- if (repo_submodule_init(&subrepo, superproject, sub))
+ subrepo = xmalloc(sizeof(*subrepo));
+ if (repo_submodule_init(subrepo, superproject, path, null_oid())) {
+ free(subrepo);
return 0;
+ }
+ ALLOC_GROW(repos_to_free, repos_to_free_nr + 1, repos_to_free_alloc);
+ repos_to_free[repos_to_free_nr++] = subrepo;
/*
* NEEDSWORK: repo_read_gitmodules() might call
@@ -438,53 +456,49 @@ static int grep_submodule(struct grep_opt *opt,
* subrepo's odbs to the in-memory alternates list.
*/
obj_read_lock();
- repo_read_gitmodules(&subrepo, 0);
+ repo_read_gitmodules(subrepo, 0);
/*
- * NEEDSWORK: This adds the submodule's object directory to the list of
- * alternates for the single in-memory object store. This has some bad
- * consequences for memory (processed objects will never be freed) and
- * performance (this increases the number of pack files git has to pay
- * attention to, to the sum of the number of pack files in all the
- * repositories processed so far). This can be removed once the object
- * store is no longer global and instead is a member of the repository
- * object.
+ * All code paths tested by test code no longer need submodule ODBs to
+ * be added as alternates, but add it to the list just in case.
+ * Submodule ODBs added through add_submodule_odb_by_path() will be
+ * lazily registered as alternates when needed (and except in an
+ * unexpected code interaction, it won't be needed).
*/
- add_to_alternates_memory(subrepo.objects->odb->path);
+ add_submodule_odb_by_path(subrepo->objects->odb->path);
obj_read_unlock();
memcpy(&subopt, opt, sizeof(subopt));
- subopt.repo = &subrepo;
+ subopt.repo = subrepo;
if (oid) {
- struct object *object;
+ enum object_type object_type;
struct tree_desc tree;
void *data;
unsigned long size;
struct strbuf base = STRBUF_INIT;
obj_read_lock();
- object = parse_object_or_die(oid, NULL);
+ object_type = oid_object_info(subrepo, oid, NULL);
obj_read_unlock();
- data = read_object_with_reference(&subrepo,
- &object->oid, tree_type,
+ data = read_object_with_reference(subrepo,
+ oid, tree_type,
&size, NULL);
if (!data)
- die(_("unable to read tree (%s)"), oid_to_hex(&object->oid));
+ die(_("unable to read tree (%s)"), oid_to_hex(oid));
strbuf_addstr(&base, filename);
strbuf_addch(&base, '/');
init_tree_desc(&tree, data, size);
hit = grep_tree(&subopt, pathspec, &tree, &base, base.len,
- object->type == OBJ_COMMIT);
+ object_type == OBJ_COMMIT);
strbuf_release(&base);
free(data);
} else {
hit = grep_cache(&subopt, pathspec, cached);
}
- repo_clear(&subrepo);
return hit;
}
@@ -1182,5 +1196,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
run_pager(&opt, prefix);
clear_pathspec(&pathspec);
free_grep_patterns(&opt);
+ free_repos();
return !hit;
}
diff --git a/builtin/hash-object.c b/builtin/hash-object.c
index 640ef4ded5..c7b3ad74c6 100644
--- a/builtin/hash-object.c
+++ b/builtin/hash-object.c
@@ -53,9 +53,7 @@ static void hash_object(const char *path, const char *type, const char *vpath,
unsigned flags, int literally)
{
int fd;
- fd = open(path, O_RDONLY);
- if (fd < 0)
- die_errno("Cannot open '%s'", path);
+ fd = xopen(path, O_RDONLY);
hash_fd(fd, type, vpath, flags, literally);
}
@@ -117,7 +115,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix)
prefix = setup_git_directory_gently(&nongit);
if (vpath && prefix)
- vpath = xstrdup(prefix_filename(prefix, vpath));
+ vpath = prefix_filename(prefix, vpath);
git_config(git_default_config, NULL);
diff --git a/builtin/help.c b/builtin/help.c
index b7eec06c3d..75cd2fb407 100644
--- a/builtin/help.c
+++ b/builtin/help.c
@@ -7,7 +7,6 @@
#include "exec-cmd.h"
#include "parse-options.h"
#include "run-command.h"
-#include "column.h"
#include "config-list.h"
#include "help.h"
#include "alias.h"
@@ -34,32 +33,52 @@ enum help_format {
HELP_FORMAT_WEB
};
-static const char *html_path;
+enum show_config_type {
+ SHOW_CONFIG_HUMAN,
+ SHOW_CONFIG_VARS,
+ SHOW_CONFIG_SECTIONS,
+};
+
+static enum help_action {
+ HELP_ACTION_ALL = 1,
+ HELP_ACTION_GUIDES,
+ HELP_ACTION_CONFIG,
+ HELP_ACTION_CONFIG_FOR_COMPLETION,
+ HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION,
+} cmd_mode;
-static int show_all = 0;
-static int show_guides = 0;
-static int show_config;
+static const char *html_path;
static int verbose = 1;
-static unsigned int colopts;
static enum help_format help_format = HELP_FORMAT_NONE;
static int exclude_guides;
static struct option builtin_help_options[] = {
- OPT_BOOL('a', "all", &show_all, N_("print all available commands")),
+ OPT_CMDMODE('a', "all", &cmd_mode, N_("print all available commands"),
+ HELP_ACTION_ALL),
OPT_HIDDEN_BOOL(0, "exclude-guides", &exclude_guides, N_("exclude guides")),
- OPT_BOOL('g', "guides", &show_guides, N_("print list of useful guides")),
- OPT_BOOL('c', "config", &show_config, N_("print all configuration variable names")),
- OPT_SET_INT_F(0, "config-for-completion", &show_config, "", 2, PARSE_OPT_HIDDEN),
OPT_SET_INT('m', "man", &help_format, N_("show man page"), HELP_FORMAT_MAN),
OPT_SET_INT('w', "web", &help_format, N_("show manual in web browser"),
HELP_FORMAT_WEB),
OPT_SET_INT('i', "info", &help_format, N_("show info page"),
HELP_FORMAT_INFO),
OPT__VERBOSE(&verbose, N_("print command description")),
+
+ OPT_CMDMODE('g', "guides", &cmd_mode, N_("print list of useful guides"),
+ HELP_ACTION_GUIDES),
+ OPT_CMDMODE('c', "config", &cmd_mode, N_("print all configuration variable names"),
+ HELP_ACTION_CONFIG),
+ OPT_CMDMODE_F(0, "config-for-completion", &cmd_mode, "",
+ HELP_ACTION_CONFIG_FOR_COMPLETION, PARSE_OPT_HIDDEN),
+ OPT_CMDMODE_F(0, "config-sections-for-completion", &cmd_mode, "",
+ HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION, PARSE_OPT_HIDDEN),
+
OPT_END(),
};
static const char * const builtin_help_usage[] = {
- N_("git help [--all] [--guides] [--man | --web | --info] [<command>]"),
+ N_("git help [-a|--all] [--[no-]verbose]]\n"
+ " [[-i|--info] [-m|--man] [-w|--web]] [<command>]"),
+ N_("git help [-g|--guides]"),
+ N_("git help [-c|--config]"),
NULL
};
@@ -70,7 +89,7 @@ struct slot_expansion {
int found;
};
-static void list_config_help(int for_human)
+static void list_config_help(enum show_config_type type)
{
struct slot_expansion slot_expansions[] = {
{ "advice", "*", list_config_advices },
@@ -88,6 +107,8 @@ static void list_config_help(int for_human)
const char **p;
struct slot_expansion *e;
struct string_list keys = STRING_LIST_INIT_DUP;
+ struct string_list keys_uniq = STRING_LIST_INIT_DUP;
+ struct string_list_item *item;
int i;
for (p = config_name_list; *p; p++) {
@@ -118,34 +139,46 @@ static void list_config_help(int for_human)
for (i = 0; i < keys.nr; i++) {
const char *var = keys.items[i].string;
const char *wildcard, *tag, *cut;
+ const char *dot = NULL;
+ struct strbuf sb = STRBUF_INIT;
- if (for_human) {
+ switch (type) {
+ case SHOW_CONFIG_HUMAN:
puts(var);
continue;
+ case SHOW_CONFIG_SECTIONS:
+ dot = strchr(var, '.');
+ break;
+ case SHOW_CONFIG_VARS:
+ break;
}
-
wildcard = strchr(var, '*');
tag = strchr(var, '<');
- if (!wildcard && !tag) {
- puts(var);
+ if (!dot && !wildcard && !tag) {
+ string_list_append(&keys_uniq, var);
continue;
}
- if (wildcard && !tag)
+ if (dot)
+ cut = dot;
+ else if (wildcard && !tag)
cut = wildcard;
else if (!wildcard && tag)
cut = tag;
else
cut = wildcard < tag ? wildcard : tag;
- /*
- * We may produce duplicates, but that's up to
- * git-completion.bash to handle
- */
- printf("%.*s\n", (int)(cut - var), var);
+ strbuf_add(&sb, var, cut - var);
+ string_list_append(&keys_uniq, sb.buf);
+ strbuf_release(&sb);
+
}
string_list_clear(&keys, 0);
+ string_list_remove_duplicates(&keys_uniq, 0);
+ for_each_string_list_item(item, &keys_uniq)
+ puts(item->string);
+ string_list_clear(&keys_uniq, 0);
}
static enum help_format parse_help_format(const char *format)
@@ -349,8 +382,6 @@ static int add_man_viewer_info(const char *var, const char *value)
static int git_help_config(const char *var, const char *value, void *cb)
{
- if (starts_with(var, "column."))
- return git_column_config(var, value, "help", &colopts);
if (!strcmp(var, "help.format")) {
if (!value)
return config_error_nonbool(var);
@@ -467,11 +498,14 @@ static void get_html_page_path(struct strbuf *page_path, const char *page)
if (!html_path)
html_path = to_free = system_path(GIT_HTML_PATH);
- /* Check that we have a git documentation directory. */
+ /*
+ * Check that the page we're looking for exists.
+ */
if (!strstr(html_path, "://")) {
- if (stat(mkpath("%s/git.html", html_path), &st)
+ if (stat(mkpath("%s/%s.html", html_path, page), &st)
|| !S_ISREG(st.st_mode))
- die("'%s': not a documentation directory.", html_path);
+ die("'%s/%s.html': documentation file not found.",
+ html_path, page);
}
strbuf_init(page_path, 0);
@@ -541,6 +575,13 @@ static const char *check_git_cmd(const char* cmd)
return cmd;
}
+static void no_extra_argc(int argc)
+{
+ if (argc)
+ usage_msg_opt(_("this option doesn't take any other arguments"),
+ builtin_help_usage, builtin_help_options);
+}
+
int cmd_help(int argc, const char **argv, const char *prefix)
{
int nongit;
@@ -551,8 +592,8 @@ int cmd_help(int argc, const char **argv, const char *prefix)
builtin_help_usage, 0);
parsed_help_format = help_format;
- if (show_all) {
- git_config(git_help_config, NULL);
+ switch (cmd_mode) {
+ case HELP_ACTION_ALL:
if (verbose) {
setup_pager();
list_all_cmds_help();
@@ -560,30 +601,27 @@ int cmd_help(int argc, const char **argv, const char *prefix)
}
printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
load_command_list("git-", &main_cmds, &other_cmds);
- list_commands(colopts, &main_cmds, &other_cmds);
- }
-
- if (show_config) {
- int for_human = show_config == 1;
-
- if (!for_human) {
- list_config_help(for_human);
- return 0;
- }
- setup_pager();
- list_config_help(for_human);
- printf("\n%s\n", _("'git help config' for more information"));
- return 0;
- }
-
- if (show_guides)
+ list_commands(&main_cmds, &other_cmds);
+ printf("%s\n", _(git_more_info_string));
+ break;
+ case HELP_ACTION_GUIDES:
+ no_extra_argc(argc);
list_guides_help();
-
- if (show_all || show_guides) {
printf("%s\n", _(git_more_info_string));
- /*
- * We're done. Ignore any remaining args
- */
+ return 0;
+ case HELP_ACTION_CONFIG_FOR_COMPLETION:
+ no_extra_argc(argc);
+ list_config_help(SHOW_CONFIG_VARS);
+ return 0;
+ case HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION:
+ no_extra_argc(argc);
+ list_config_help(SHOW_CONFIG_SECTIONS);
+ return 0;
+ case HELP_ACTION_CONFIG:
+ no_extra_argc(argc);
+ setup_pager();
+ list_config_help(SHOW_CONFIG_HUMAN);
+ printf("\n%s\n", _("'git help config' for more information"));
return 0;
}
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index 8336466865..7ce69c087e 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -122,6 +122,7 @@ static int strict;
static int do_fsck_object;
static struct fsck_options fsck_options = FSCK_OPTIONS_MISSING_GITMODULES;
static int verbose;
+static const char *progress_title;
static int show_resolving_progress;
static int show_stat;
static int check_self_contained_and_connected;
@@ -187,9 +188,7 @@ static void init_thread(void)
pthread_key_create(&key, NULL);
CALLOC_ARRAY(thread_data, nr_threads);
for (i = 0; i < nr_threads; i++) {
- thread_data[i].pack_fd = open(curr_pack, O_RDONLY);
- if (thread_data[i].pack_fd == -1)
- die_errno(_("unable to open %s"), curr_pack);
+ thread_data[i].pack_fd = xopen(curr_pack, O_RDONLY);
}
threads_active = 1;
@@ -338,15 +337,11 @@ static const char *open_pack_file(const char *pack_name)
"pack/tmp_pack_XXXXXX");
pack_name = strbuf_detach(&tmp_file, NULL);
} else {
- output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
- if (output_fd < 0)
- die_errno(_("unable to create '%s'"), pack_name);
+ output_fd = xopen(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
}
nothread_data.pack_fd = output_fd;
} else {
- input_fd = open(pack_name, O_RDONLY);
- if (input_fd < 0)
- die_errno(_("cannot open packfile '%s'"), pack_name);
+ input_fd = xopen(pack_name, O_RDONLY);
output_fd = -1;
nothread_data.pack_fd = input_fd;
}
@@ -1157,6 +1152,7 @@ static void parse_pack_objects(unsigned char *hash)
if (verbose)
progress = start_progress(
+ progress_title ? progress_title :
from_stdin ? _("Receiving objects") : _("Indexing objects"),
nr_objects);
for (i = 0; i < nr_objects; i++) {
@@ -1481,6 +1477,22 @@ static void write_special_file(const char *suffix, const char *msg,
strbuf_release(&name_buf);
}
+static void rename_tmp_packfile(const char **final_name,
+ const char *curr_name,
+ struct strbuf *name, unsigned char *hash,
+ const char *ext, int make_read_only_if_same)
+{
+ if (*final_name != curr_name) {
+ if (!*final_name)
+ *final_name = odb_pack_name(name, hash, ext);
+ if (finalize_object_file(curr_name, *final_name))
+ die(_("unable to rename temporary '*.%s' file to '%s"),
+ ext, *final_name);
+ } else if (make_read_only_if_same) {
+ chmod(*final_name, 0444);
+ }
+}
+
static void final(const char *final_pack_name, const char *curr_pack_name,
const char *final_index_name, const char *curr_index_name,
const char *final_rev_index_name, const char *curr_rev_index_name,
@@ -1509,31 +1521,13 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
write_special_file("promisor", promisor_msg, final_pack_name,
hash, NULL);
- if (final_pack_name != curr_pack_name) {
- if (!final_pack_name)
- final_pack_name = odb_pack_name(&pack_name, hash, "pack");
- if (finalize_object_file(curr_pack_name, final_pack_name))
- die(_("cannot store pack file"));
- } else if (from_stdin)
- chmod(final_pack_name, 0444);
-
- if (final_index_name != curr_index_name) {
- if (!final_index_name)
- final_index_name = odb_pack_name(&index_name, hash, "idx");
- if (finalize_object_file(curr_index_name, final_index_name))
- die(_("cannot store index file"));
- } else
- chmod(final_index_name, 0444);
-
- if (curr_rev_index_name) {
- if (final_rev_index_name != curr_rev_index_name) {
- if (!final_rev_index_name)
- final_rev_index_name = odb_pack_name(&rev_index_name, hash, "rev");
- if (finalize_object_file(curr_rev_index_name, final_rev_index_name))
- die(_("cannot store reverse index file"));
- } else
- chmod(final_rev_index_name, 0444);
- }
+ rename_tmp_packfile(&final_pack_name, curr_pack_name, &pack_name,
+ hash, "pack", from_stdin);
+ if (curr_rev_index_name)
+ rename_tmp_packfile(&final_rev_index_name, curr_rev_index_name,
+ &rev_index_name, hash, "rev", 1);
+ rename_tmp_packfile(&final_index_name, curr_index_name, &index_name,
+ hash, "idx", 1);
if (do_fsck_object) {
struct packed_git *p;
@@ -1806,6 +1800,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
input_len = sizeof(*hdr);
} else if (!strcmp(arg, "-v")) {
verbose = 1;
+ } else if (!strcmp(arg, "--progress-title")) {
+ if (progress_title || (i+1) >= argc)
+ usage(index_pack_usage);
+ progress_title = argv[++i];
} else if (!strcmp(arg, "--show-resolving-progress")) {
show_resolving_progress = 1;
} else if (!strcmp(arg, "--report-end-of-input")) {
diff --git a/builtin/log.c b/builtin/log.c
index 3d7717ba5c..f75d87e8d7 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -637,7 +637,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
repo_init_revisions(the_repository, &rev, prefix);
rev.diff = 1;
rev.always_show_header = 1;
- rev.no_walk = REVISION_WALK_NO_WALK_SORTED;
+ rev.no_walk = 1;
rev.diffopt.stat_width = -1; /* Scale to real terminal size */
memset(&opt, 0, sizeof(opt));
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index 29a26ad8ae..a2000ed6bf 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -209,10 +209,8 @@ static void show_submodule(struct repository *superproject,
struct dir_struct *dir, const char *path)
{
struct repository subrepo;
- const struct submodule *sub = submodule_from_path(superproject,
- null_oid(), path);
- if (repo_submodule_init(&subrepo, superproject, sub))
+ if (repo_submodule_init(&subrepo, superproject, path, null_oid()))
return;
if (repo_read_index(&subrepo) < 0)
@@ -614,7 +612,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
struct option builtin_ls_files_options[] = {
/* Think twice before adding "--nul" synonym to this */
OPT_SET_INT('z', NULL, &line_terminator,
- N_("paths are separated with NUL character"), '\0'),
+ N_("separate paths with the NUL character"), '\0'),
OPT_BOOL('t', NULL, &show_tag,
N_("identify the file status with tags")),
OPT_BOOL('v', NULL, &show_valid_bit,
@@ -651,7 +649,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
N_("skip files matching pattern"),
PARSE_OPT_NONEG, option_parse_exclude),
OPT_CALLBACK_F('X', "exclude-from", &dir, N_("file"),
- N_("exclude patterns are read from <file>"),
+ N_("read exclude patterns from <file>"),
PARSE_OPT_NONEG, option_parse_exclude_from),
OPT_STRING(0, "exclude-per-directory", &dir.exclude_per_dir, N_("file"),
N_("read additional per-directory exclude patterns in <file>")),
diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c
index 1794548c71..318949c3d7 100644
--- a/builtin/ls-remote.c
+++ b/builtin/ls-remote.c
@@ -7,8 +7,8 @@
static const char * const ls_remote_usage[] = {
N_("git ls-remote [--heads] [--tags] [--refs] [--upload-pack=<exec>]\n"
- " [-q | --quiet] [--exit-code] [--get-url]\n"
- " [--symref] [<repository> [<refs>...]]"),
+ " [-q | --quiet] [--exit-code] [--get-url]\n"
+ " [--symref] [<repository> [<refs>...]]"),
NULL
};
@@ -84,6 +84,8 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
PARSE_OPT_STOP_AT_NON_OPTION);
dest = argv[0];
+ packet_trace_identity("ls-remote");
+
UNLEAK(sorting);
if (argc > 1) {
diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c
index 664400b816..7baef30569 100644
--- a/builtin/mailsplit.c
+++ b/builtin/mailsplit.c
@@ -75,9 +75,7 @@ static int split_one(FILE *mbox, const char *name, int allow_bare)
fprintf(stderr, "corrupt mailbox\n");
exit(1);
}
- fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
- if (fd < 0)
- die_errno("cannot open output file '%s'", name);
+ fd = xopen(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
output = xfdopen(fd, "w");
/* Copy it out, while searching for a line that begins with
diff --git a/builtin/merge.c b/builtin/merge.c
index 22f23990b3..cc4a910c69 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -13,6 +13,7 @@
#include "builtin.h"
#include "lockfile.h"
#include "run-command.h"
+#include "hook.h"
#include "diff.h"
#include "diff-merges.h"
#include "refs.h"
@@ -88,9 +89,9 @@ static int autostash;
static int no_verify;
static struct strategy all_strategy[] = {
- { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL },
+ { "recursive", NO_TRIVIAL },
{ "octopus", DEFAULT_OCTOPUS },
- { "ort", NO_TRIVIAL },
+ { "ort", DEFAULT_TWOHEAD | NO_TRIVIAL },
{ "resolve", 0 },
{ "ours", NO_FAST_FORWARD | NO_TRIVIAL },
{ "subtree", NO_FAST_FORWARD | NO_TRIVIAL },
@@ -469,7 +470,6 @@ static void finish(struct commit *head_commit,
* We ignore errors in 'gc --auto', since the
* user should see them.
*/
- close_object_store(the_repository->objects);
run_auto_maintenance(verbosity < 0);
}
}
@@ -681,6 +681,7 @@ static int read_tree_trivial(struct object_id *common, struct object_id *head,
opts.verbose_update = 1;
opts.trivial_merges_only = 1;
opts.merge = 1;
+ opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
trees[nr_trees] = parse_tree_indirect(common);
if (!trees[nr_trees++])
return -1;
@@ -739,7 +740,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
for (x = 0; x < xopts_nr; x++)
if (parse_merge_opt(&o, xopts[x]))
- die(_("Unknown option for merge-recursive: -X%s"), xopts[x]);
+ die(_("unknown strategy option: -X%s"), xopts[x]);
o.branch1 = head_arg;
o.branch2 = merge_remote_util(remoteheads->item)->name;
@@ -849,7 +850,7 @@ static void prepare_to_commit(struct commit_list *remoteheads)
* and write it out as a tree. We must do this before we invoke
* the editor and after we invoke run_status above.
*/
- if (find_hook("pre-merge-commit"))
+ if (hook_exists("pre-merge-commit"))
discard_cache();
read_cache_from(index_file);
strbuf_addbuf(&msg, &merge_msg);
@@ -862,9 +863,11 @@ static void prepare_to_commit(struct commit_list *remoteheads)
strbuf_commented_addf(&msg, "\n");
}
strbuf_commented_addf(&msg, _(merge_editor_comment));
- strbuf_commented_addf(&msg, _(cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS ?
- scissors_editor_comment :
- no_scissors_editor_comment), comment_line_char);
+ if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS)
+ strbuf_commented_addf(&msg, _(scissors_editor_comment));
+ else
+ strbuf_commented_addf(&msg,
+ _(no_scissors_editor_comment), comment_line_char);
}
if (signoff)
append_signoff(&msg, ignore_non_trailer(msg.buf, msg.len), 0);
@@ -1136,9 +1139,7 @@ static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge
merge_names = &fetch_head_file;
filename = git_path_fetch_head(the_repository);
- fd = open(filename, O_RDONLY);
- if (fd < 0)
- die_errno(_("could not open '%s' for reading"), filename);
+ fd = xopen(filename, O_RDONLY);
if (strbuf_read(merge_names, fd, 0) < 0)
die_errno(_("could not read '%s'"), filename);
@@ -1276,6 +1277,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_merge_usage, builtin_merge_options);
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+
/*
* Check if we are _not_ on a detached HEAD, i.e. if there is a
* current branch.
@@ -1368,14 +1372,14 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* There is no unmerged entry, don't advise 'git
* add/rm <file>', just 'git commit'.
*/
- if (advice_resolve_conflict)
+ if (advice_enabled(ADVICE_RESOLVE_CONFLICT))
die(_("You have not concluded your merge (MERGE_HEAD exists).\n"
"Please, commit your changes before you merge."));
else
die(_("You have not concluded your merge (MERGE_HEAD exists)."));
}
if (ref_exists("CHERRY_PICK_HEAD")) {
- if (advice_resolve_conflict)
+ if (advice_enabled(ADVICE_RESOLVE_CONFLICT))
die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
"Please, commit your changes before you merge."));
else
@@ -1485,6 +1489,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
fast_forward = FF_NO;
}
+ if (!use_strategies && !pull_twohead &&
+ remoteheads && !remoteheads->next) {
+ char *default_strategy = getenv("GIT_TEST_MERGE_ALGORITHM");
+ if (default_strategy)
+ append_strategy(get_strategy(default_strategy));
+ }
if (!use_strategies) {
if (!remoteheads)
; /* already up-to-date */
@@ -1622,7 +1632,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
}
if (fast_forward == FF_ONLY)
- die(_("Not possible to fast-forward, aborting."));
+ die_ff_impossible();
if (autostash)
create_autostash(the_repository,
diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c
index 4b827a07c0..075d15d706 100644
--- a/builtin/multi-pack-index.c
+++ b/builtin/multi-pack-index.c
@@ -55,7 +55,6 @@ static struct opts_multi_pack_index {
static struct option common_opts[] = {
OPT_FILENAME(0, "object-dir", &opts.object_dir,
N_("object directory containing set of packfile and pack-index pairs")),
- OPT_BIT(0, "progress", &opts.flags, N_("force progress reporting"), MIDX_PROGRESS),
OPT_END(),
};
@@ -64,6 +63,23 @@ static struct option *add_common_options(struct option *prev)
return parse_options_concat(common_opts, prev);
}
+static int git_multi_pack_index_write_config(const char *var, const char *value,
+ void *cb)
+{
+ if (!strcmp(var, "pack.writebitmaphashcache")) {
+ if (git_config_bool(var, value))
+ opts.flags |= MIDX_WRITE_BITMAP_HASH_CACHE;
+ else
+ opts.flags &= ~MIDX_WRITE_BITMAP_HASH_CACHE;
+ }
+
+ /*
+ * We should never make a fall-back call to 'git_default_config', since
+ * this was already called in 'cmd_multi_pack_index()'.
+ */
+ return 0;
+}
+
static void read_packs_from_stdin(struct string_list *to)
{
struct strbuf buf = STRBUF_INIT;
@@ -83,6 +99,8 @@ static int cmd_multi_pack_index_write(int argc, const char **argv)
N_("pack for reuse when computing a multi-pack bitmap")),
OPT_BIT(0, "bitmap", &opts.flags, N_("write multi-pack bitmap"),
MIDX_WRITE_BITMAP | MIDX_WRITE_REV_INDEX),
+ OPT_BIT(0, "progress", &opts.flags,
+ N_("force progress reporting"), MIDX_PROGRESS),
OPT_BOOL(0, "stdin-packs", &opts.stdin_packs,
N_("write multi-pack index containing only given indexes")),
OPT_FILENAME(0, "refs-snapshot", &opts.refs_snapshot,
@@ -90,10 +108,16 @@ static int cmd_multi_pack_index_write(int argc, const char **argv)
OPT_END(),
};
+ opts.flags |= MIDX_WRITE_BITMAP_HASH_CACHE;
+
+ git_config(git_multi_pack_index_write_config, NULL);
+
options = add_common_options(builtin_multi_pack_index_write_options);
trace2_cmd_mode(argv[0]);
+ if (isatty(2))
+ opts.flags |= MIDX_PROGRESS;
argc = parse_options(argc, argv, NULL,
options, builtin_multi_pack_index_write_usage,
PARSE_OPT_KEEP_UNKNOWN);
@@ -124,10 +148,18 @@ static int cmd_multi_pack_index_write(int argc, const char **argv)
static int cmd_multi_pack_index_verify(int argc, const char **argv)
{
- struct option *options = common_opts;
+ struct option *options;
+ static struct option builtin_multi_pack_index_verify_options[] = {
+ OPT_BIT(0, "progress", &opts.flags,
+ N_("force progress reporting"), MIDX_PROGRESS),
+ OPT_END(),
+ };
+ options = add_common_options(builtin_multi_pack_index_verify_options);
trace2_cmd_mode(argv[0]);
+ if (isatty(2))
+ opts.flags |= MIDX_PROGRESS;
argc = parse_options(argc, argv, NULL,
options, builtin_multi_pack_index_verify_usage,
PARSE_OPT_KEEP_UNKNOWN);
@@ -140,10 +172,18 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv)
static int cmd_multi_pack_index_expire(int argc, const char **argv)
{
- struct option *options = common_opts;
+ struct option *options;
+ static struct option builtin_multi_pack_index_expire_options[] = {
+ OPT_BIT(0, "progress", &opts.flags,
+ N_("force progress reporting"), MIDX_PROGRESS),
+ OPT_END(),
+ };
+ options = add_common_options(builtin_multi_pack_index_expire_options);
trace2_cmd_mode(argv[0]);
+ if (isatty(2))
+ opts.flags |= MIDX_PROGRESS;
argc = parse_options(argc, argv, NULL,
options, builtin_multi_pack_index_expire_usage,
PARSE_OPT_KEEP_UNKNOWN);
@@ -160,6 +200,8 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv)
static struct option builtin_multi_pack_index_repack_options[] = {
OPT_MAGNITUDE(0, "batch-size", &opts.batch_size,
N_("during repack, collect pack-files of smaller size into a batch that is larger than this size")),
+ OPT_BIT(0, "progress", &opts.flags,
+ N_("force progress reporting"), MIDX_PROGRESS),
OPT_END(),
};
@@ -167,6 +209,8 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv)
trace2_cmd_mode(argv[0]);
+ if (isatty(2))
+ opts.flags |= MIDX_PROGRESS;
argc = parse_options(argc, argv, NULL,
options,
builtin_multi_pack_index_repack_usage,
@@ -188,8 +232,6 @@ int cmd_multi_pack_index(int argc, const char **argv,
git_config(git_default_config, NULL);
- if (isatty(2))
- opts.flags |= MIDX_PROGRESS;
argc = parse_options(argc, argv, prefix,
builtin_multi_pack_index_options,
builtin_multi_pack_index_usage,
@@ -198,7 +240,7 @@ int cmd_multi_pack_index(int argc, const char **argv,
if (!opts.object_dir)
opts.object_dir = get_object_directory();
- if (argc == 0)
+ if (!argc)
goto usage;
if (!strcmp(argv[0], "repack"))
@@ -209,10 +251,9 @@ int cmd_multi_pack_index(int argc, const char **argv,
return cmd_multi_pack_index_verify(argc, argv);
else if (!strcmp(argv[0], "expire"))
return cmd_multi_pack_index_expire(argc, argv);
- else {
- error(_("unrecognized subcommand: %s"), argv[0]);
+
+ error(_("unrecognized subcommand: %s"), argv[0]);
usage:
- usage_with_options(builtin_multi_pack_index_usage,
- builtin_multi_pack_index_options);
- }
+ usage_with_options(builtin_multi_pack_index_usage,
+ builtin_multi_pack_index_options);
}
diff --git a/builtin/mv.c b/builtin/mv.c
index c2f96c8e89..83a465ba83 100644
--- a/builtin/mv.c
+++ b/builtin/mv.c
@@ -118,21 +118,23 @@ static int index_range_of_same_dir(const char *src, int length,
int cmd_mv(int argc, const char **argv, const char *prefix)
{
int i, flags, gitmodules_modified = 0;
- int verbose = 0, show_only = 0, force = 0, ignore_errors = 0;
+ int verbose = 0, show_only = 0, force = 0, ignore_errors = 0, ignore_sparse = 0;
struct option builtin_mv_options[] = {
OPT__VERBOSE(&verbose, N_("be verbose")),
OPT__DRY_RUN(&show_only, N_("dry run")),
OPT__FORCE(&force, N_("force move/rename even if target exists"),
PARSE_OPT_NOCOMPLETE),
OPT_BOOL('k', NULL, &ignore_errors, N_("skip move/rename errors")),
+ OPT_BOOL(0, "sparse", &ignore_sparse, N_("allow updating entries outside of the sparse-checkout cone")),
OPT_END(),
};
const char **source, **destination, **dest_path, **submodule_gitfile;
- enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes;
+ enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX, SPARSE } *modes;
struct stat st;
struct string_list src_for_dst = STRING_LIST_INIT_NODUP;
struct lock_file lock_file = LOCK_INIT;
struct cache_entry *ce;
+ struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP;
git_config(git_default_config, NULL);
@@ -176,14 +178,17 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
const char *src = source[i], *dst = destination[i];
int length, src_is_dir;
const char *bad = NULL;
+ int skip_sparse = 0;
if (show_only)
printf(_("Checking rename of '%s' to '%s'\n"), src, dst);
length = strlen(src);
- if (lstat(src, &st) < 0)
- bad = _("bad source");
- else if (!strncmp(src, dst, length) &&
+ if (lstat(src, &st) < 0) {
+ /* only error if existence is expected. */
+ if (modes[i] != SPARSE)
+ bad = _("bad source");
+ } else if (!strncmp(src, dst, length) &&
(dst[length] == 0 || dst[length] == '/')) {
bad = _("can not move directory into itself");
} else if ((src_is_dir = S_ISDIR(st.st_mode))
@@ -212,11 +217,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
dst_len = strlen(dst);
for (j = 0; j < last - first; j++) {
- const char *path = active_cache[first + j]->name;
+ const struct cache_entry *ce = active_cache[first + j];
+ const char *path = ce->name;
source[argc + j] = path;
destination[argc + j] =
prefix_path(dst, dst_len, path + length + 1);
- modes[argc + j] = INDEX;
+ modes[argc + j] = ce_skip_worktree(ce) ? SPARSE : INDEX;
submodule_gitfile[argc + j] = NULL;
}
argc += last - first;
@@ -244,14 +250,36 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
bad = _("multiple sources for the same target");
else if (is_dir_sep(dst[strlen(dst) - 1]))
bad = _("destination directory does not exist");
- else
+ else {
+ /*
+ * We check if the paths are in the sparse-checkout
+ * definition as a very final check, since that
+ * allows us to point the user to the --sparse
+ * option as a way to have a successful run.
+ */
+ if (!ignore_sparse &&
+ !path_in_sparse_checkout(src, &the_index)) {
+ string_list_append(&only_match_skip_worktree, src);
+ skip_sparse = 1;
+ }
+ if (!ignore_sparse &&
+ !path_in_sparse_checkout(dst, &the_index)) {
+ string_list_append(&only_match_skip_worktree, dst);
+ skip_sparse = 1;
+ }
+
+ if (skip_sparse)
+ goto remove_entry;
+
string_list_insert(&src_for_dst, dst);
+ }
if (!bad)
continue;
if (!ignore_errors)
die(_("%s, source=%s, destination=%s"),
bad, src, dst);
+remove_entry:
if (--argc > 0) {
int n = argc - i;
memmove(source + i, source + i + 1,
@@ -266,6 +294,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
}
}
+ if (only_match_skip_worktree.nr) {
+ advise_on_updating_sparse_paths(&only_match_skip_worktree);
+ if (!ignore_errors)
+ return 1;
+ }
+
for (i = 0; i < argc; i++) {
const char *src = source[i], *dst = destination[i];
enum update_mode mode = modes[i];
@@ -274,7 +308,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
printf(_("Renaming %s to %s\n"), src, dst);
if (show_only)
continue;
- if (mode != INDEX && rename(src, dst) < 0) {
+ if (mode != INDEX && mode != SPARSE && rename(src, dst) < 0) {
if (ignore_errors)
continue;
die_errno(_("renaming '%s' failed"), src);
diff --git a/builtin/notes.c b/builtin/notes.c
index 74bba39ca8..71c59583a1 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -172,9 +172,7 @@ static void prepare_note_data(const struct object_id *object, struct note_data *
/* write the template message before editing: */
d->edit_path = git_pathdup("NOTES_EDITMSG");
- fd = open(d->edit_path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
- if (fd < 0)
- die_errno(_("could not create file '%s'"), d->edit_path);
+ fd = xopen(d->edit_path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
if (d->given)
write_or_die(fd, d->buf.buf, d->buf.len);
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index e11d3ac2e5..1a3dd445f8 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -1222,6 +1222,7 @@ static void write_pack_file(void)
if (!pack_to_stdout) {
struct stat st;
struct strbuf tmpname = STRBUF_INIT;
+ char *idx_tmp_name = NULL;
/*
* Packs are runtime accessed in their mtime
@@ -1242,7 +1243,8 @@ static void write_pack_file(void)
warning_errno(_("failed utime() on %s"), pack_tmp_name);
}
- strbuf_addf(&tmpname, "%s-", base_name);
+ strbuf_addf(&tmpname, "%s-%s.", base_name,
+ hash_to_hex(hash));
if (write_bitmap_index) {
bitmap_writer_set_checksum(hash);
@@ -1250,13 +1252,14 @@ static void write_pack_file(void)
&to_pack, written_list, nr_written);
}
- finish_tmp_packfile(&tmpname, pack_tmp_name,
+ stage_tmp_packfiles(&tmpname, pack_tmp_name,
written_list, nr_written,
- &pack_idx_opts, hash);
+ &pack_idx_opts, hash, &idx_tmp_name);
if (write_bitmap_index) {
- strbuf_addf(&tmpname, "%s.bitmap", hash_to_hex(hash));
+ size_t tmpname_len = tmpname.len;
+ strbuf_addstr(&tmpname, "bitmap");
stop_progress(&progress_state);
bitmap_writer_show_progress(progress);
@@ -1266,8 +1269,12 @@ static void write_pack_file(void)
bitmap_writer_finish(written_list, nr_written,
tmpname.buf, write_bitmap_options);
write_bitmap_index = 0;
+ strbuf_setlen(&tmpname, tmpname_len);
}
+ rename_tmp_packfile_idx(&tmpname, &idx_tmp_name);
+
+ free(idx_tmp_name);
strbuf_release(&tmpname);
free(pack_tmp_name);
puts(hash_to_hex(hash));
@@ -3317,9 +3324,26 @@ static void read_packs_list_from_stdin(void)
}
/*
- * First handle all of the excluded packs, marking them as kept in-core
- * so that later calls to add_object_entry() discards any objects that
- * are also found in excluded packs.
+ * Arguments we got on stdin may not even be packs. First
+ * check that to avoid segfaulting later on in
+ * e.g. pack_mtime_cmp(), excluded packs are handled below.
+ *
+ * Since we first parsed our STDIN and then sorted the input
+ * lines the pack we error on will be whatever line happens to
+ * sort first. This is lazy, it's enough that we report one
+ * bad case here, we don't need to report the first/last one,
+ * or all of them.
+ */
+ for_each_string_list_item(item, &include_packs) {
+ struct packed_git *p = item->util;
+ if (!p)
+ die(_("could not find pack '%s'"), item->string);
+ }
+
+ /*
+ * Then, handle all of the excluded packs, marking them as
+ * kept in-core so that later calls to add_object_entry()
+ * discards any objects that are also found in excluded packs.
*/
for_each_string_list_item(item, &exclude_packs) {
struct packed_git *p = item->util;
@@ -3394,13 +3418,9 @@ static void read_object_list_from_stdin(void)
}
}
-/* Remember to update object flag allocation in object.h */
-#define OBJECT_ADDED (1u<<20)
-
static void show_commit(struct commit *commit, void *data)
{
add_object_entry(&commit->object.oid, OBJ_COMMIT, NULL, 0);
- commit->object.flags |= OBJECT_ADDED;
if (write_bitmap_index)
index_commit_for_bitmap(commit);
@@ -3413,7 +3433,6 @@ static void show_object(struct object *obj, const char *name, void *data)
{
add_preferred_base_object(name);
add_object_entry(&obj->oid, obj->type, name, 0);
- obj->flags |= OBJECT_ADDED;
if (use_delta_islands) {
const char *p;
@@ -3494,79 +3513,23 @@ static void show_edge(struct commit *commit)
add_preferred_base(&commit->object.oid);
}
-struct in_pack_object {
- off_t offset;
- struct object *object;
-};
-
-struct in_pack {
- unsigned int alloc;
- unsigned int nr;
- struct in_pack_object *array;
-};
-
-static void mark_in_pack_object(struct object *object, struct packed_git *p, struct in_pack *in_pack)
+static int add_object_in_unpacked_pack(const struct object_id *oid,
+ struct packed_git *pack,
+ uint32_t pos,
+ void *_data)
{
- in_pack->array[in_pack->nr].offset = find_pack_entry_one(object->oid.hash, p);
- in_pack->array[in_pack->nr].object = object;
- in_pack->nr++;
-}
-
-/*
- * Compare the objects in the offset order, in order to emulate the
- * "git rev-list --objects" output that produced the pack originally.
- */
-static int ofscmp(const void *a_, const void *b_)
-{
- struct in_pack_object *a = (struct in_pack_object *)a_;
- struct in_pack_object *b = (struct in_pack_object *)b_;
-
- if (a->offset < b->offset)
- return -1;
- else if (a->offset > b->offset)
- return 1;
- else
- return oidcmp(&a->object->oid, &b->object->oid);
+ add_object_entry(oid, OBJ_NONE, "", 0);
+ return 0;
}
static void add_objects_in_unpacked_packs(void)
{
- struct packed_git *p;
- struct in_pack in_pack;
- uint32_t i;
-
- memset(&in_pack, 0, sizeof(in_pack));
-
- for (p = get_all_packs(the_repository); p; p = p->next) {
- struct object_id oid;
- struct object *o;
-
- if (!p->pack_local || p->pack_keep || p->pack_keep_in_core)
- continue;
- if (open_pack_index(p))
- die(_("cannot open pack index"));
-
- ALLOC_GROW(in_pack.array,
- in_pack.nr + p->num_objects,
- in_pack.alloc);
-
- for (i = 0; i < p->num_objects; i++) {
- nth_packed_object_id(&oid, p, i);
- o = lookup_unknown_object(the_repository, &oid);
- if (!(o->flags & OBJECT_ADDED))
- mark_in_pack_object(o, p, &in_pack);
- o->flags |= OBJECT_ADDED;
- }
- }
-
- if (in_pack.nr) {
- QSORT(in_pack.array, in_pack.nr, ofscmp);
- for (i = 0; i < in_pack.nr; i++) {
- struct object *o = in_pack.array[i].object;
- add_object_entry(&o->oid, o->type, "", 0);
- }
- }
- free(in_pack.array);
+ if (for_each_packed_object(add_object_in_unpacked_pack, NULL,
+ FOR_EACH_OBJECT_PACK_ORDER |
+ FOR_EACH_OBJECT_LOCAL_ONLY |
+ FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |
+ FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS))
+ die(_("cannot open pack index"));
}
static int add_loose_object(const struct object_id *oid, const char *path,
diff --git a/builtin/prune.c b/builtin/prune.c
index 02c6ab7cba..485c9a3c56 100644
--- a/builtin/prune.c
+++ b/builtin/prune.c
@@ -143,7 +143,6 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
expire = TIME_MAX;
save_commit_buffer = 0;
read_replace_refs = 0;
- ref_paranoia = 1;
repo_init_revisions(the_repository, &revs, prefix);
argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
diff --git a/builtin/pull.c b/builtin/pull.c
index 3e13f81084..ae9f5bd7cc 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -26,13 +26,13 @@
#include "wt-status.h"
#include "commit-reach.h"
#include "sequencer.h"
+#include "packfile.h"
/**
* Parses the value of --rebase. If value is a false value, returns
* REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is
- * "merges", returns REBASE_MERGES. If value is "preserve", returns
- * REBASE_PRESERVE. If value is a invalid value, dies with a fatal error if
- * fatal is true, otherwise returns REBASE_INVALID.
+ * "merges", returns REBASE_MERGES. If value is a invalid value, dies with
+ * a fatal error if fatal is true, otherwise returns REBASE_INVALID.
*/
static enum rebase_type parse_config_rebase(const char *key, const char *value,
int fatal)
@@ -126,7 +126,7 @@ static struct option pull_options[] = {
/* Options passed to git-merge or git-rebase */
OPT_GROUP(N_("Options related to merging")),
OPT_CALLBACK_F('r', "rebase", &opt_rebase,
- "(false|true|merges|preserve|interactive)",
+ "(false|true|merges|interactive)",
N_("incorporate changes by rebasing rather than merging"),
PARSE_OPT_OPTARG, parse_opt_rebase),
OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,
@@ -577,7 +577,7 @@ static int run_fetch(const char *repo, const char **refspecs)
strvec_pushv(&args, refspecs);
} else if (*refspecs)
BUG("refspecs without repo?");
- ret = run_command_v_opt(args.v, RUN_GIT_CMD);
+ ret = run_command_v_opt(args.v, RUN_GIT_CMD | RUN_CLOSE_OBJECT_STORE);
strvec_clear(&args);
return ret;
}
@@ -883,8 +883,6 @@ static int run_rebase(const struct object_id *newbase,
/* Options passed to git-rebase */
if (opt_rebase == REBASE_MERGES)
strvec_push(&args, "--rebase-merges");
- else if (opt_rebase == REBASE_PRESERVE)
- strvec_push(&args, "--preserve-merges");
else if (opt_rebase == REBASE_INTERACTIVE)
strvec_push(&args, "--interactive");
if (opt_diffstat)
@@ -893,6 +891,8 @@ static int run_rebase(const struct object_id *newbase,
strvec_pushv(&args, opt_strategy_opts.v);
if (opt_gpg_sign)
strvec_push(&args, opt_gpg_sign);
+ if (opt_signoff)
+ strvec_push(&args, opt_signoff);
if (opt_autostash == 0)
strvec_push(&args, "--no-autostash");
else if (opt_autostash == 1)
@@ -911,12 +911,18 @@ static int run_rebase(const struct object_id *newbase,
return ret;
}
-static int get_can_ff(struct object_id *orig_head, struct object_id *orig_merge_head)
+static int get_can_ff(struct object_id *orig_head,
+ struct oid_array *merge_heads)
{
int ret;
struct commit_list *list = NULL;
struct commit *merge_head, *head;
+ struct object_id *orig_merge_head;
+ if (merge_heads->nr > 1)
+ return 0;
+
+ orig_merge_head = &merge_heads->oid[0];
head = lookup_commit_reference(the_repository, orig_head);
commit_list_insert(head, &list);
merge_head = lookup_commit_reference(the_repository, orig_merge_head);
@@ -927,9 +933,9 @@ static int get_can_ff(struct object_id *orig_head, struct object_id *orig_merge_
static void show_advice_pull_non_ff(void)
{
- advise(_("Pulling without specifying how to reconcile divergent branches is\n"
- "discouraged. You can squelch this message by running one of the following\n"
- "commands sometime before your next pull:\n"
+ advise(_("You have divergent branches and need to specify how to reconcile them.\n"
+ "You can do so by running one of the following commands sometime before\n"
+ "your next pull:\n"
"\n"
" git config pull.rebase false # merge (the default strategy)\n"
" git config pull.rebase true # rebase\n"
@@ -966,8 +972,22 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
parse_repo_refspecs(argc, argv, &repo, &refspecs);
- if (!opt_ff)
+ if (!opt_ff) {
opt_ff = xstrdup_or_null(config_get_ff());
+ /*
+ * A subtle point: opt_ff was set on the line above via
+ * reading from config. opt_rebase, in contrast, is set
+ * before this point via command line options. The setting
+ * of opt_rebase via reading from config (using
+ * config_get_rebase()) does not happen until later. We
+ * are relying on the next if-condition happening before
+ * the config_get_rebase() call so that an explicit
+ * "--rebase" can override a config setting of
+ * pull.ff=only.
+ */
+ if (opt_rebase >= 0 && opt_ff && !strcmp(opt_ff, "--ff-only"))
+ opt_ff = "--ff";
+ }
if (opt_rebase < 0)
opt_rebase = config_get_rebase(&rebase_unspecified);
@@ -1041,14 +1061,25 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
die(_("Cannot merge multiple branches into empty head."));
return pull_into_void(merge_heads.oid, &curr_head);
}
- if (opt_rebase && merge_heads.nr > 1)
- die(_("Cannot rebase onto multiple branches."));
+ if (merge_heads.nr > 1) {
+ if (opt_rebase)
+ die(_("Cannot rebase onto multiple branches."));
+ if (opt_ff && !strcmp(opt_ff, "--ff-only"))
+ die(_("Cannot fast-forward to multiple branches."));
+ }
- can_ff = get_can_ff(&orig_head, &merge_heads.oid[0]);
+ can_ff = get_can_ff(&orig_head, &merge_heads);
- if (rebase_unspecified && !opt_ff && !can_ff) {
- if (opt_verbosity >= 0)
- show_advice_pull_non_ff();
+ /* ff-only takes precedence over rebase */
+ if (opt_ff && !strcmp(opt_ff, "--ff-only")) {
+ if (!can_ff)
+ die_ff_impossible();
+ opt_rebase = REBASE_FALSE;
+ }
+ /* If no action specified and we can't fast forward, then warn. */
+ if (!opt_ff && rebase_unspecified && !can_ff) {
+ show_advice_pull_non_ff();
+ die(_("Need to specify how to reconcile divergent branches."));
}
if (opt_rebase) {
diff --git a/builtin/push.c b/builtin/push.c
index e8b10a9b7e..4b026ce6c6 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -289,42 +289,42 @@ static const char message_advice_ref_needs_update[] =
static void advise_pull_before_push(void)
{
- if (!advice_push_non_ff_current || !advice_push_update_rejected)
+ if (!advice_enabled(ADVICE_PUSH_NON_FF_CURRENT) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED))
return;
advise(_(message_advice_pull_before_push));
}
static void advise_checkout_pull_push(void)
{
- if (!advice_push_non_ff_matching || !advice_push_update_rejected)
+ if (!advice_enabled(ADVICE_PUSH_NON_FF_MATCHING) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED))
return;
advise(_(message_advice_checkout_pull_push));
}
static void advise_ref_already_exists(void)
{
- if (!advice_push_already_exists || !advice_push_update_rejected)
+ if (!advice_enabled(ADVICE_PUSH_ALREADY_EXISTS) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED))
return;
advise(_(message_advice_ref_already_exists));
}
static void advise_ref_fetch_first(void)
{
- if (!advice_push_fetch_first || !advice_push_update_rejected)
+ if (!advice_enabled(ADVICE_PUSH_FETCH_FIRST) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED))
return;
advise(_(message_advice_ref_fetch_first));
}
static void advise_ref_needs_force(void)
{
- if (!advice_push_needs_force || !advice_push_update_rejected)
+ if (!advice_enabled(ADVICE_PUSH_NEEDS_FORCE) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED))
return;
advise(_(message_advice_ref_needs_force));
}
static void advise_ref_needs_update(void)
{
- if (!advice_push_ref_needs_update || !advice_push_update_rejected)
+ if (!advice_enabled(ADVICE_PUSH_REF_NEEDS_UPDATE) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED))
return;
advise(_(message_advice_ref_needs_update));
}
diff --git a/builtin/read-tree.c b/builtin/read-tree.c
index 485e7b0479..2109c4c9e5 100644
--- a/builtin/read-tree.c
+++ b/builtin/read-tree.c
@@ -38,7 +38,7 @@ static int list_tree(struct object_id *oid)
}
static const char * const read_tree_usage[] = {
- N_("git read-tree [(-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>) [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])"),
+ N_("git read-tree [(-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>) [-u | -i]] [--no-sparse-checkout] [--index-output=<file>] (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])"),
NULL
};
@@ -53,24 +53,16 @@ static int index_output_cb(const struct option *opt, const char *arg,
static int exclude_per_directory_cb(const struct option *opt, const char *arg,
int unset)
{
- struct dir_struct *dir;
struct unpack_trees_options *opts;
BUG_ON_OPT_NEG(unset);
opts = (struct unpack_trees_options *)opt->value;
- if (opts->dir)
- die("more than one --exclude-per-directory given.");
-
- dir = xcalloc(1, sizeof(*opts->dir));
- dir->flags |= DIR_SHOW_IGNORED;
- dir->exclude_per_dir = arg;
- opts->dir = dir;
- /* We do not need to nor want to do read-directory
- * here; we are merely interested in reusing the
- * per directory ignore stack mechanism.
- */
+ if (!opts->update)
+ die("--exclude-per-directory is meaningless unless -u");
+ if (strcmp(arg, ".gitignore"))
+ die("--exclude-per-directory argument must be .gitignore");
return 0;
}
@@ -174,6 +166,9 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)
if (1 < opts.merge + opts.reset + prefix_set)
die("Which one? -m, --reset, or --prefix?");
+ if (opts.reset)
+ opts.reset = UNPACK_RESET_OVERWRITE_UNTRACKED;
+
/*
* NEEDSWORK
*
@@ -209,8 +204,9 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)
if ((opts.update || opts.index_only) && !opts.merge)
die("%s is meaningless without -m, --reset, or --prefix",
opts.update ? "-u" : "-i");
- if ((opts.dir && !opts.update))
- die("--exclude-per-directory is meaningless unless -u");
+ if (opts.update && !opts.reset)
+ opts.preserve_ignored = 0;
+ /* otherwise, opts.preserve_ignored is irrelevant */
if (opts.merge && !opts.index_only)
setup_work_tree();
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 33e0961900..34b4744e5f 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -48,8 +48,7 @@ static GIT_PATH_FUNC(merge_dir, "rebase-merge")
enum rebase_type {
REBASE_UNSPECIFIED = -1,
REBASE_APPLY,
- REBASE_MERGE,
- REBASE_PRESERVE_MERGES
+ REBASE_MERGE
};
enum empty_type {
@@ -163,12 +162,7 @@ enum action {
ACTION_ABORT,
ACTION_QUIT,
ACTION_EDIT_TODO,
- ACTION_SHOW_CURRENT_PATCH,
- ACTION_SHORTEN_OIDS,
- ACTION_EXPAND_OIDS,
- ACTION_CHECK_TODO_LIST,
- ACTION_REARRANGE_SQUASH,
- ACTION_ADD_EXEC
+ ACTION_SHOW_CURRENT_PATCH
};
static const char *action_names[] = { "undefined",
@@ -179,81 +173,6 @@ static const char *action_names[] = { "undefined",
"edit_todo",
"show_current_patch" };
-static int add_exec_commands(struct string_list *commands)
-{
- const char *todo_file = rebase_path_todo();
- struct todo_list todo_list = TODO_LIST_INIT;
- int res;
-
- if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
- return error_errno(_("could not read '%s'."), todo_file);
-
- if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf,
- &todo_list)) {
- todo_list_release(&todo_list);
- return error(_("unusable todo list: '%s'"), todo_file);
- }
-
- todo_list_add_exec_commands(&todo_list, commands);
- res = todo_list_write_to_file(the_repository, &todo_list,
- todo_file, NULL, NULL, -1, 0);
- todo_list_release(&todo_list);
-
- if (res)
- return error_errno(_("could not write '%s'."), todo_file);
- return 0;
-}
-
-static int rearrange_squash_in_todo_file(void)
-{
- const char *todo_file = rebase_path_todo();
- struct todo_list todo_list = TODO_LIST_INIT;
- int res = 0;
-
- if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
- return error_errno(_("could not read '%s'."), todo_file);
- if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf,
- &todo_list)) {
- todo_list_release(&todo_list);
- return error(_("unusable todo list: '%s'"), todo_file);
- }
-
- res = todo_list_rearrange_squash(&todo_list);
- if (!res)
- res = todo_list_write_to_file(the_repository, &todo_list,
- todo_file, NULL, NULL, -1, 0);
-
- todo_list_release(&todo_list);
-
- if (res)
- return error_errno(_("could not write '%s'."), todo_file);
- return 0;
-}
-
-static int transform_todo_file(unsigned flags)
-{
- const char *todo_file = rebase_path_todo();
- struct todo_list todo_list = TODO_LIST_INIT;
- int res;
-
- if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
- return error_errno(_("could not read '%s'."), todo_file);
-
- if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf,
- &todo_list)) {
- todo_list_release(&todo_list);
- return error(_("unusable todo list: '%s'"), todo_file);
- }
-
- res = todo_list_write_to_file(the_repository, &todo_list, todo_file,
- NULL, NULL, -1, flags);
- todo_list_release(&todo_list);
-
- if (res)
- return error_errno(_("could not write '%s'."), todo_file);
- return 0;
-}
-
static int edit_todo_file(unsigned flags)
{
const char *todo_file = rebase_path_todo();
@@ -403,8 +322,8 @@ static int run_sequencer_rebase(struct rebase_options *opts,
flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
flags |= opts->root_with_onto ? TODO_LIST_ROOT_WITH_ONTO : 0;
- flags |= command == ACTION_SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
flags |= opts->reapply_cherry_picks ? TODO_LIST_REAPPLY_CHERRY_PICKS : 0;
+ flags |= opts->flags & REBASE_NO_QUIET ? TODO_LIST_WARN_SKIPPED_CHERRY_PICKS : 0;
switch (command) {
case ACTION_NONE: {
@@ -438,24 +357,6 @@ static int run_sequencer_rebase(struct rebase_options *opts,
break;
}
- case ACTION_SHORTEN_OIDS:
- case ACTION_EXPAND_OIDS:
- ret = transform_todo_file(flags);
- break;
- case ACTION_CHECK_TODO_LIST:
- ret = check_todo_list_from_file(the_repository);
- break;
- case ACTION_REARRANGE_SQUASH:
- ret = rearrange_squash_in_todo_file();
- break;
- case ACTION_ADD_EXEC: {
- struct string_list commands = STRING_LIST_INIT_DUP;
-
- split_exec_commands(opts->cmd, &commands);
- ret = add_exec_commands(&commands);
- string_list_clear(&commands, 0);
- break;
- }
default:
BUG("invalid command '%d'", command);
}
@@ -477,102 +378,9 @@ static int parse_opt_keep_empty(const struct option *opt, const char *arg,
return 0;
}
-static const char * const builtin_rebase_interactive_usage[] = {
- N_("git rebase--interactive [<options>]"),
- NULL
-};
-
-int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
-{
- struct rebase_options opts = REBASE_OPTIONS_INIT;
- struct object_id squash_onto = *null_oid();
- enum action command = ACTION_NONE;
- struct option options[] = {
- OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
- REBASE_FORCE),
- OPT_CALLBACK_F('k', "keep-empty", &options, NULL,
- N_("keep commits which start empty"),
- PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
- parse_opt_keep_empty),
- OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message,
- N_("allow commits with empty messages"),
- PARSE_OPT_HIDDEN),
- OPT_BOOL(0, "rebase-merges", &opts.rebase_merges, N_("rebase merge commits")),
- OPT_BOOL(0, "rebase-cousins", &opts.rebase_cousins,
- N_("keep original branch points of cousins")),
- OPT_BOOL(0, "autosquash", &opts.autosquash,
- N_("move commits that begin with squash!/fixup!")),
- OPT_BOOL(0, "signoff", &opts.signoff, N_("sign commits")),
- OPT_BIT('v', "verbose", &opts.flags,
- N_("display a diffstat of what changed upstream"),
- REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
- OPT_CMDMODE(0, "continue", &command, N_("continue rebase"),
- ACTION_CONTINUE),
- OPT_CMDMODE(0, "skip", &command, N_("skip commit"), ACTION_SKIP),
- OPT_CMDMODE(0, "edit-todo", &command, N_("edit the todo list"),
- ACTION_EDIT_TODO),
- OPT_CMDMODE(0, "show-current-patch", &command, N_("show the current patch"),
- ACTION_SHOW_CURRENT_PATCH),
- OPT_CMDMODE(0, "shorten-ids", &command,
- N_("shorten commit ids in the todo list"), ACTION_SHORTEN_OIDS),
- OPT_CMDMODE(0, "expand-ids", &command,
- N_("expand commit ids in the todo list"), ACTION_EXPAND_OIDS),
- OPT_CMDMODE(0, "check-todo-list", &command,
- N_("check the todo list"), ACTION_CHECK_TODO_LIST),
- OPT_CMDMODE(0, "rearrange-squash", &command,
- N_("rearrange fixup/squash lines"), ACTION_REARRANGE_SQUASH),
- OPT_CMDMODE(0, "add-exec-commands", &command,
- N_("insert exec commands in todo list"), ACTION_ADD_EXEC),
- { OPTION_CALLBACK, 0, "onto", &opts.onto, N_("onto"), N_("onto"),
- PARSE_OPT_NONEG, parse_opt_commit, 0 },
- { OPTION_CALLBACK, 0, "restrict-revision", &opts.restrict_revision,
- N_("restrict-revision"), N_("restrict revision"),
- PARSE_OPT_NONEG, parse_opt_commit, 0 },
- { OPTION_CALLBACK, 0, "squash-onto", &squash_onto, N_("squash-onto"),
- N_("squash onto"), PARSE_OPT_NONEG, parse_opt_object_id, 0 },
- { OPTION_CALLBACK, 0, "upstream", &opts.upstream, N_("upstream"),
- N_("the upstream commit"), PARSE_OPT_NONEG, parse_opt_commit,
- 0 },
- OPT_STRING(0, "head-name", &opts.head_name, N_("head-name"), N_("head name")),
- { OPTION_STRING, 'S', "gpg-sign", &opts.gpg_sign_opt, N_("key-id"),
- N_("GPG-sign commits"),
- PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
- OPT_STRING(0, "strategy", &opts.strategy, N_("strategy"),
- N_("rebase strategy")),
- OPT_STRING(0, "strategy-opts", &opts.strategy_opts, N_("strategy-opts"),
- N_("strategy options")),
- OPT_STRING(0, "switch-to", &opts.switch_to, N_("switch-to"),
- N_("the branch or commit to checkout")),
- OPT_STRING(0, "onto-name", &opts.onto_name, N_("onto-name"), N_("onto name")),
- OPT_STRING(0, "cmd", &opts.cmd, N_("cmd"), N_("the command to run")),
- OPT_RERERE_AUTOUPDATE(&opts.allow_rerere_autoupdate),
- OPT_BOOL(0, "reschedule-failed-exec", &opts.reschedule_failed_exec,
- N_("automatically re-schedule any `exec` that fails")),
- OPT_END()
- };
-
- opts.rebase_cousins = -1;
-
- if (argc == 1)
- usage_with_options(builtin_rebase_interactive_usage, options);
-
- argc = parse_options(argc, argv, prefix, options,
- builtin_rebase_interactive_usage, PARSE_OPT_KEEP_ARGV0);
-
- if (!is_null_oid(&squash_onto))
- opts.squash_onto = &squash_onto;
-
- if (opts.rebase_cousins >= 0 && !opts.rebase_merges)
- warning(_("--[no-]rebase-cousins has no effect without "
- "--rebase-merges"));
-
- return !!run_sequencer_rebase(&opts, command);
-}
-
static int is_merge(struct rebase_options *opts)
{
- return opts->type == REBASE_MERGE ||
- opts->type == REBASE_PRESERVE_MERGES;
+ return opts->type == REBASE_MERGE;
}
static void imply_merge(struct rebase_options *opts, const char *option)
@@ -582,7 +390,6 @@ static void imply_merge(struct rebase_options *opts, const char *option)
die(_("%s requires the merge backend"), option);
break;
case REBASE_MERGE:
- case REBASE_PRESERVE_MERGES:
break;
default:
opts->type = REBASE_MERGE; /* implied */
@@ -740,7 +547,6 @@ static int finish_rebase(struct rebase_options *opts)
delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
unlink(git_path_auto_merge(the_repository));
apply_autostash(state_dir_path("autostash", opts));
- close_object_store(the_repository->objects);
/*
* We ignore errors in 'git maintenance run --auto', since the
* user should see them.
@@ -762,28 +568,6 @@ static int finish_rebase(struct rebase_options *opts)
return ret;
}
-static struct commit *peel_committish(const char *name)
-{
- struct object *obj;
- struct object_id oid;
-
- if (get_oid(name, &oid))
- return NULL;
- obj = parse_object(the_repository, &oid);
- return (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT);
-}
-
-static void add_var(struct strbuf *buf, const char *name, const char *value)
-{
- if (!value)
- strbuf_addf(buf, "unset %s; ", name);
- else {
- strbuf_addf(buf, "%s=", name);
- sq_quote_buf(buf, value);
- strbuf_addstr(buf, "; ");
- }
-}
-
static int move_to_original_branch(struct rebase_options *opts)
{
struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;
@@ -940,10 +724,7 @@ static int run_am(struct rebase_options *opts)
static int run_specific_rebase(struct rebase_options *opts, enum action action)
{
- const char *argv[] = { NULL, NULL };
- struct strbuf script_snippet = STRBUF_INIT, buf = STRBUF_INIT;
int status;
- const char *backend, *backend_func;
if (opts->type == REBASE_MERGE) {
/* Run sequencer-based rebase */
@@ -960,87 +741,11 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
}
status = run_sequencer_rebase(opts, action);
- goto finished_rebase;
- }
-
- if (opts->type == REBASE_APPLY) {
+ } else if (opts->type == REBASE_APPLY)
status = run_am(opts);
- goto finished_rebase;
- }
-
- add_var(&script_snippet, "GIT_DIR", absolute_path(get_git_dir()));
- add_var(&script_snippet, "state_dir", opts->state_dir);
-
- add_var(&script_snippet, "upstream_name", opts->upstream_name);
- add_var(&script_snippet, "upstream", opts->upstream ?
- oid_to_hex(&opts->upstream->object.oid) : NULL);
- add_var(&script_snippet, "head_name",
- opts->head_name ? opts->head_name : "detached HEAD");
- add_var(&script_snippet, "orig_head", oid_to_hex(&opts->orig_head));
- add_var(&script_snippet, "onto", opts->onto ?
- oid_to_hex(&opts->onto->object.oid) : NULL);
- add_var(&script_snippet, "onto_name", opts->onto_name);
- add_var(&script_snippet, "revisions", opts->revisions);
- add_var(&script_snippet, "restrict_revision", opts->restrict_revision ?
- oid_to_hex(&opts->restrict_revision->object.oid) : NULL);
- sq_quote_argv_pretty(&buf, opts->git_am_opts.v);
- add_var(&script_snippet, "git_am_opt", buf.buf);
- strbuf_release(&buf);
- add_var(&script_snippet, "verbose",
- opts->flags & REBASE_VERBOSE ? "t" : "");
- add_var(&script_snippet, "diffstat",
- opts->flags & REBASE_DIFFSTAT ? "t" : "");
- add_var(&script_snippet, "force_rebase",
- opts->flags & REBASE_FORCE ? "t" : "");
- if (opts->switch_to)
- add_var(&script_snippet, "switch_to", opts->switch_to);
- add_var(&script_snippet, "action", opts->action ? opts->action : "");
- add_var(&script_snippet, "signoff", opts->signoff ? "--signoff" : "");
- add_var(&script_snippet, "allow_rerere_autoupdate",
- opts->allow_rerere_autoupdate ?
- opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?
- "--rerere-autoupdate" : "--no-rerere-autoupdate" : "");
- add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : "");
- add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : "");
- add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt);
- add_var(&script_snippet, "cmd", opts->cmd);
- add_var(&script_snippet, "allow_empty_message",
- opts->allow_empty_message ? "--allow-empty-message" : "");
- add_var(&script_snippet, "rebase_merges",
- opts->rebase_merges ? "t" : "");
- add_var(&script_snippet, "rebase_cousins",
- opts->rebase_cousins ? "t" : "");
- add_var(&script_snippet, "strategy", opts->strategy);
- add_var(&script_snippet, "strategy_opts", opts->strategy_opts);
- add_var(&script_snippet, "rebase_root", opts->root ? "t" : "");
- add_var(&script_snippet, "squash_onto",
- opts->squash_onto ? oid_to_hex(opts->squash_onto) : "");
- add_var(&script_snippet, "git_format_patch_opt",
- opts->git_format_patch_opt.buf);
-
- if (is_merge(opts) &&
- !(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {
- strbuf_addstr(&script_snippet,
- "GIT_SEQUENCE_EDITOR=:; export GIT_SEQUENCE_EDITOR; ");
- opts->autosquash = 0;
- }
-
- switch (opts->type) {
- case REBASE_PRESERVE_MERGES:
- backend = "git-rebase--preserve-merges";
- backend_func = "git_rebase__preserve_merges";
- break;
- default:
+ else
BUG("Unhandled rebase type %d", opts->type);
- break;
- }
-
- strbuf_addf(&script_snippet,
- ". git-sh-setup && . %s && %s", backend, backend_func);
- argv[0] = script_snippet.buf;
- status = run_command_v_opt(argv, RUN_USING_SHELL);
-finished_rebase:
if (opts->dont_finish_rebase)
; /* do nothing */
else if (opts->type == REBASE_MERGE)
@@ -1058,8 +763,6 @@ finished_rebase:
die("Nothing to do");
}
- strbuf_release(&script_snippet);
-
return status ? -1 : 0;
}
@@ -1195,7 +898,7 @@ static int parse_opt_merge(const struct option *opt, const char *arg, int unset)
return 0;
}
-/* -i followed by -p is still explicitly interactive, but -p alone is not */
+/* -i followed by -r is still explicitly interactive, but -r alone is not */
static int parse_opt_interactive(const struct option *opt, const char *arg,
int unset)
{
@@ -1313,6 +1016,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
char *squash_onto_name = NULL;
int reschedule_failed_exec = -1;
int allow_preemptive_ff = 1;
+ int preserve_merges_selected = 0;
struct option builtin_rebase_options[] = {
OPT_STRING(0, "onto", &options.onto_name,
N_("revision"),
@@ -1377,10 +1081,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
N_("let the user edit the list of commits to rebase"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
parse_opt_interactive),
- OPT_SET_INT_F('p', "preserve-merges", &options.type,
+ OPT_SET_INT_F('p', "preserve-merges", &preserve_merges_selected,
N_("(DEPRECATED) try to recreate merges instead of "
"ignoring them"),
- REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
+ 1, PARSE_OPT_HIDDEN),
OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
OPT_CALLBACK_F(0, "empty", &options, "{drop,keep,ask}",
N_("how to handle commits that become empty"),
@@ -1430,6 +1134,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
usage_with_options(builtin_rebase_usage,
builtin_rebase_options);
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+
options.allow_empty_message = 1;
git_config(rebase_config, &options);
/* options.gpg_sign_opt will be either "-S" or NULL */
@@ -1448,8 +1155,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
strbuf_reset(&buf);
strbuf_addf(&buf, "%s/rewritten", merge_dir());
if (is_directory(buf.buf)) {
- options.type = REBASE_PRESERVE_MERGES;
- options.flags |= REBASE_INTERACTIVE_EXPLICIT;
+ die("`rebase -p` is no longer supported");
} else {
strbuf_reset(&buf);
strbuf_addf(&buf, "%s/interactive", merge_dir());
@@ -1470,6 +1176,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
builtin_rebase_options,
builtin_rebase_usage, 0);
+ if (preserve_merges_selected)
+ die(_("--preserve-merges was replaced by --rebase-merges"));
+
if (action != ACTION_NONE && total_argc != 2) {
usage_with_options(builtin_rebase_usage,
builtin_rebase_options);
@@ -1479,10 +1188,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
usage_with_options(builtin_rebase_usage,
builtin_rebase_options);
- if (options.type == REBASE_PRESERVE_MERGES)
- warning(_("git rebase --preserve-merges is deprecated. "
- "Use --rebase-merges instead."));
-
if (keep_base) {
if (options.onto_name)
die(_("cannot combine '--keep-base' with '--onto'"));
@@ -1574,7 +1279,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
die(_("could not move back to %s"),
oid_to_hex(&options.orig_head));
remove_branch_state(the_repository, 0);
- ret = !!finish_rebase(&options);
+ ret = finish_rebase(&options);
goto cleanup;
}
case ACTION_QUIT: {
@@ -1583,11 +1288,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
struct replay_opts replay = REPLAY_OPTS_INIT;
replay.action = REPLAY_INTERACTIVE_REBASE;
- ret = !!sequencer_remove_state(&replay);
+ ret = sequencer_remove_state(&replay);
} else {
strbuf_reset(&buf);
strbuf_addstr(&buf, options.state_dir);
- ret = !!remove_dir_recursively(&buf, 0);
+ ret = remove_dir_recursively(&buf, 0);
if (ret)
error(_("could not remove '%s'"),
options.state_dir);
@@ -1702,7 +1407,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if (options.ignore_date)
strvec_push(&options.git_am_opts, "--ignore-date");
} else {
- /* REBASE_MERGE and PRESERVE_MERGES */
+ /* REBASE_MERGE */
if (ignore_whitespace) {
string_list_append(&strategy_options,
"ignore-space-change");
@@ -1713,7 +1418,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
int i;
if (!options.strategy)
- options.strategy = "recursive";
+ options.strategy = "ort";
strbuf_reset(&buf);
for (i = 0; i < strategy_options.nr; i++)
@@ -1728,7 +1433,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
case REBASE_APPLY:
die(_("--strategy requires --merge or --interactive"));
case REBASE_MERGE:
- case REBASE_PRESERVE_MERGES:
/* compatible */
break;
case REBASE_UNSPECIFIED:
@@ -1780,7 +1484,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
switch (options.type) {
case REBASE_MERGE:
- case REBASE_PRESERVE_MERGES:
options.state_dir = merge_dir();
break;
case REBASE_APPLY:
@@ -1805,28 +1508,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
options.reschedule_failed_exec = reschedule_failed_exec;
if (options.signoff) {
- if (options.type == REBASE_PRESERVE_MERGES)
- die("cannot combine '--signoff' with "
- "'--preserve-merges'");
strvec_push(&options.git_am_opts, "--signoff");
options.flags |= REBASE_FORCE;
}
- if (options.type == REBASE_PRESERVE_MERGES) {
- /*
- * Note: incompatibility with --signoff handled in signoff block above
- * Note: incompatibility with --interactive is just a strong warning;
- * git-rebase.txt caveats with "unless you know what you are doing"
- */
- if (options.rebase_merges)
- die(_("cannot combine '--preserve-merges' with "
- "'--rebase-merges'"));
-
- if (options.reschedule_failed_exec)
- die(_("error: cannot combine '--preserve-merges' with "
- "'--reschedule-failed-exec'"));
- }
-
if (!options.root) {
if (argc < 1) {
struct branch *branch;
@@ -1845,7 +1530,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if (!strcmp(options.upstream_name, "-"))
options.upstream_name = "@{-1}";
}
- options.upstream = peel_committish(options.upstream_name);
+ options.upstream =
+ lookup_commit_reference_by_name(options.upstream_name);
if (!options.upstream)
die(_("invalid upstream '%s'"), options.upstream_name);
options.upstream_arg = options.upstream_name;
@@ -1888,7 +1574,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
options.onto = lookup_commit_or_die(&merge_base,
options.onto_name);
} else {
- options.onto = peel_committish(options.onto_name);
+ options.onto =
+ lookup_commit_reference_by_name(options.onto_name);
if (!options.onto)
die(_("Does not point to a valid commit '%s'"),
options.onto_name);
@@ -1913,13 +1600,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
die_if_checked_out(buf.buf, 1);
options.head_name = xstrdup(buf.buf);
/* If not is it a valid ref (branch or commit)? */
- } else if (!get_oid(branch_name, &options.orig_head) &&
- lookup_commit_reference(the_repository,
- &options.orig_head))
+ } else {
+ struct commit *commit =
+ lookup_commit_reference_by_name(branch_name);
+ if (!commit)
+ die(_("no such branch/commit '%s'"),
+ branch_name);
+ oidcpy(&options.orig_head, &commit->object.oid);
options.head_name = NULL;
- else
- die(_("fatal: no such branch/commit '%s'"),
- branch_name);
+ }
} else if (argc == 0) {
/* Do not need to switch branches, we are already on it. */
options.head_name =
@@ -1959,7 +1648,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if (require_clean_work_tree(the_repository, "rebase",
_("Please commit or stash them."), 1, 1)) {
- ret = 1;
+ ret = -1;
goto cleanup;
}
@@ -1994,7 +1683,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
NULL, buf.buf,
DEFAULT_REFLOG_ACTION) < 0) {
- ret = !!error(_("could not switch to "
+ ret = error(_("could not switch to "
"%s"),
options.switch_to);
goto cleanup;
@@ -2009,7 +1698,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
else
printf(_("Current branch %s is up to date.\n"),
branch_name);
- ret = !!finish_rebase(&options);
+ ret = finish_rebase(&options);
goto cleanup;
} else if (!(options.flags & REBASE_NO_QUIET))
; /* be quiet */
@@ -2087,7 +1776,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
RESET_HEAD_REFS_ONLY, "HEAD", msg.buf,
DEFAULT_REFLOG_ACTION);
strbuf_release(&msg);
- ret = !!finish_rebase(&options);
+ ret = finish_rebase(&options);
goto cleanup;
}
@@ -2101,7 +1790,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
options.revisions = revisions.buf;
run_rebase:
- ret = !!run_specific_rebase(&options, action);
+ ret = run_specific_rebase(&options, action);
cleanup:
strbuf_release(&buf);
@@ -2112,5 +1801,5 @@ cleanup:
free(options.strategy);
strbuf_release(&options.git_format_patch_opt);
free(squash_onto_name);
- return ret;
+ return !!ret;
}
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 2d1f97e1ca..25cc0c907e 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -7,6 +7,7 @@
#include "pkt-line.h"
#include "sideband.h"
#include "run-command.h"
+#include "hook.h"
#include "exec-cmd.h"
#include "commit.h"
#include "object.h"
@@ -1306,7 +1307,7 @@ static void refuse_unconfigured_deny_delete_current(void)
rp_error("%s", _(refuse_unconfigured_deny_delete_current_msg));
}
-static int command_singleton_iterator(void *cb_data, struct object_id *oid);
+static const struct object_id *command_singleton_iterator(void *cb_data);
static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
{
struct shallow_lock shallow_lock = SHALLOW_LOCK_INIT;
@@ -1463,7 +1464,7 @@ static const char *update_worktree(unsigned char *sha1, const struct worktree *w
strvec_pushf(&env, "GIT_DIR=%s", absolute_path(git_dir));
- if (!find_hook(push_to_checkout_hook))
+ if (!hook_exists(push_to_checkout_hook))
retval = push_to_deploy(sha1, &env, work_tree);
else
retval = push_to_checkout(sha1, &env, work_tree);
@@ -1731,16 +1732,15 @@ static void check_aliased_updates(struct command *commands)
string_list_clear(&ref_list, 0);
}
-static int command_singleton_iterator(void *cb_data, struct object_id *oid)
+static const struct object_id *command_singleton_iterator(void *cb_data)
{
struct command **cmd_list = cb_data;
struct command *cmd = *cmd_list;
if (!cmd || is_null_oid(&cmd->new_oid))
- return -1; /* end of list */
+ return NULL;
*cmd_list = NULL; /* this returns only one */
- oidcpy(oid, &cmd->new_oid);
- return 0;
+ return &cmd->new_oid;
}
static void set_connectivity_errors(struct command *commands,
@@ -1770,7 +1770,7 @@ struct iterate_data {
struct shallow_info *si;
};
-static int iterate_receive_command_list(void *cb_data, struct object_id *oid)
+static const struct object_id *iterate_receive_command_list(void *cb_data)
{
struct iterate_data *data = cb_data;
struct command **cmd_list = &data->cmds;
@@ -1781,13 +1781,11 @@ static int iterate_receive_command_list(void *cb_data, struct object_id *oid)
/* to be checked in update_shallow_ref() */
continue;
if (!is_null_oid(&cmd->new_oid) && !cmd->skip_update) {
- oidcpy(oid, &cmd->new_oid);
*cmd_list = cmd->next;
- return 0;
+ return &cmd->new_oid;
}
}
- *cmd_list = NULL;
- return -1; /* end of list */
+ return NULL;
}
static void reject_updates_to_hidden(struct command *commands)
@@ -2477,7 +2475,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
struct option options[] = {
OPT__QUIET(&quiet, N_("quiet")),
OPT_HIDDEN_BOOL(0, "stateless-rpc", &stateless_rpc, NULL),
- OPT_HIDDEN_BOOL(0, "advertise-refs", &advertise_refs, NULL),
+ OPT_HIDDEN_BOOL(0, "http-backend-info-refs", &advertise_refs, NULL),
+ OPT_ALIAS(0, "advertise-refs", "http-backend-info-refs"),
OPT_HIDDEN_BOOL(0, "reject-thin-pack-for-testing", &reject_thin, NULL),
OPT_END()
};
@@ -2580,10 +2579,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
proc.no_stdin = 1;
proc.stdout_to_stderr = 1;
proc.err = use_sideband ? -1 : 0;
- proc.git_cmd = 1;
+ proc.git_cmd = proc.close_object_store = 1;
proc.argv = argv_gc_auto;
- close_object_store(the_repository->objects);
if (!start_command(&proc)) {
if (use_sideband)
copy_to_sideband(proc.err, -1, NULL);
diff --git a/builtin/reflog.c b/builtin/reflog.c
index 09541d1c80..bd4c669918 100644
--- a/builtin/reflog.c
+++ b/builtin/reflog.c
@@ -629,8 +629,9 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
free_worktrees(worktrees);
for (i = 0; i < collected.nr; i++) {
struct collected_reflog *e = collected.e[i];
+
set_reflog_expiry_param(&cb.cmd, explicit_expiry, e->reflog);
- status |= reflog_expire(e->reflog, &e->oid, flags,
+ status |= reflog_expire(e->reflog, flags,
reflog_expiry_prepare,
should_expire_reflog_ent,
reflog_expiry_cleanup,
@@ -642,13 +643,12 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
for (; i < argc; i++) {
char *ref;
- struct object_id oid;
- if (!dwim_log(argv[i], strlen(argv[i]), &oid, &ref)) {
+ if (!dwim_log(argv[i], strlen(argv[i]), NULL, &ref)) {
status |= error(_("%s points nowhere!"), argv[i]);
continue;
}
set_reflog_expiry_param(&cb.cmd, explicit_expiry, ref);
- status |= reflog_expire(ref, &oid, flags,
+ status |= reflog_expire(ref, flags,
reflog_expiry_prepare,
should_expire_reflog_ent,
reflog_expiry_cleanup,
@@ -700,7 +700,6 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
for ( ; i < argc; i++) {
const char *spec = strstr(argv[i], "@{");
- struct object_id oid;
char *ep, *ref;
int recno;
@@ -709,7 +708,7 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
continue;
}
- if (!dwim_log(argv[i], spec - argv[i], &oid, &ref)) {
+ if (!dwim_log(argv[i], spec - argv[i], NULL, &ref)) {
status |= error(_("no reflog for '%s'"), argv[i]);
continue;
}
@@ -724,7 +723,7 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
cb.cmd.expire_total = 0;
}
- status |= reflog_expire(ref, &oid, flags,
+ status |= reflog_expire(ref, flags,
reflog_expiry_prepare,
should_expire_reflog_ent,
reflog_expiry_cleanup,
diff --git a/builtin/remote.c b/builtin/remote.c
index 7f88e6ce9d..5705e088e5 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -318,6 +318,9 @@ static int config_read_branches(const char *key, const char *value, void *cb)
* truth value with >= REBASE_TRUE.
*/
info->rebase = rebase_parse_value(value);
+ if (info->rebase == REBASE_INVALID)
+ warning(_("unhandled branch.%s.rebase=%s; assuming "
+ "'true'"), name, value);
break;
case PUSH_REMOTE:
if (info->push_remote_name)
diff --git a/builtin/repack.c b/builtin/repack.c
index 799e16c4bf..0b2d1e5d82 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -211,10 +211,10 @@ static struct {
unsigned optional:1;
} exts[] = {
{".pack"},
- {".idx"},
{".rev", 1},
{".bitmap", 1},
{".promisor", 1},
+ {".idx"},
};
static unsigned populate_pack_exts(char *name)
@@ -771,15 +771,12 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
strvec_pushf(&cmd.args,
"--unpack-unreachable=%s",
unpack_unreachable);
- strvec_push(&cmd.env_array, "GIT_REF_PARANOIA=1");
} else if (pack_everything & LOOSEN_UNREACHABLE) {
strvec_push(&cmd.args,
"--unpack-unreachable");
} else if (keep_unreachable) {
strvec_push(&cmd.args, "--keep-unreachable");
strvec_push(&cmd.args, "--pack-loose-unreachable");
- } else {
- strvec_push(&cmd.env_array, "GIT_REF_PARANOIA=1");
}
}
} else if (geometry) {
diff --git a/builtin/replace.c b/builtin/replace.c
index cd48765911..946938d011 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -507,7 +507,7 @@ static int convert_graft_file(int force)
if (!fp)
return -1;
- advice_graft_file_deprecated = 0;
+ no_graft_file_deprecated_advice = 1;
while (strbuf_getline(&buf, fp) != EOF) {
if (*buf.buf == '#')
continue;
diff --git a/builtin/reset.c b/builtin/reset.c
index 43e855cb88..7393595349 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -67,12 +67,18 @@ static int reset_index(const char *ref, const struct object_id *oid, int reset_t
case KEEP:
case MERGE:
opts.update = 1;
+ opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
break;
case HARD:
opts.update = 1;
- /* fallthrough */
+ opts.reset = UNPACK_RESET_OVERWRITE_UNTRACKED;
+ break;
+ case MIXED:
+ opts.reset = UNPACK_RESET_PROTECT_UNTRACKED;
+ /* but opts.update=0, so working tree not updated */
+ break;
default:
- opts.reset = 1;
+ BUG("invalid reset_type passed to reset_index");
}
read_cache_unmerged();
@@ -412,7 +418,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
refresh_index(&the_index, flags, NULL, NULL,
_("Unstaged changes after reset:"));
t_delta_in_ms = (getnanotime() - t_begin) / 1000000;
- if (advice_reset_quiet_warning && t_delta_in_ms > REFRESH_INDEX_DELAY_WARNING_IN_MS) {
+ if (advice_enabled(ADVICE_RESET_QUIET_WARNING) && t_delta_in_ms > REFRESH_INDEX_DELAY_WARNING_IN_MS) {
printf(_("\nIt took %.2f seconds to enumerate unstaged changes after reset. You can\n"
"use '--quiet' to avoid this. Set the config setting reset.quiet to true\n"
"to make this the default.\n"), t_delta_in_ms / 1000.0);
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 22c4e1a4ff..8480a59f57 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -863,8 +863,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
continue;
}
if (!strcmp(arg, "--bisect")) {
- for_each_fullref_in("refs/bisect/bad", show_reference, NULL, 0);
- for_each_fullref_in("refs/bisect/good", anti_reference, NULL, 0);
+ for_each_fullref_in("refs/bisect/bad", show_reference, NULL);
+ for_each_fullref_in("refs/bisect/good", anti_reference, NULL);
continue;
}
if (opt_with_value(arg, "--branches", &arg)) {
diff --git a/builtin/revert.c b/builtin/revert.c
index 237f2f18d4..51776abea6 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -136,6 +136,9 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)
PARSE_OPT_KEEP_ARGV0 |
PARSE_OPT_KEEP_UNKNOWN);
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+
/* implies allow_empty */
if (opts->keep_redundant_commits)
opts->allow_empty = 1;
@@ -191,7 +194,8 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)
struct setup_revision_opt s_r_opt;
opts->revs = xmalloc(sizeof(*opts->revs));
repo_init_revisions(the_repository, opts->revs, NULL);
- opts->revs->no_walk = REVISION_WALK_NO_WALK_UNSORTED;
+ opts->revs->no_walk = 1;
+ opts->revs->unsorted_input = 1;
if (argc < 2)
usage_with_options(usage_str, options);
if (!strcmp(argv[1], "-"))
diff --git a/builtin/rm.c b/builtin/rm.c
index 8a24c715e0..3d0967cdc1 100644
--- a/builtin/rm.c
+++ b/builtin/rm.c
@@ -55,7 +55,7 @@ static void print_error_files(struct string_list *files_list,
strbuf_addf(&err_msg,
"\n %s",
files_list->items[i].string);
- if (advice_rm_hints)
+ if (advice_enabled(ADVICE_RM_HINTS))
strbuf_addstr(&err_msg, hints_msg);
*errs = error("%s", err_msg.buf);
strbuf_release(&err_msg);
@@ -237,6 +237,7 @@ static int check_local_mod(struct object_id *head, int index_only)
static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0;
static int ignore_unmatch = 0, pathspec_file_nul;
+static int include_sparse;
static char *pathspec_from_file;
static struct option builtin_rm_options[] = {
@@ -247,6 +248,7 @@ static struct option builtin_rm_options[] = {
OPT_BOOL('r', NULL, &recursive, N_("allow recursive removal")),
OPT_BOOL( 0 , "ignore-unmatch", &ignore_unmatch,
N_("exit with a zero status even if nothing matched")),
+ OPT_BOOL(0, "sparse", &include_sparse, N_("allow updating entries outside of the sparse-checkout cone")),
OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
OPT_END(),
@@ -298,7 +300,10 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
ensure_full_index(&the_index);
for (i = 0; i < active_nr; i++) {
const struct cache_entry *ce = active_cache[i];
- if (ce_skip_worktree(ce))
+
+ if (!include_sparse &&
+ (ce_skip_worktree(ce) ||
+ !path_in_sparse_checkout(ce->name, &the_index)))
continue;
if (!ce_path_match(&the_index, ce, &pathspec, seen))
continue;
@@ -322,7 +327,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
seen_any = 1;
else if (ignore_unmatch)
continue;
- else if (matches_skip_worktree(&pathspec, i, &skip_worktree_seen))
+ else if (!include_sparse &&
+ matches_skip_worktree(&pathspec, i, &skip_worktree_seen))
string_list_append(&only_match_skip_worktree, original);
else
die(_("pathspec '%s' did not match any files"), original);
diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index a7e01667b0..8932142312 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -17,10 +17,10 @@
#include "protocol.h"
static const char * const send_pack_usage[] = {
- N_("git send-pack [--all | --mirror] [--dry-run] [--force] "
- "[--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] "
- "[<host>:]<directory> [<ref>...]\n"
- " --all and explicit <ref> specification are mutually exclusive."),
+ N_("git send-pack [--mirror] [--dry-run] [--force]\n"
+ " [--receive-pack=<git-receive-pack>]\n"
+ " [--verbose] [--thin] [--atomic]\n"
+ " [<host>:]<directory> (--all | <ref>...)"),
NULL,
};
@@ -230,6 +230,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
args.atomic = atomic;
args.stateless_rpc = stateless_rpc;
args.push_options = push_options.nr ? &push_options : NULL;
+ args.url = dest;
if (from_stdin) {
if (args.stateless_rpc) {
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index d77ce7aeb3..082449293b 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -11,9 +11,9 @@
static const char* show_branch_usage[] = {
N_("git show-branch [-a | --all] [-r | --remotes] [--topo-order | --date-order]\n"
- " [--current] [--color[=<when>] | --no-color] [--sparse]\n"
- " [--more=<n> | --list | --independent | --merge-base]\n"
- " [--no-name | --sha1-name] [--topics] [(<rev> | <glob>)...]"),
+ " [--current] [--color[=<when>] | --no-color] [--sparse]\n"
+ " [--more=<n> | --list | --independent | --merge-base]\n"
+ " [--no-name | --sha1-name] [--topics] [(<rev> | <glob>)...]"),
N_("git show-branch (-g | --reflog)[=<n>[,<base>]] [--list] [<ref>]"),
NULL
};
@@ -482,10 +482,9 @@ static void snarf_refs(int head, int remotes)
}
}
-static int rev_is_head(const char *head, const char *name,
- unsigned char *head_sha1, unsigned char *sha1)
+static int rev_is_head(const char *head, const char *name)
{
- if (!head || (head_sha1 && sha1 && !hasheq(head_sha1, sha1)))
+ if (!head)
return 0;
skip_prefix(head, "refs/heads/", &head);
if (!skip_prefix(name, "refs/heads/", &name))
@@ -806,9 +805,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
/* We are only interested in adding the branch
* HEAD points at.
*/
- if (rev_is_head(head,
- ref_name[i],
- head_oid.hash, NULL))
+ if (rev_is_head(head, ref_name[i]))
has_head++;
}
if (!has_head) {
@@ -867,10 +864,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
if (1 < num_rev || extra < 0) {
for (i = 0; i < num_rev; i++) {
int j;
- int is_head = rev_is_head(head,
- ref_name[i],
- head_oid.hash,
- rev[i]->object.oid.hash);
+ int is_head = rev_is_head(head, ref_name[i]) &&
+ oideq(&head_oid, &rev[i]->object.oid);
if (extra < 0)
printf("%c [%s] ",
is_head ? '*' : ' ', ref_name[i]);
diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c
index 8ba9f13787..d0f5c4702b 100644
--- a/builtin/sparse-checkout.c
+++ b/builtin/sparse-checkout.c
@@ -100,6 +100,98 @@ static int sparse_checkout_list(int argc, const char **argv)
return 0;
}
+static void clean_tracked_sparse_directories(struct repository *r)
+{
+ int i, was_full = 0;
+ struct strbuf path = STRBUF_INIT;
+ size_t pathlen;
+ struct string_list_item *item;
+ struct string_list sparse_dirs = STRING_LIST_INIT_DUP;
+
+ /*
+ * If we are not using cone mode patterns, then we cannot
+ * delete directories outside of the sparse cone.
+ */
+ if (!r || !r->index || !r->worktree)
+ return;
+ if (init_sparse_checkout_patterns(r->index) ||
+ !r->index->sparse_checkout_patterns->use_cone_patterns)
+ return;
+
+ /*
+ * Use the sparse index as a data structure to assist finding
+ * directories that are safe to delete. This conversion to a
+ * sparse index will not delete directories that contain
+ * conflicted entries or submodules.
+ */
+ if (!r->index->sparse_index) {
+ /*
+ * If something, such as a merge conflict or other concern,
+ * prevents us from converting to a sparse index, then do
+ * not try deleting files.
+ */
+ if (convert_to_sparse(r->index, SPARSE_INDEX_MEMORY_ONLY))
+ return;
+ was_full = 1;
+ }
+
+ strbuf_addstr(&path, r->worktree);
+ strbuf_complete(&path, '/');
+ pathlen = path.len;
+
+ /*
+ * Collect directories that have gone out of scope but also
+ * exist on disk, so there is some work to be done. We need to
+ * store the entries in a list before exploring, since that might
+ * expand the sparse-index again.
+ */
+ for (i = 0; i < r->index->cache_nr; i++) {
+ struct cache_entry *ce = r->index->cache[i];
+
+ if (S_ISSPARSEDIR(ce->ce_mode) &&
+ repo_file_exists(r, ce->name))
+ string_list_append(&sparse_dirs, ce->name);
+ }
+
+ for_each_string_list_item(item, &sparse_dirs) {
+ struct dir_struct dir = DIR_INIT;
+ struct pathspec p = { 0 };
+ struct strvec s = STRVEC_INIT;
+
+ strbuf_setlen(&path, pathlen);
+ strbuf_addstr(&path, item->string);
+
+ dir.flags |= DIR_SHOW_IGNORED_TOO;
+
+ setup_standard_excludes(&dir);
+ strvec_push(&s, path.buf);
+
+ parse_pathspec(&p, PATHSPEC_GLOB, 0, NULL, s.v);
+ fill_directory(&dir, r->index, &p);
+
+ if (dir.nr) {
+ warning(_("directory '%s' contains untracked files,"
+ " but is not in the sparse-checkout cone"),
+ item->string);
+ } else if (remove_dir_recursively(&path, 0)) {
+ /*
+ * Removal is "best effort". If something blocks
+ * the deletion, then continue with a warning.
+ */
+ warning(_("failed to remove directory '%s'"),
+ item->string);
+ }
+
+ dir_clear(&dir);
+ }
+
+ string_list_clear(&sparse_dirs, 0);
+ strbuf_release(&path);
+
+ if (was_full)
+ ensure_full_index(r->index);
+}
+
static int update_working_directory(struct pattern_list *pl)
{
enum update_sparsity_result result;
@@ -141,6 +233,8 @@ static int update_working_directory(struct pattern_list *pl)
else
rollback_lock_file(&lock_file);
+ clean_tracked_sparse_directories(r);
+
r->index->sparse_checkout_patterns = NULL;
return result;
}
diff --git a/builtin/stash.c b/builtin/stash.c
index 8f42360ca9..a0ccc8654d 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -85,7 +85,7 @@ static const char * const git_stash_push_usage[] = {
static const char * const git_stash_save_usage[] = {
N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
- " [-u|--include-untracked] [-a|--all] [<message>]"),
+ " [-u|--include-untracked] [-a|--all] [<message>]"),
NULL
};
@@ -256,8 +256,10 @@ static int reset_tree(struct object_id *i_tree, int update, int reset)
opts.src_index = &the_index;
opts.dst_index = &the_index;
opts.merge = 1;
- opts.reset = reset;
+ opts.reset = reset ? UNPACK_RESET_PROTECT_UNTRACKED : 0;
opts.update = update;
+ if (update)
+ opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
opts.fn = oneway_merge;
if (unpack_trees(nr_trees, t, &opts))
@@ -313,6 +315,17 @@ static int reset_head(void)
return run_command(&cp);
}
+static int is_path_a_directory(const char *path)
+{
+ /*
+ * This function differs from abspath.c:is_directory() in that
+ * here we use lstat() instead of stat(); we do not want to
+ * follow symbolic links here.
+ */
+ struct stat st;
+ return (!lstat(path, &st) && S_ISDIR(st.st_mode));
+}
+
static void add_diff_to_buf(struct diff_queue_struct *q,
struct diff_options *options,
void *data)
@@ -320,6 +333,9 @@ static void add_diff_to_buf(struct diff_queue_struct *q,
int i;
for (i = 0; i < q->nr; i++) {
+ if (is_path_a_directory(q->queue[i]->one->path))
+ continue;
+
strbuf_addstr(data, q->queue[i]->one->path);
/* NUL-terminate: will be fed to update-index -z */
@@ -521,9 +537,6 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,
}
}
- if (info->has_u && restore_untracked(&info->u_tree))
- return error(_("could not restore untracked files from stash"));
-
init_merge_options(&o, the_repository);
o.branch1 = "Updated upstream";
@@ -558,6 +571,9 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,
unstage_changes_unless_new(&c_tree);
}
+ if (info->has_u && restore_untracked(&info->u_tree))
+ return error(_("could not restore untracked files from stash"));
+
if (!quiet) {
struct child_process cp = CHILD_PROCESS_INIT;
@@ -1519,6 +1535,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
} else {
struct child_process cp = CHILD_PROCESS_INIT;
cp.git_cmd = 1;
+ /* BUG: this nukes untracked files in the way */
strvec_pushl(&cp.args, "reset", "--hard", "-q",
"--no-recurse-submodules", NULL);
if (run_command(&cp)) {
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index ef2776a9e4..6298cbdd4e 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -199,34 +199,28 @@ static char *relative_url(const char *remote_url,
return strbuf_detach(&sb, NULL);
}
-static int resolve_relative_url(int argc, const char **argv, const char *prefix)
+static char *resolve_relative_url(const char *rel_url, const char *up_path, int quiet)
{
- char *remoteurl = NULL;
+ char *remoteurl, *resolved_url;
char *remote = get_default_remote();
- const char *up_path = NULL;
- char *res;
- const char *url;
- struct strbuf sb = STRBUF_INIT;
-
- if (argc != 2 && argc != 3)
- die("resolve-relative-url only accepts one or two arguments");
-
- url = argv[1];
- strbuf_addf(&sb, "remote.%s.url", remote);
- free(remote);
+ struct strbuf remotesb = STRBUF_INIT;
- if (git_config_get_string(sb.buf, &remoteurl))
- /* the repository is its own authoritative upstream */
+ strbuf_addf(&remotesb, "remote.%s.url", remote);
+ if (git_config_get_string(remotesb.buf, &remoteurl)) {
+ if (!quiet)
+ warning(_("could not look up configuration '%s'. "
+ "Assuming this repository is its own "
+ "authoritative upstream."),
+ remotesb.buf);
remoteurl = xgetcwd();
+ }
+ resolved_url = relative_url(remoteurl, rel_url, up_path);
- if (argc == 3)
- up_path = argv[2];
-
- res = relative_url(remoteurl, url, up_path);
- puts(res);
- free(res);
+ free(remote);
free(remoteurl);
- return 0;
+ strbuf_release(&remotesb);
+
+ return resolved_url;
}
static int resolve_relative_url_test(int argc, const char **argv, const char *prefix)
@@ -313,7 +307,7 @@ struct module_list {
const struct cache_entry **entries;
int alloc, nr;
};
-#define MODULE_LIST_INIT { NULL, 0, 0 }
+#define MODULE_LIST_INIT { 0 }
static int module_list_compute(int argc, const char **argv,
const char *prefix,
@@ -590,31 +584,11 @@ static int module_foreach(int argc, const char **argv, const char *prefix)
return 0;
}
-static char *compute_submodule_clone_url(const char *rel_url)
-{
- char *remoteurl, *relurl;
- char *remote = get_default_remote();
- struct strbuf remotesb = STRBUF_INIT;
-
- strbuf_addf(&remotesb, "remote.%s.url", remote);
- if (git_config_get_string(remotesb.buf, &remoteurl)) {
- warning(_("could not look up configuration '%s'. Assuming this repository is its own authoritative upstream."), remotesb.buf);
- remoteurl = xgetcwd();
- }
- relurl = relative_url(remoteurl, rel_url, NULL);
-
- free(remote);
- free(remoteurl);
- strbuf_release(&remotesb);
-
- return relurl;
-}
-
struct init_cb {
const char *prefix;
unsigned int flags;
};
-#define INIT_CB_INIT { NULL, 0 }
+#define INIT_CB_INIT { 0 }
static void init_submodule(const char *path, const char *prefix,
unsigned int flags)
@@ -660,7 +634,7 @@ static void init_submodule(const char *path, const char *prefix,
if (starts_with_dot_dot_slash(url) ||
starts_with_dot_slash(url)) {
char *oldurl = url;
- url = compute_submodule_clone_url(oldurl);
+ url = resolve_relative_url(oldurl, NULL, 0);
free(oldurl);
}
@@ -743,7 +717,7 @@ struct status_cb {
const char *prefix;
unsigned int flags;
};
-#define STATUS_CB_INIT { NULL, 0 }
+#define STATUS_CB_INIT { 0 }
static void print_status(unsigned int flags, char state, const char *path,
const struct object_id *oid, const char *displaypath)
@@ -937,13 +911,13 @@ struct module_cb {
char status;
const char *sm_path;
};
-#define MODULE_CB_INIT { 0, 0, NULL, NULL, '\0', NULL }
+#define MODULE_CB_INIT { 0 }
struct module_cb_list {
struct module_cb **entries;
int alloc, nr;
};
-#define MODULE_CB_LIST_INIT { NULL, 0, 0 }
+#define MODULE_CB_LIST_INIT { 0 }
struct summary_cb {
int argc;
@@ -954,7 +928,7 @@ struct summary_cb {
unsigned int files: 1;
int summary_limit;
};
-#define SUMMARY_CB_INIT { 0, NULL, NULL, 0, 0, 0, 0 }
+#define SUMMARY_CB_INIT { 0 }
enum diff_cmd {
DIFF_INDEX,
@@ -1360,7 +1334,7 @@ struct sync_cb {
const char *prefix;
unsigned int flags;
};
-#define SYNC_CB_INIT { NULL, 0 }
+#define SYNC_CB_INIT { 0 }
static void sync_submodule(const char *path, const char *prefix,
unsigned int flags)
@@ -1380,20 +1354,10 @@ static void sync_submodule(const char *path, const char *prefix,
if (sub && sub->url) {
if (starts_with_dot_dot_slash(sub->url) ||
starts_with_dot_slash(sub->url)) {
- char *remote_url, *up_path;
- char *remote = get_default_remote();
- strbuf_addf(&sb, "remote.%s.url", remote);
-
- if (git_config_get_string(sb.buf, &remote_url))
- remote_url = xgetcwd();
-
- up_path = get_up_path(path);
- sub_origin_url = relative_url(remote_url, sub->url, up_path);
- super_config_url = relative_url(remote_url, sub->url, NULL);
-
- free(remote);
+ char *up_path = get_up_path(path);
+ sub_origin_url = resolve_relative_url(sub->url, up_path, 1);
+ super_config_url = resolve_relative_url(sub->url, NULL, 1);
free(up_path);
- free(remote_url);
} else {
sub_origin_url = xstrdup(sub->url);
super_config_url = xstrdup(sub->url);
@@ -1516,7 +1480,7 @@ struct deinit_cb {
const char *prefix;
unsigned int flags;
};
-#define DEINIT_CB_INIT { NULL, 0 }
+#define DEINIT_CB_INIT { 0 }
static void deinit_submodule(const char *path, const char *prefix,
unsigned int flags)
@@ -1683,8 +1647,9 @@ struct submodule_alternate_setup {
} error_mode;
struct string_list *reference;
};
-#define SUBMODULE_ALTERNATE_SETUP_INIT { NULL, \
- SUBMODULE_ALTERNATE_ERROR_IGNORE, NULL }
+#define SUBMODULE_ALTERNATE_SETUP_INIT { \
+ .error_mode = SUBMODULE_ALTERNATE_ERROR_IGNORE, \
+}
static const char alternate_error_advice[] = N_(
"An alternate computed from a superproject's alternate is invalid.\n"
@@ -1704,18 +1669,24 @@ static int add_possible_reference_from_superproject(
* standard layout with .git/(modules/<name>)+/objects
*/
if (strip_suffix(odb->path, "/objects", &len)) {
+ struct repository alternate;
char *sm_alternate;
struct strbuf sb = STRBUF_INIT;
struct strbuf err = STRBUF_INIT;
strbuf_add(&sb, odb->path, len);
+ repo_init(&alternate, sb.buf, NULL);
+
/*
* We need to end the new path with '/' to mark it as a dir,
* otherwise a submodule name containing '/' will be broken
* as the last part of a missing submodule reference would
* be taken as a file name.
*/
- strbuf_addf(&sb, "/modules/%s/", sas->submodule_name);
+ strbuf_reset(&sb);
+ submodule_name_to_gitdir(&sb, &alternate, sas->submodule_name);
+ strbuf_addch(&sb, '/');
+ repo_clear(&alternate);
sm_alternate = compute_alternate_path(sb.buf, &err);
if (sm_alternate) {
@@ -1724,7 +1695,7 @@ static int add_possible_reference_from_superproject(
} else {
switch (sas->error_mode) {
case SUBMODULE_ALTERNATE_ERROR_DIE:
- if (advice_submodule_alternate_error_strategy_die)
+ if (advice_enabled(ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE))
advise(_(alternate_error_advice));
die(_("submodule '%s' cannot add alternate: %s"),
sas->submodule_name, err.buf);
@@ -1785,7 +1756,7 @@ static int clone_submodule(struct module_clone_data *clone_data)
struct strbuf sb = STRBUF_INIT;
struct child_process cp = CHILD_PROCESS_INIT;
- strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), clone_data->name);
+ submodule_name_to_gitdir(&sb, the_repository, clone_data->name);
sm_gitdir = absolute_pathdup(sb.buf);
strbuf_reset(&sb);
@@ -2045,6 +2016,20 @@ struct submodule_update_clone {
.max_jobs = 1, \
}
+struct update_data {
+ const char *recursive_prefix;
+ const char *sm_path;
+ const char *displaypath;
+ struct object_id oid;
+ struct object_id suboid;
+ struct submodule_update_strategy update_strategy;
+ int depth;
+ unsigned int force: 1;
+ unsigned int quiet: 1;
+ unsigned int nofetch: 1;
+ unsigned int just_cloned: 1;
+};
+#define UPDATE_DATA_INIT { .update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT }
static void next_submodule_warn_missing(struct submodule_update_clone *suc,
struct strbuf *out, const char *displaypath)
@@ -2134,7 +2119,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
if (repo_config_get_string_tmp(the_repository, sb.buf, &url)) {
if (starts_with_dot_slash(sub->url) ||
starts_with_dot_dot_slash(sub->url)) {
- url = compute_submodule_clone_url(sub->url);
+ url = resolve_relative_url(sub->url, NULL, 0);
need_free_url = 1;
} else
url = sub->url;
@@ -2298,6 +2283,181 @@ static int git_update_clone_config(const char *var, const char *value,
return 0;
}
+static int is_tip_reachable(const char *path, struct object_id *oid)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+ struct strbuf rev = STRBUF_INIT;
+ char *hex = oid_to_hex(oid);
+
+ cp.git_cmd = 1;
+ cp.dir = xstrdup(path);
+ cp.no_stderr = 1;
+ strvec_pushl(&cp.args, "rev-list", "-n", "1", hex, "--not", "--all", NULL);
+
+ prepare_submodule_repo_env(&cp.env_array);
+
+ if (capture_command(&cp, &rev, GIT_MAX_HEXSZ + 1) || rev.len)
+ return 0;
+
+ return 1;
+}
+
+static int fetch_in_submodule(const char *module_path, int depth, int quiet, struct object_id *oid)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ prepare_submodule_repo_env(&cp.env_array);
+ cp.git_cmd = 1;
+ cp.dir = xstrdup(module_path);
+
+ strvec_push(&cp.args, "fetch");
+ if (quiet)
+ strvec_push(&cp.args, "--quiet");
+ if (depth)
+ strvec_pushf(&cp.args, "--depth=%d", depth);
+ if (oid) {
+ char *hex = oid_to_hex(oid);
+ char *remote = get_default_remote();
+ strvec_pushl(&cp.args, remote, hex, NULL);
+ }
+
+ return run_command(&cp);
+}
+
+static int run_update_command(struct update_data *ud, int subforce)
+{
+ struct strvec args = STRVEC_INIT;
+ struct strvec child_env = STRVEC_INIT;
+ char *oid = oid_to_hex(&ud->oid);
+ int must_die_on_failure = 0;
+ int git_cmd;
+
+ switch (ud->update_strategy.type) {
+ case SM_UPDATE_CHECKOUT:
+ git_cmd = 1;
+ strvec_pushl(&args, "checkout", "-q", NULL);
+ if (subforce)
+ strvec_push(&args, "-f");
+ break;
+ case SM_UPDATE_REBASE:
+ git_cmd = 1;
+ strvec_push(&args, "rebase");
+ if (ud->quiet)
+ strvec_push(&args, "--quiet");
+ must_die_on_failure = 1;
+ break;
+ case SM_UPDATE_MERGE:
+ git_cmd = 1;
+ strvec_push(&args, "merge");
+ if (ud->quiet)
+ strvec_push(&args, "--quiet");
+ must_die_on_failure = 1;
+ break;
+ case SM_UPDATE_COMMAND:
+ git_cmd = 0;
+ strvec_push(&args, ud->update_strategy.command);
+ must_die_on_failure = 1;
+ break;
+ default:
+ BUG("unexpected update strategy type: %s",
+ submodule_strategy_to_string(&ud->update_strategy));
+ }
+ strvec_push(&args, oid);
+
+ prepare_submodule_repo_env(&child_env);
+ if (run_command_v_opt_cd_env(args.v, git_cmd ? RUN_GIT_CMD : RUN_USING_SHELL,
+ ud->sm_path, child_env.v)) {
+ switch (ud->update_strategy.type) {
+ case SM_UPDATE_CHECKOUT:
+ printf(_("Unable to checkout '%s' in submodule path '%s'"),
+ oid, ud->displaypath);
+ break;
+ case SM_UPDATE_REBASE:
+ printf(_("Unable to rebase '%s' in submodule path '%s'"),
+ oid, ud->displaypath);
+ break;
+ case SM_UPDATE_MERGE:
+ printf(_("Unable to merge '%s' in submodule path '%s'"),
+ oid, ud->displaypath);
+ break;
+ case SM_UPDATE_COMMAND:
+ printf(_("Execution of '%s %s' failed in submodule path '%s'"),
+ ud->update_strategy.command, oid, ud->displaypath);
+ break;
+ default:
+ BUG("unexpected update strategy type: %s",
+ submodule_strategy_to_string(&ud->update_strategy));
+ }
+ /*
+ * NEEDSWORK: We are currently printing to stdout with error
+ * return so that the shell caller handles the error output
+ * properly. Once we start handling the error messages within
+ * C, we should use die() instead.
+ */
+ if (must_die_on_failure)
+ return 2;
+ /*
+ * This signifies to the caller in shell that the command
+ * failed without dying
+ */
+ return 1;
+ }
+
+ switch (ud->update_strategy.type) {
+ case SM_UPDATE_CHECKOUT:
+ printf(_("Submodule path '%s': checked out '%s'\n"),
+ ud->displaypath, oid);
+ break;
+ case SM_UPDATE_REBASE:
+ printf(_("Submodule path '%s': rebased into '%s'\n"),
+ ud->displaypath, oid);
+ break;
+ case SM_UPDATE_MERGE:
+ printf(_("Submodule path '%s': merged in '%s'\n"),
+ ud->displaypath, oid);
+ break;
+ case SM_UPDATE_COMMAND:
+ printf(_("Submodule path '%s': '%s %s'\n"),
+ ud->displaypath, ud->update_strategy.command, oid);
+ break;
+ default:
+ BUG("unexpected update strategy type: %s",
+ submodule_strategy_to_string(&ud->update_strategy));
+ }
+
+ return 0;
+}
+
+static int do_run_update_procedure(struct update_data *ud)
+{
+ int subforce = is_null_oid(&ud->suboid) || ud->force;
+
+ if (!ud->nofetch) {
+ /*
+ * Run fetch only if `oid` isn't present or it
+ * is not reachable from a ref.
+ */
+ if (!is_tip_reachable(ud->sm_path, &ud->oid) &&
+ fetch_in_submodule(ud->sm_path, ud->depth, ud->quiet, NULL) &&
+ !ud->quiet)
+ fprintf_ln(stderr,
+ _("Unable to fetch in submodule path '%s'; "
+ "trying to directly fetch %s:"),
+ ud->displaypath, oid_to_hex(&ud->oid));
+ /*
+ * Now we tried the usual fetch, but `oid` may
+ * not be reachable from any of the refs.
+ */
+ if (!is_tip_reachable(ud->sm_path, &ud->oid) &&
+ fetch_in_submodule(ud->sm_path, ud->depth, ud->quiet, &ud->oid))
+ die(_("Fetched in submodule path '%s', but it did not "
+ "contain %s. Direct fetching of that commit failed."),
+ ud->displaypath, oid_to_hex(&ud->oid));
+ }
+
+ return run_update_command(ud, subforce);
+}
+
static void update_submodule(struct update_clone_data *ucd)
{
fprintf(stdout, "dummy %s %d\t%s\n",
@@ -2395,6 +2555,73 @@ static int update_clone(int argc, const char **argv, const char *prefix)
return update_submodules(&suc);
}
+static int run_update_procedure(int argc, const char **argv, const char *prefix)
+{
+ int force = 0, quiet = 0, nofetch = 0, just_cloned = 0;
+ char *prefixed_path, *update = NULL;
+ struct update_data update_data = UPDATE_DATA_INIT;
+
+ struct option options[] = {
+ OPT__QUIET(&quiet, N_("suppress output for update by rebase or merge")),
+ OPT__FORCE(&force, N_("force checkout updates"), 0),
+ OPT_BOOL('N', "no-fetch", &nofetch,
+ N_("don't fetch new objects from the remote site")),
+ OPT_BOOL(0, "just-cloned", &just_cloned,
+ N_("overrides update mode in case the repository is a fresh clone")),
+ OPT_INTEGER(0, "depth", &update_data.depth, N_("depth for shallow fetch")),
+ OPT_STRING(0, "prefix", &prefix,
+ N_("path"),
+ N_("path into the working tree")),
+ OPT_STRING(0, "update", &update,
+ N_("string"),
+ N_("rebase, merge, checkout or none")),
+ OPT_STRING(0, "recursive-prefix", &update_data.recursive_prefix, N_("path"),
+ N_("path into the working tree, across nested "
+ "submodule boundaries")),
+ OPT_CALLBACK_F(0, "oid", &update_data.oid, N_("sha1"),
+ N_("SHA1 expected by superproject"), PARSE_OPT_NONEG,
+ parse_opt_object_id),
+ OPT_CALLBACK_F(0, "suboid", &update_data.suboid, N_("subsha1"),
+ N_("SHA1 of submodule's HEAD"), PARSE_OPT_NONEG,
+ parse_opt_object_id),
+ OPT_END()
+ };
+
+ const char *const usage[] = {
+ N_("git submodule--helper run-update-procedure [<options>] <path>"),
+ NULL
+ };
+
+ argc = parse_options(argc, argv, prefix, options, usage, 0);
+
+ if (argc != 1)
+ usage_with_options(usage, options);
+
+ update_data.force = !!force;
+ update_data.quiet = !!quiet;
+ update_data.nofetch = !!nofetch;
+ update_data.just_cloned = !!just_cloned;
+ update_data.sm_path = argv[0];
+
+ if (update_data.recursive_prefix)
+ prefixed_path = xstrfmt("%s%s", update_data.recursive_prefix, update_data.sm_path);
+ else
+ prefixed_path = xstrdup(update_data.sm_path);
+
+ update_data.displaypath = get_submodule_displaypath(prefixed_path, prefix);
+
+ determine_submodule_update_strategy(the_repository, update_data.just_cloned,
+ update_data.sm_path, update,
+ &update_data.update_strategy);
+
+ free(prefixed_path);
+
+ if (!oideq(&update_data.oid, &update_data.suboid) || update_data.force)
+ return do_run_update_procedure(&update_data);
+
+ return 3;
+}
+
static int resolve_relative_path(int argc, const char **argv, const char *prefix)
{
struct strbuf sb = STRBUF_INIT;
@@ -2540,7 +2767,6 @@ static int push_check(int argc, const char **argv, const char *prefix)
static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
{
- const struct submodule *sub;
const char *path;
const char *cw;
struct repository subrepo;
@@ -2550,11 +2776,7 @@ static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
path = argv[1];
- sub = submodule_from_path(the_repository, null_oid(), path);
- if (!sub)
- BUG("We could get the submodule handle before?");
-
- if (repo_submodule_init(&subrepo, the_repository, sub))
+ if (repo_submodule_init(&subrepo, the_repository, path, null_oid()))
die(_("could not get a repository handle for submodule '%s'"), path);
if (!repo_config_get_string_tmp(&subrepo, "core.worktree", &cw)) {
@@ -2765,7 +2987,7 @@ struct add_data {
const char *prefix;
const char *branch;
const char *reference_path;
- const char *sm_path;
+ char *sm_path;
const char *sm_name;
const char *repo;
const char *realrepo;
@@ -2864,6 +3086,10 @@ static int add_submodule(const struct add_data *add_data)
prepare_submodule_repo_env(&cp.env_array);
cp.git_cmd = 1;
cp.dir = add_data->sm_path;
+ /*
+ * NOTE: we only get here if add_data->force is true, so
+ * passing --force to checkout is reasonable.
+ */
strvec_pushl(&cp.args, "checkout", "-f", "-q", NULL);
if (add_data->branch) {
@@ -2877,61 +3103,244 @@ static int add_submodule(const struct add_data *add_data)
return 0;
}
-static int add_clone(int argc, const char **argv, const char *prefix)
+static int config_submodule_in_gitmodules(const char *name, const char *var, const char *value)
+{
+ char *key;
+ int ret;
+
+ if (!is_writing_gitmodules_ok())
+ die(_("please make sure that the .gitmodules file is in the working tree"));
+
+ key = xstrfmt("submodule.%s.%s", name, var);
+ ret = config_set_in_gitmodules_file_gently(key, value);
+ free(key);
+
+ return ret;
+}
+
+static void configure_added_submodule(struct add_data *add_data)
+{
+ char *key;
+ char *val = NULL;
+ struct child_process add_submod = CHILD_PROCESS_INIT;
+ struct child_process add_gitmodules = CHILD_PROCESS_INIT;
+
+ key = xstrfmt("submodule.%s.url", add_data->sm_name);
+ git_config_set_gently(key, add_data->realrepo);
+ free(key);
+
+ add_submod.git_cmd = 1;
+ strvec_pushl(&add_submod.args, "add",
+ "--no-warn-embedded-repo", NULL);
+ if (add_data->force)
+ strvec_push(&add_submod.args, "--force");
+ strvec_pushl(&add_submod.args, "--", add_data->sm_path, NULL);
+
+ if (run_command(&add_submod))
+ die(_("Failed to add submodule '%s'"), add_data->sm_path);
+
+ if (config_submodule_in_gitmodules(add_data->sm_name, "path", add_data->sm_path) ||
+ config_submodule_in_gitmodules(add_data->sm_name, "url", add_data->repo))
+ die(_("Failed to register submodule '%s'"), add_data->sm_path);
+
+ if (add_data->branch) {
+ if (config_submodule_in_gitmodules(add_data->sm_name,
+ "branch", add_data->branch))
+ die(_("Failed to register submodule '%s'"), add_data->sm_path);
+ }
+
+ add_gitmodules.git_cmd = 1;
+ strvec_pushl(&add_gitmodules.args,
+ "add", "--force", "--", ".gitmodules", NULL);
+
+ if (run_command(&add_gitmodules))
+ die(_("Failed to register submodule '%s'"), add_data->sm_path);
+
+ /*
+ * NEEDSWORK: In a multi-working-tree world this needs to be
+ * set in the per-worktree config.
+ */
+ /*
+ * NEEDSWORK: In the longer run, we need to get rid of this
+ * pattern of querying "submodule.active" before calling
+ * is_submodule_active(), since that function needs to find
+ * out the value of "submodule.active" again anyway.
+ */
+ if (!git_config_get_string("submodule.active", &val) && val) {
+ /*
+ * If the submodule being added isn't already covered by the
+ * current configured pathspec, set the submodule's active flag
+ */
+ if (!is_submodule_active(the_repository, add_data->sm_path)) {
+ key = xstrfmt("submodule.%s.active", add_data->sm_name);
+ git_config_set_gently(key, "true");
+ free(key);
+ }
+ } else {
+ key = xstrfmt("submodule.%s.active", add_data->sm_name);
+ git_config_set_gently(key, "true");
+ free(key);
+ }
+}
+
+static void die_on_index_match(const char *path, int force)
+{
+ struct pathspec ps;
+ const char *args[] = { path, NULL };
+ parse_pathspec(&ps, 0, PATHSPEC_PREFER_CWD, NULL, args);
+
+ if (read_cache_preload(NULL) < 0)
+ die(_("index file corrupt"));
+
+ if (ps.nr) {
+ int i;
+ char *ps_matched = xcalloc(ps.nr, 1);
+
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(&the_index);
+
+ /*
+ * Since there is only one pathspec, we just need
+ * need to check ps_matched[0] to know if a cache
+ * entry matched.
+ */
+ for (i = 0; i < active_nr; i++) {
+ ce_path_match(&the_index, active_cache[i], &ps,
+ ps_matched);
+
+ if (ps_matched[0]) {
+ if (!force)
+ die(_("'%s' already exists in the index"),
+ path);
+ if (!S_ISGITLINK(active_cache[i]->ce_mode))
+ die(_("'%s' already exists in the index "
+ "and is not a submodule"), path);
+ break;
+ }
+ }
+ free(ps_matched);
+ }
+}
+
+static void die_on_repo_without_commits(const char *path)
{
- int force = 0, quiet = 0, dissociate = 0, progress = 0;
+ struct strbuf sb = STRBUF_INIT;
+ strbuf_addstr(&sb, path);
+ if (is_nonbare_repository_dir(&sb)) {
+ struct object_id oid;
+ if (resolve_gitlink_ref(path, "HEAD", &oid) < 0)
+ die(_("'%s' does not have a commit checked out"), path);
+ }
+}
+
+static int module_add(int argc, const char **argv, const char *prefix)
+{
+ int force = 0, quiet = 0, progress = 0, dissociate = 0;
struct add_data add_data = ADD_DATA_INIT;
struct option options[] = {
- OPT_STRING('b', "branch", &add_data.branch,
- N_("branch"),
- N_("branch of repository to checkout on cloning")),
- OPT_STRING(0, "prefix", &prefix,
- N_("path"),
- N_("alternative anchor for relative paths")),
- OPT_STRING(0, "path", &add_data.sm_path,
- N_("path"),
- N_("where the new submodule will be cloned to")),
- OPT_STRING(0, "name", &add_data.sm_name,
- N_("string"),
- N_("name of the new submodule")),
- OPT_STRING(0, "url", &add_data.realrepo,
- N_("string"),
- N_("url where to clone the submodule from")),
- OPT_STRING(0, "reference", &add_data.reference_path,
- N_("repo"),
- N_("reference repository")),
- OPT_BOOL(0, "dissociate", &dissociate,
- N_("use --reference only while cloning")),
- OPT_INTEGER(0, "depth", &add_data.depth,
- N_("depth for shallow clones")),
- OPT_BOOL(0, "progress", &progress,
- N_("force cloning progress")),
+ OPT_STRING('b', "branch", &add_data.branch, N_("branch"),
+ N_("branch of repository to add as submodule")),
OPT__FORCE(&force, N_("allow adding an otherwise ignored submodule path"),
PARSE_OPT_NOCOMPLETE),
- OPT__QUIET(&quiet, "suppress output for cloning a submodule"),
+ OPT__QUIET(&quiet, N_("print only error messages")),
+ OPT_BOOL(0, "progress", &progress, N_("force cloning progress")),
+ OPT_STRING(0, "reference", &add_data.reference_path, N_("repository"),
+ N_("reference repository")),
+ OPT_BOOL(0, "dissociate", &dissociate, N_("borrow the objects from reference repositories")),
+ OPT_STRING(0, "name", &add_data.sm_name, N_("name"),
+ N_("sets the submodule’s name to the given string "
+ "instead of defaulting to its path")),
+ OPT_INTEGER(0, "depth", &add_data.depth, N_("depth for shallow clones")),
OPT_END()
};
const char *const usage[] = {
- N_("git submodule--helper add-clone [<options>...] "
- "--url <url> --path <path> --name <name>"),
+ N_("git submodule--helper add [<options>] [--] <repository> [<path>]"),
NULL
};
argc = parse_options(argc, argv, prefix, options, usage, 0);
- if (argc != 0)
+ if (!is_writing_gitmodules_ok())
+ die(_("please make sure that the .gitmodules file is in the working tree"));
+
+ if (prefix && *prefix &&
+ add_data.reference_path && !is_absolute_path(add_data.reference_path))
+ add_data.reference_path = xstrfmt("%s%s", prefix, add_data.reference_path);
+
+ if (argc == 0 || argc > 2)
usage_with_options(usage, options);
+ add_data.repo = argv[0];
+ if (argc == 1)
+ add_data.sm_path = git_url_basename(add_data.repo, 0, 0);
+ else
+ add_data.sm_path = xstrdup(argv[1]);
+
+ if (prefix && *prefix && !is_absolute_path(add_data.sm_path))
+ add_data.sm_path = xstrfmt("%s%s", prefix, add_data.sm_path);
+
+ if (starts_with_dot_dot_slash(add_data.repo) ||
+ starts_with_dot_slash(add_data.repo)) {
+ if (prefix)
+ die(_("Relative path can only be used from the toplevel "
+ "of the working tree"));
+
+ /* dereference source url relative to parent's url */
+ add_data.realrepo = resolve_relative_url(add_data.repo, NULL, 1);
+ } else if (is_dir_sep(add_data.repo[0]) || strchr(add_data.repo, ':')) {
+ add_data.realrepo = add_data.repo;
+ } else {
+ die(_("repo URL: '%s' must be absolute or begin with ./|../"),
+ add_data.repo);
+ }
+
+ /*
+ * normalize path:
+ * multiple //; leading ./; /./; /../;
+ */
+ normalize_path_copy(add_data.sm_path, add_data.sm_path);
+ strip_dir_trailing_slashes(add_data.sm_path);
+
+ die_on_index_match(add_data.sm_path, force);
+ die_on_repo_without_commits(add_data.sm_path);
+
+ if (!force) {
+ int exit_code = -1;
+ struct strbuf sb = STRBUF_INIT;
+ struct child_process cp = CHILD_PROCESS_INIT;
+ cp.git_cmd = 1;
+ cp.no_stdout = 1;
+ strvec_pushl(&cp.args, "add", "--dry-run", "--ignore-missing",
+ "--no-warn-embedded-repo", add_data.sm_path, NULL);
+ if ((exit_code = pipe_command(&cp, NULL, 0, NULL, 0, &sb, 0))) {
+ strbuf_complete_line(&sb);
+ fputs(sb.buf, stderr);
+ free(add_data.sm_path);
+ return exit_code;
+ }
+ strbuf_release(&sb);
+ }
+
+ if(!add_data.sm_name)
+ add_data.sm_name = add_data.sm_path;
+
+ if (check_submodule_name(add_data.sm_name))
+ die(_("'%s' is not a valid submodule name"), add_data.sm_name);
+
add_data.prefix = prefix;
- add_data.progress = !!progress;
- add_data.dissociate = !!dissociate;
add_data.force = !!force;
add_data.quiet = !!quiet;
+ add_data.progress = !!progress;
+ add_data.dissociate = !!dissociate;
- if (add_submodule(&add_data))
+ if (add_submodule(&add_data)) {
+ free(add_data.sm_path);
return 1;
+ }
+ configure_added_submodule(&add_data);
+ free(add_data.sm_path);
return 0;
}
@@ -2948,12 +3357,12 @@ static struct cmd_struct commands[] = {
{"list", module_list, 0},
{"name", module_name, 0},
{"clone", module_clone, 0},
- {"add-clone", add_clone, 0},
+ {"add", module_add, SUPPORT_SUPER_PREFIX},
{"update-module-mode", module_update_module_mode, 0},
{"update-clone", update_clone, 0},
+ {"run-update-procedure", run_update_procedure, 0},
{"ensure-core-worktree", ensure_core_worktree, 0},
{"relative-path", resolve_relative_path, 0},
- {"resolve-relative-url", resolve_relative_url, 0},
{"resolve-relative-url-test", resolve_relative_url_test, 0},
{"foreach", module_foreach, SUPPORT_SUPER_PREFIX},
{"init", module_init, SUPPORT_SUPER_PREFIX},
diff --git a/builtin/tag.c b/builtin/tag.c
index 82fcfc0982..6535ed27ee 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -23,10 +23,10 @@
static const char * const git_tag_usage[] = {
N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\n"
- "\t\t<tagname> [<head>]"),
+ " <tagname> [<head>]"),
N_("git tag -d <tagname>..."),
N_("git tag -l [-n[<num>]] [--contains <commit>] [--no-contains <commit>] [--points-at <object>]\n"
- "\t\t[--format=<format>] [--merged <commit>] [--no-merged <commit>] [<pattern>...]"),
+ " [--format=<format>] [--merged <commit>] [--no-merged <commit>] [<pattern>...]"),
N_("git tag -v [--format=<format>] <tagname>..."),
NULL
};
@@ -146,7 +146,7 @@ static int verify_tag(const char *name, const char *ref,
const struct object_id *oid, void *cb_data)
{
int flags;
- const struct ref_format *format = cb_data;
+ struct ref_format *format = cb_data;
flags = GPG_VERIFY_VERBOSE;
if (format->format)
@@ -293,9 +293,7 @@ static void create_tag(const struct object_id *object, const char *object_ref,
/* write the template message before editing: */
path = git_pathdup("TAG_EDITMSG");
- fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
- if (fd < 0)
- die_errno(_("could not create file '%s'"), path);
+ fd = xopen(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
if (opt->message_given) {
write_or_die(fd, buf->buf, buf->len);
diff --git a/builtin/update-index.c b/builtin/update-index.c
index f1f16f2de5..187203e8bb 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -95,9 +95,7 @@ static int create_file(const char *path)
{
int fd;
path = get_mtime_path(path);
- fd = open(path, O_CREAT | O_RDWR, 0644);
- if (fd < 0)
- die_errno(_("failed to create file %s"), path);
+ fd = xopen(path, O_CREAT | O_RDWR, 0644);
return fd;
}
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 6029a80544..a84e7b47a2 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -302,6 +302,12 @@ static void parse_cmd_verify(struct ref_transaction *transaction,
strbuf_release(&err);
}
+static void report_ok(const char *command)
+{
+ fprintf(stdout, "%s: ok\n", command);
+ fflush(stdout);
+}
+
static void parse_cmd_option(struct ref_transaction *transaction,
const char *next, const char *end)
{
@@ -317,7 +323,7 @@ static void parse_cmd_start(struct ref_transaction *transaction,
{
if (*next != line_termination)
die("start: extra input: %s", next);
- puts("start: ok");
+ report_ok("start");
}
static void parse_cmd_prepare(struct ref_transaction *transaction,
@@ -328,7 +334,7 @@ static void parse_cmd_prepare(struct ref_transaction *transaction,
die("prepare: extra input: %s", next);
if (ref_transaction_prepare(transaction, &error))
die("prepare: %s", error.buf);
- puts("prepare: ok");
+ report_ok("prepare");
}
static void parse_cmd_abort(struct ref_transaction *transaction,
@@ -339,7 +345,7 @@ static void parse_cmd_abort(struct ref_transaction *transaction,
die("abort: extra input: %s", next);
if (ref_transaction_abort(transaction, &error))
die("abort: %s", error.buf);
- puts("abort: ok");
+ report_ok("abort");
}
static void parse_cmd_commit(struct ref_transaction *transaction,
@@ -350,7 +356,7 @@ static void parse_cmd_commit(struct ref_transaction *transaction,
die("commit: extra input: %s", next);
if (ref_transaction_commit(transaction, &error))
die("commit: %s", error.buf);
- puts("commit: ok");
+ report_ok("commit");
ref_transaction_free(transaction);
}
diff --git a/builtin/upload-pack.c b/builtin/upload-pack.c
index 6da8fa2607..125af53885 100644
--- a/builtin/upload-pack.c
+++ b/builtin/upload-pack.c
@@ -16,16 +16,18 @@ int cmd_upload_pack(int argc, const char **argv, const char *prefix)
{
const char *dir;
int strict = 0;
- struct upload_pack_options opts = { 0 };
- struct serve_options serve_opts = SERVE_OPTIONS_INIT;
+ int advertise_refs = 0;
+ int stateless_rpc = 0;
+ int timeout = 0;
struct option options[] = {
- OPT_BOOL(0, "stateless-rpc", &opts.stateless_rpc,
+ OPT_BOOL(0, "stateless-rpc", &stateless_rpc,
N_("quit after a single request/response exchange")),
- OPT_BOOL(0, "advertise-refs", &opts.advertise_refs,
- N_("exit immediately after initial ref advertisement")),
+ OPT_HIDDEN_BOOL(0, "http-backend-info-refs", &advertise_refs,
+ N_("serve up the info/refs for git-http-backend")),
+ OPT_ALIAS(0, "advertise-refs", "http-backend-info-refs"),
OPT_BOOL(0, "strict", &strict,
N_("do not try <directory>/.git/ if <directory> is no Git directory")),
- OPT_INTEGER(0, "timeout", &opts.timeout,
+ OPT_INTEGER(0, "timeout", &timeout,
N_("interrupt transfer after <n> seconds of inactivity")),
OPT_END()
};
@@ -38,9 +40,6 @@ int cmd_upload_pack(int argc, const char **argv, const char *prefix)
if (argc != 1)
usage_with_options(upload_pack_usage, options);
- if (opts.timeout)
- opts.daemon_mode = 1;
-
setup_path();
dir = argv[0];
@@ -50,21 +49,22 @@ int cmd_upload_pack(int argc, const char **argv, const char *prefix)
switch (determine_protocol_version_server()) {
case protocol_v2:
- serve_opts.advertise_capabilities = opts.advertise_refs;
- serve_opts.stateless_rpc = opts.stateless_rpc;
- serve(&serve_opts);
+ if (advertise_refs)
+ protocol_v2_advertise_capabilities();
+ else
+ protocol_v2_serve_loop(stateless_rpc);
break;
case protocol_v1:
/*
* v1 is just the original protocol with a version string,
* so just fall through after writing the version string.
*/
- if (opts.advertise_refs || !opts.stateless_rpc)
+ if (advertise_refs || !stateless_rpc)
packet_write_fmt(1, "version 1\n");
/* fallthrough */
case protocol_v0:
- upload_pack(&opts);
+ upload_pack(advertise_refs, stateless_rpc, timeout);
break;
case protocol_unknown_version:
BUG("unknown protocol version");
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 0d0a80da61..d22ece93e1 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -8,6 +8,7 @@
#include "branch.h"
#include "refs.h"
#include "run-command.h"
+#include "hook.h"
#include "sigchain.h"
#include "submodule.h"
#include "utf8.h"
diff --git a/bulk-checkin.c b/bulk-checkin.c
index b023d9959a..8785b2ac80 100644
--- a/bulk-checkin.c
+++ b/bulk-checkin.c
@@ -23,9 +23,25 @@ static struct bulk_checkin_state {
uint32_t nr_written;
} state;
+static void finish_tmp_packfile(struct strbuf *basename,
+ const char *pack_tmp_name,
+ struct pack_idx_entry **written_list,
+ uint32_t nr_written,
+ struct pack_idx_option *pack_idx_opts,
+ unsigned char hash[])
+{
+ char *idx_tmp_name = NULL;
+
+ stage_tmp_packfiles(basename, pack_tmp_name, written_list, nr_written,
+ pack_idx_opts, hash, &idx_tmp_name);
+ rename_tmp_packfile_idx(basename, &idx_tmp_name);
+
+ free(idx_tmp_name);
+}
+
static void finish_bulk_checkin(struct bulk_checkin_state *state)
{
- struct object_id oid;
+ unsigned char hash[GIT_MAX_RAWSZ];
struct strbuf packname = STRBUF_INIT;
int i;
@@ -37,19 +53,20 @@ static void finish_bulk_checkin(struct bulk_checkin_state *state)
unlink(state->pack_tmp_name);
goto clear_exit;
} else if (state->nr_written == 1) {
- finalize_hashfile(state->f, oid.hash, CSUM_HASH_IN_STREAM | CSUM_FSYNC | CSUM_CLOSE);
+ finalize_hashfile(state->f, hash, CSUM_HASH_IN_STREAM | CSUM_FSYNC | CSUM_CLOSE);
} else {
- int fd = finalize_hashfile(state->f, oid.hash, 0);
- fixup_pack_header_footer(fd, oid.hash, state->pack_tmp_name,
- state->nr_written, oid.hash,
+ int fd = finalize_hashfile(state->f, hash, 0);
+ fixup_pack_header_footer(fd, hash, state->pack_tmp_name,
+ state->nr_written, hash,
state->offset);
close(fd);
}
- strbuf_addf(&packname, "%s/pack/pack-", get_object_directory());
+ strbuf_addf(&packname, "%s/pack/pack-%s.", get_object_directory(),
+ hash_to_hex(hash));
finish_tmp_packfile(&packname, state->pack_tmp_name,
state->written, state->nr_written,
- &state->pack_idx_opts, oid.hash);
+ &state->pack_idx_opts, hash);
for (i = 0; i < state->nr_written; i++)
free(state->written[i]);
diff --git a/bundle.c b/bundle.c
index ab63f40226..a0bb687b0f 100644
--- a/bundle.c
+++ b/bundle.c
@@ -569,18 +569,18 @@ err:
}
int unbundle(struct repository *r, struct bundle_header *header,
- int bundle_fd, int flags)
+ int bundle_fd, struct strvec *extra_index_pack_args)
{
- const char *argv_index_pack[] = {"index-pack",
- "--fix-thin", "--stdin", NULL, NULL};
struct child_process ip = CHILD_PROCESS_INIT;
+ strvec_pushl(&ip.args, "index-pack", "--fix-thin", "--stdin", NULL);
- if (flags & BUNDLE_VERBOSE)
- argv_index_pack[3] = "-v";
+ if (extra_index_pack_args) {
+ strvec_pushv(&ip.args, extra_index_pack_args->v);
+ strvec_clear(extra_index_pack_args);
+ }
if (verify_bundle(r, header, 0))
return -1;
- ip.argv = argv_index_pack;
ip.in = bundle_fd;
ip.no_stdout = 1;
ip.git_cmd = 1;
diff --git a/bundle.h b/bundle.h
index 1927d8cd6a..06009fe6b1 100644
--- a/bundle.h
+++ b/bundle.h
@@ -26,9 +26,19 @@ int create_bundle(struct repository *r, const char *path,
int argc, const char **argv, struct strvec *pack_options,
int version);
int verify_bundle(struct repository *r, struct bundle_header *header, int verbose);
-#define BUNDLE_VERBOSE 1
+
+/**
+ * Unbundle after reading the header with read_bundle_header().
+ *
+ * We'll invoke "git index-pack --stdin --fix-thin" for you on the
+ * provided `bundle_fd` from read_bundle_header().
+ *
+ * Provide "extra_index_pack_args" to pass any extra arguments
+ * (e.g. "-v" for verbose/progress), NULL otherwise. The provided
+ * "extra_index_pack_args" (if any) will be strvec_clear()'d for you.
+ */
int unbundle(struct repository *r, struct bundle_header *header,
- int bundle_fd, int flags);
+ int bundle_fd, struct strvec *extra_index_pack_args);
int list_bundle_refs(struct bundle_header *header,
int argc, const char **argv);
diff --git a/cache.h b/cache.h
index bd4869beee..d092820c94 100644
--- a/cache.h
+++ b/cache.h
@@ -958,7 +958,6 @@ extern char *apply_default_ignorewhitespace;
extern const char *git_attributes_file;
extern const char *git_hooks_path;
extern int zlib_compression_level;
-extern int core_compression_level;
extern int pack_compression_level;
extern size_t packed_git_window_size;
extern size_t packed_git_limit;
@@ -996,14 +995,6 @@ extern int core_apply_sparse_checkout;
extern int core_sparse_checkout_cone;
/*
- * Include broken refs in all ref iterations, which will
- * generally choke dangerous operations rather than letting
- * them silently proceed without taking the broken ref into
- * account.
- */
-extern int ref_paranoia;
-
-/*
* Returns the boolean value of $GIT_OPTIONAL_LOCKS (or the default value).
*/
int use_optional_locks(void);
@@ -1211,51 +1202,10 @@ enum scld_error safe_create_leading_directories(char *path);
enum scld_error safe_create_leading_directories_const(const char *path);
enum scld_error safe_create_leading_directories_no_share(char *path);
-/*
- * Callback function for raceproof_create_file(). This function is
- * expected to do something that makes dirname(path) permanent despite
- * the fact that other processes might be cleaning up empty
- * directories at the same time. Usually it will create a file named
- * path, but alternatively it could create another file in that
- * directory, or even chdir() into that directory. The function should
- * return 0 if the action was completed successfully. On error, it
- * should return a nonzero result and set errno.
- * raceproof_create_file() treats two errno values specially:
- *
- * - ENOENT -- dirname(path) does not exist. In this case,
- * raceproof_create_file() tries creating dirname(path)
- * (and any parent directories, if necessary) and calls
- * the function again.
- *
- * - EISDIR -- the file already exists and is a directory. In this
- * case, raceproof_create_file() removes the directory if
- * it is empty (and recursively any empty directories that
- * it contains) and calls the function again.
- *
- * Any other errno causes raceproof_create_file() to fail with the
- * callback's return value and errno.
- *
- * Obviously, this function should be OK with being called again if it
- * fails with ENOENT or EISDIR. In other scenarios it will not be
- * called again.
- */
-typedef int create_file_fn(const char *path, void *cb);
-
-/*
- * Create a file in dirname(path) by calling fn, creating leading
- * directories if necessary. Retry a few times in case we are racing
- * with another process that is trying to clean up the directory that
- * contains path. See the documentation for create_file_fn for more
- * details.
- *
- * Return the value and set the errno that resulted from the most
- * recent call of fn. fn is always called at least once, and will be
- * called more than once if it returns ENOENT or EISDIR.
- */
-int raceproof_create_file(const char *path, create_file_fn fn, void *cb);
-
int mkdir_in_gitdir(const char *path);
-char *expand_user_path(const char *path, int real_home);
+char *interpolate_path(const char *path, int real_home);
+/* NEEDSWORK: remove this synonym once in-flight topics have migrated */
+#define expand_user_path interpolate_path
const char *enter_repo(const char *path, int strict);
static inline int is_absolute_path(const char *path)
{
@@ -1297,6 +1247,13 @@ int looks_like_command_line_option(const char *str);
/**
* Return a newly allocated string with the evaluation of
+ * "$XDG_CONFIG_HOME/$subdir/$filename" if $XDG_CONFIG_HOME is non-empty, otherwise
+ * "$HOME/.config/$subdir/$filename". Return NULL upon error.
+ */
+char *xdg_config_home_for(const char *subdir, const char *filename);
+
+/**
+ * Return a newly allocated string with the evaluation of
* "$XDG_CONFIG_HOME/git/$filename" if $XDG_CONFIG_HOME is non-empty, otherwise
* "$HOME/.config/git/$filename". Return NULL upon error.
*/
@@ -1660,7 +1617,9 @@ struct cache_def {
int track_flags;
int prefix_len_stat_func;
};
-#define CACHE_DEF_INIT { STRBUF_INIT, 0, 0, 0 }
+#define CACHE_DEF_INIT { \
+ .path = STRBUF_INIT, \
+}
static inline void cache_def_clear(struct cache_def *cache)
{
strbuf_release(&cache->path);
@@ -1717,13 +1676,6 @@ int update_server_info(int);
const char *get_log_output_encoding(void);
const char *get_commit_output_encoding(void);
-/*
- * This is a hack for test programs like test-dump-untracked-cache to
- * ensure that they do not modify the untracked cache when reading it.
- * Do not use it otherwise!
- */
-extern int ignore_untracked_cache_config;
-
int committer_ident_sufficiently_given(void);
int author_ident_sufficiently_given(void);
@@ -1736,6 +1688,8 @@ extern const char *git_mailmap_blob;
void maybe_flush_or_die(FILE *, const char *);
__attribute__((format (printf, 2, 3)))
void fprintf_or_die(FILE *, const char *fmt, ...);
+void fwrite_or_die(FILE *f, const void *buf, size_t count);
+void fflush_or_die(FILE *f);
#define COPY_READ_ERROR (-2)
#define COPY_WRITE_ERROR (-3)
diff --git a/cbtree.h b/cbtree.h
index a04a312c3f..dedbb8e2a4 100644
--- a/cbtree.h
+++ b/cbtree.h
@@ -37,11 +37,12 @@ enum cb_next {
CB_BREAK = 1
};
-#define CBTREE_INIT { .root = NULL }
+#define CBTREE_INIT { 0 }
static inline void cb_init(struct cb_tree *t)
{
- t->root = NULL;
+ struct cb_tree blank = CBTREE_INIT;
+ memcpy(t, &blank, sizeof(*t));
}
struct cb_node *cb_lookup(struct cb_tree *, const uint8_t *k, size_t klen);
diff --git a/check_bindir b/check_bindir
deleted file mode 100755
index 623eadcbb7..0000000000
--- a/check_bindir
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/sh
-bindir="$1"
-gitexecdir="$2"
-gitcmd="$3"
-if test "$bindir" != "$gitexecdir" && test -x "$gitcmd"
-then
- echo
- echo "!! You have installed git-* commands to new gitexecdir."
- echo "!! Old version git-* commands still remain in bindir."
- echo "!! Mixing two versions of Git will lead to problems."
- echo "!! Please remove old version commands in bindir now."
- echo
-fi
diff --git a/checkout.c b/checkout.c
index 6586e30ca5..2e39dae684 100644
--- a/checkout.c
+++ b/checkout.c
@@ -14,7 +14,7 @@ struct tracking_name_data {
struct object_id *default_dst_oid;
};
-#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 0, NULL, NULL, NULL }
+#define TRACKING_NAME_DATA_INIT { 0 }
static int check_tracking_name(struct remote *remote, void *cb_data)
{
diff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh
index 5772081b6e..1d0e48f451 100755
--- a/ci/install-dependencies.sh
+++ b/ci/install-dependencies.sh
@@ -12,7 +12,7 @@ UBUNTU_COMMON_PKGS="make libssl-dev libcurl4-openssl-dev libexpat-dev
libemail-valid-perl libio-socket-ssl-perl libnet-smtp-ssl-perl"
case "$jobname" in
-linux-clang|linux-gcc)
+linux-clang|linux-gcc|linux-leaks)
sudo apt-add-repository -y "ppa:ubuntu-toolchain-r/test"
sudo apt-get -q update
sudo apt-get -q -y install language-pack-is libsvn-perl apache2 \
diff --git a/ci/install-docker-dependencies.sh b/ci/install-docker-dependencies.sh
index 26a6689766..07a8c6b199 100755
--- a/ci/install-docker-dependencies.sh
+++ b/ci/install-docker-dependencies.sh
@@ -15,4 +15,8 @@ linux-musl)
apk add --update build-base curl-dev openssl-dev expat-dev gettext \
pcre2-dev python3 musl-libintl perl-utils ncurses >/dev/null
;;
+pedantic)
+ dnf -yq update >/dev/null &&
+ dnf -yq install make gcc findutils diffutils perl python3 gettext zlib-devel expat-devel openssl-devel curl-devel pcre2-devel >/dev/null
+ ;;
esac
diff --git a/ci/lib.sh b/ci/lib.sh
index 476c3f369f..82cb17f8ee 100755
--- a/ci/lib.sh
+++ b/ci/lib.sh
@@ -183,7 +183,7 @@ export GIT_TEST_CLONE_2GB=true
export SKIP_DASHED_BUILT_INS=YesPlease
case "$jobname" in
-linux-clang|linux-gcc)
+linux-clang|linux-gcc|linux-leaks)
if [ "$jobname" = linux-gcc ]
then
export CC=gcc-8
@@ -233,4 +233,11 @@ linux-musl)
;;
esac
+case "$jobname" in
+linux-leaks)
+ export SANITIZE=leak
+ export GIT_TEST_PASSING_SANITIZE_LEAK=true
+ ;;
+esac
+
MAKEFLAGS="$MAKEFLAGS CC=${CC:-cc}"
diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh
index 7ee9ba9325..cc62616d80 100755
--- a/ci/run-build-and-tests.sh
+++ b/ci/run-build-and-tests.sh
@@ -10,6 +10,11 @@ windows*) cmd //c mklink //j t\\.prove "$(cygpath -aw "$cache_dir/.prove")";;
*) ln -s "$cache_dir/.prove" t/.prove;;
esac
+if test "$jobname" = "pedantic"
+then
+ export DEVOPTS=pedantic
+fi
+
make
case "$jobname" in
linux-gcc)
@@ -36,10 +41,9 @@ linux-clang)
export GIT_TEST_DEFAULT_HASH=sha256
make test
;;
-linux-gcc-4.8)
+linux-gcc-4.8|pedantic)
# Don't run the tests; we only care about whether Git can be
- # built with GCC 4.8, as it errors out on some undesired (C99)
- # constructs that newer compilers seem to quietly accept.
+ # built with GCC 4.8 or with pedantic
;;
*)
make test
diff --git a/commit-graph.c b/commit-graph.c
index 3860a0d847..2706683acf 100644
--- a/commit-graph.c
+++ b/commit-graph.c
@@ -713,6 +713,7 @@ static void close_commit_graph_one(struct commit_graph *g)
if (!g)
return;
+ clear_commit_graph_data_slab(&commit_graph_data_slab);
close_commit_graph_one(g->base_graph);
free_commit_graph(g);
}
@@ -723,7 +724,7 @@ void close_commit_graph(struct raw_object_store *o)
o->commit_graph = NULL;
}
-static int bsearch_graph(struct commit_graph *g, struct object_id *oid, uint32_t *pos)
+static int bsearch_graph(struct commit_graph *g, const struct object_id *oid, uint32_t *pos)
{
return bsearch_hash(oid->hash, g->chunk_oid_fanout,
g->chunk_oid_lookup, g->hash_len, pos);
@@ -864,26 +865,55 @@ static int fill_commit_in_graph(struct repository *r,
return 1;
}
-static int find_commit_in_graph(struct commit *item, struct commit_graph *g, uint32_t *pos)
+static int search_commit_pos_in_graph(const struct object_id *id, struct commit_graph *g, uint32_t *pos)
+{
+ struct commit_graph *cur_g = g;
+ uint32_t lex_index;
+
+ while (cur_g && !bsearch_graph(cur_g, id, &lex_index))
+ cur_g = cur_g->base_graph;
+
+ if (cur_g) {
+ *pos = lex_index + cur_g->num_commits_in_base;
+ return 1;
+ }
+
+ return 0;
+}
+
+static int find_commit_pos_in_graph(struct commit *item, struct commit_graph *g, uint32_t *pos)
{
uint32_t graph_pos = commit_graph_position(item);
if (graph_pos != COMMIT_NOT_FROM_GRAPH) {
*pos = graph_pos;
return 1;
} else {
- struct commit_graph *cur_g = g;
- uint32_t lex_index;
+ return search_commit_pos_in_graph(&item->object.oid, g, pos);
+ }
+}
+
+struct commit *lookup_commit_in_graph(struct repository *repo, const struct object_id *id)
+{
+ struct commit *commit;
+ uint32_t pos;
- while (cur_g && !bsearch_graph(cur_g, &(item->object.oid), &lex_index))
- cur_g = cur_g->base_graph;
+ if (!repo->objects->commit_graph)
+ return NULL;
+ if (!search_commit_pos_in_graph(id, repo->objects->commit_graph, &pos))
+ return NULL;
+ if (!repo_has_object_file(repo, id))
+ return NULL;
- if (cur_g) {
- *pos = lex_index + cur_g->num_commits_in_base;
- return 1;
- }
+ commit = lookup_commit(repo, id);
+ if (!commit)
+ return NULL;
+ if (commit->object.parsed)
+ return commit;
- return 0;
- }
+ if (!fill_commit_in_graph(repo, commit, repo->objects->commit_graph, pos))
+ return NULL;
+
+ return commit;
}
static int parse_commit_in_graph_one(struct repository *r,
@@ -895,7 +925,7 @@ static int parse_commit_in_graph_one(struct repository *r,
if (item->object.parsed)
return 1;
- if (find_commit_in_graph(item, g, &pos))
+ if (find_commit_pos_in_graph(item, g, &pos))
return fill_commit_in_graph(r, item, g, pos);
return 0;
@@ -921,7 +951,7 @@ void load_commit_graph_info(struct repository *r, struct commit *item)
uint32_t pos;
if (!prepare_commit_graph(r))
return;
- if (find_commit_in_graph(item, r->objects->commit_graph, &pos))
+ if (find_commit_pos_in_graph(item, r->objects->commit_graph, &pos))
fill_commit_graph_info(item, r->objects->commit_graph, pos);
}
@@ -1091,9 +1121,9 @@ static int write_graph_chunk_data(struct hashfile *f,
edge_value += ctx->new_num_commits_in_base;
else if (ctx->new_base_graph) {
uint32_t pos;
- if (find_commit_in_graph(parent->item,
- ctx->new_base_graph,
- &pos))
+ if (find_commit_pos_in_graph(parent->item,
+ ctx->new_base_graph,
+ &pos))
edge_value = pos;
}
@@ -1122,9 +1152,9 @@ static int write_graph_chunk_data(struct hashfile *f,
edge_value += ctx->new_num_commits_in_base;
else if (ctx->new_base_graph) {
uint32_t pos;
- if (find_commit_in_graph(parent->item,
- ctx->new_base_graph,
- &pos))
+ if (find_commit_pos_in_graph(parent->item,
+ ctx->new_base_graph,
+ &pos))
edge_value = pos;
}
@@ -1235,9 +1265,9 @@ static int write_graph_chunk_extra_edges(struct hashfile *f,
edge_value += ctx->new_num_commits_in_base;
else if (ctx->new_base_graph) {
uint32_t pos;
- if (find_commit_in_graph(parent->item,
- ctx->new_base_graph,
- &pos))
+ if (find_commit_pos_in_graph(parent->item,
+ ctx->new_base_graph,
+ &pos))
edge_value = pos;
}
@@ -2096,7 +2126,7 @@ static void sort_and_scan_merged_commits(struct write_commit_graph_context *ctx)
ctx->num_extra_edges = 0;
for (i = 0; i < ctx->commits.nr; i++) {
- display_progress(ctx->progress, i);
+ display_progress(ctx->progress, i + 1);
if (i && oideq(&ctx->commits.list[i - 1]->object.oid,
&ctx->commits.list[i]->object.oid)) {
diff --git a/commit-graph.h b/commit-graph.h
index 96c24fb577..04a94e1830 100644
--- a/commit-graph.h
+++ b/commit-graph.h
@@ -41,6 +41,14 @@ int open_commit_graph(const char *graph_file, int *fd, struct stat *st);
int parse_commit_in_graph(struct repository *r, struct commit *item);
/*
+ * Look up the given commit ID in the commit-graph. This will only return a
+ * commit if the ID exists both in the graph and in the object database such
+ * that we don't return commits whose object has been pruned. Otherwise, this
+ * function returns `NULL`.
+ */
+struct commit *lookup_commit_in_graph(struct repository *repo, const struct object_id *id);
+
+/*
* It is possible that we loaded commit contents from the commit buffer,
* but we also want to ensure the commit-graph content is correctly
* checked and filled. Fill the graph_pos and generation members of
diff --git a/commit.c b/commit.c
index 143f472c0f..551de4903c 100644
--- a/commit.c
+++ b/commit.c
@@ -25,6 +25,7 @@
static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
int save_commit_buffer = 1;
+int no_graft_file_deprecated_advice;
const char *commit_type = "commit";
@@ -190,7 +191,8 @@ static int read_graft_file(struct repository *r, const char *graft_file)
struct strbuf buf = STRBUF_INIT;
if (!fp)
return -1;
- if (advice_graft_file_deprecated)
+ if (!no_graft_file_deprecated_advice &&
+ advice_enabled(ADVICE_GRAFT_FILE_DEPRECATED))
advise(_("Support for <GIT_DIR>/info/grafts is deprecated\n"
"and will be removed in a future Git version.\n"
"\n"
diff --git a/commit.h b/commit.h
index df42eb434f..3ea32766bc 100644
--- a/commit.h
+++ b/commit.h
@@ -41,6 +41,7 @@ struct commit {
};
extern int save_commit_buffer;
+extern int no_graft_file_deprecated_advice;
extern const char *commit_type;
/* While we can decorate any object with a name, it's only used for commits.. */
diff --git a/compat/linux/procinfo.c b/compat/linux/procinfo.c
new file mode 100644
index 0000000000..bc2f9382a1
--- /dev/null
+++ b/compat/linux/procinfo.c
@@ -0,0 +1,176 @@
+#include "cache.h"
+
+#include "strbuf.h"
+#include "strvec.h"
+#include "trace2.h"
+
+/*
+ * We need more complex parsing in stat_parent_pid() and
+ * parse_proc_stat() below than a dumb fscanf(). That's because while
+ * the statcomm field is surrounded by parentheses, the process itself
+ * is free to insert any arbitrary byte sequence its its name. That
+ * can include newlines, spaces, closing parentheses etc.
+ *
+ * See do_task_stat() in fs/proc/array.c in linux.git, this is in
+ * contrast with the escaped version of the name found in
+ * /proc/%d/status.
+ *
+ * So instead of using fscanf() we'll read N bytes from it, look for
+ * the first "(", and then the last ")", anything in-between is our
+ * process name.
+ *
+ * How much N do we need? On Linux /proc/sys/kernel/pid_max is 2^15 by
+ * default, but it can be raised set to values of up to 2^22. So
+ * that's 7 digits for a PID. We have 2 PIDs in the first four fields
+ * we're interested in, so 2 * 7 = 14.
+ *
+ * We then have 3 spaces between those four values, and we'd like to
+ * get to the space between the 4th and the 5th (the "pgrp" field) to
+ * make sure we read the entire "ppid" field. So that brings us up to
+ * 14 + 3 + 1 = 18. Add the two parentheses around the "comm" value
+ * and it's 20. The "state" value itself is then one character (now at
+ * 21).
+ *
+ * Finally the maximum length of the "comm" name itself is 15
+ * characters, e.g. a setting of "123456789abcdefg" will be truncated
+ * to "123456789abcdef". See PR_SET_NAME in prctl(2). So all in all
+ * we'd need to read 21 + 15 = 36 bytes.
+ *
+ * Let's just read 2^6 (64) instead for good measure. If PID_MAX ever
+ * grows past 2^22 we'll be future-proof. We'll then anchor at the
+ * last ")" we find to locate the parent PID.
+ */
+#define STAT_PARENT_PID_READ_N 64
+
+static int parse_proc_stat(struct strbuf *sb, struct strbuf *name,
+ int *statppid)
+{
+ const char *comm_lhs = strchr(sb->buf, '(');
+ const char *comm_rhs = strrchr(sb->buf, ')');
+ const char *ppid_lhs, *ppid_rhs;
+ char *p;
+ pid_t ppid;
+
+ if (!comm_lhs || !comm_rhs)
+ goto bad_kernel;
+
+ /*
+ * We're at the ")", that's followed by " X ", where X is a
+ * single "state" character. So advance by 4 bytes.
+ */
+ ppid_lhs = comm_rhs + 4;
+
+ /*
+ * Read until the space between the "ppid" and "pgrp" fields
+ * to make sure we're anchored after the untruncated "ppid"
+ * field..
+ */
+ ppid_rhs = strchr(ppid_lhs, ' ');
+ if (!ppid_rhs)
+ goto bad_kernel;
+
+ ppid = strtol(ppid_lhs, &p, 10);
+ if (ppid_rhs == p) {
+ const char *comm = comm_lhs + 1;
+ size_t commlen = comm_rhs - comm;
+
+ strbuf_add(name, comm, commlen);
+ *statppid = ppid;
+
+ return 0;
+ }
+
+bad_kernel:
+ /*
+ * We were able to read our STAT_PARENT_PID_READ_N bytes from
+ * /proc/%d/stat, but the content is bad. Broken kernel?
+ * Should not happen, but handle it gracefully.
+ */
+ return -1;
+}
+
+static int stat_parent_pid(pid_t pid, struct strbuf *name, int *statppid)
+{
+ struct strbuf procfs_path = STRBUF_INIT;
+ struct strbuf sb = STRBUF_INIT;
+ FILE *fp;
+ int ret = -1;
+
+ /* try to use procfs if it's present. */
+ strbuf_addf(&procfs_path, "/proc/%d/stat", pid);
+ fp = fopen(procfs_path.buf, "r");
+ if (!fp)
+ goto cleanup;
+
+ /*
+ * We could be more strict here and assert that we read at
+ * least STAT_PARENT_PID_READ_N. My reading of procfs(5) is
+ * that on any modern kernel (at least since 2.6.0 released in
+ * 2003) even if all the mandatory numeric fields were zero'd
+ * out we'd get at least 100 bytes, but let's just check that
+ * we got anything at all and trust the parse_proc_stat()
+ * function to handle its "Bad Kernel?" error checking.
+ */
+ if (!strbuf_fread(&sb, STAT_PARENT_PID_READ_N, fp))
+ goto cleanup;
+ if (parse_proc_stat(&sb, name, statppid) < 0)
+ goto cleanup;
+
+ ret = 0;
+cleanup:
+ if (fp)
+ fclose(fp);
+ strbuf_release(&procfs_path);
+ strbuf_release(&sb);
+
+ return ret;
+}
+
+static void push_ancestry_name(struct strvec *names, pid_t pid)
+{
+ struct strbuf name = STRBUF_INIT;
+ int ppid;
+
+ if (stat_parent_pid(pid, &name, &ppid) < 0)
+ goto cleanup;
+
+ strvec_push(names, name.buf);
+
+ /*
+ * Both errors and reaching the end of the process chain are
+ * reported as fields of 0 by proc(5)
+ */
+ if (ppid)
+ push_ancestry_name(names, ppid);
+cleanup:
+ strbuf_release(&name);
+
+ return;
+}
+
+void trace2_collect_process_info(enum trace2_process_info_reason reason)
+{
+ struct strvec names = STRVEC_INIT;
+
+ if (!trace2_is_enabled())
+ return;
+
+ switch (reason) {
+ case TRACE2_PROCESS_INFO_EXIT:
+ /*
+ * The Windows version of this calls its
+ * get_peak_memory_info() here. We may want to insert
+ * similar process-end statistics here in the future.
+ */
+ break;
+ case TRACE2_PROCESS_INFO_STARTUP:
+ push_ancestry_name(&names, getppid());
+
+ if (names.nr)
+ trace2_cmd_ancestry(names.v);
+ strvec_clear(&names);
+ break;
+ }
+
+ return;
+}
diff --git a/compat/mmap.c b/compat/mmap.c
index 14d31010df..8d6c02d4bc 100644
--- a/compat/mmap.c
+++ b/compat/mmap.c
@@ -7,7 +7,12 @@ void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t of
if (start != NULL || flags != MAP_PRIVATE || prot != PROT_READ)
die("Invalid usage of mmap when built with NO_MMAP");
- start = xmalloc(length);
+ if (length == 0) {
+ errno = EINVAL;
+ return MAP_FAILED;
+ }
+
+ start = malloc(length);
if (start == NULL) {
errno = ENOMEM;
return MAP_FAILED;
diff --git a/compat/nedmalloc/nedmalloc.c b/compat/nedmalloc/nedmalloc.c
index 1cc31c3502..edb438a777 100644
--- a/compat/nedmalloc/nedmalloc.c
+++ b/compat/nedmalloc/nedmalloc.c
@@ -510,7 +510,7 @@ static void threadcache_free(nedpool *p, threadcache *tc, int mymspace, void *me
assert(idx<=THREADCACHEMAXBINS);
if(tck==*binsptr)
{
- fprintf(stderr, "Attempt to free already freed memory block %p - aborting!\n", tck);
+ fprintf(stderr, "Attempt to free already freed memory block %p - aborting!\n", (void *)tck);
abort();
}
#ifdef FULLSANITYCHECKS
diff --git a/compat/simple-ipc/ipc-unix-socket.c b/compat/simple-ipc/ipc-unix-socket.c
index 1927e6ef4b..4e28857a0a 100644
--- a/compat/simple-ipc/ipc-unix-socket.c
+++ b/compat/simple-ipc/ipc-unix-socket.c
@@ -168,7 +168,8 @@ void ipc_client_close_connection(struct ipc_client_connection *connection)
int ipc_client_send_command_to_connection(
struct ipc_client_connection *connection,
- const char *message, struct strbuf *answer)
+ const char *message, size_t message_len,
+ struct strbuf *answer)
{
int ret = 0;
@@ -176,7 +177,7 @@ int ipc_client_send_command_to_connection(
trace2_region_enter("ipc-client", "send-command", NULL);
- if (write_packetized_from_buf_no_flush(message, strlen(message),
+ if (write_packetized_from_buf_no_flush(message, message_len,
connection->fd) < 0 ||
packet_flush_gently(connection->fd) < 0) {
ret = error(_("could not send IPC command"));
@@ -197,7 +198,8 @@ done:
int ipc_client_send_command(const char *path,
const struct ipc_client_connect_options *options,
- const char *message, struct strbuf *answer)
+ const char *message, size_t message_len,
+ struct strbuf *answer)
{
int ret = -1;
enum ipc_active_state state;
@@ -208,7 +210,9 @@ int ipc_client_send_command(const char *path,
if (state != IPC_STATE__LISTENING)
return ret;
- ret = ipc_client_send_command_to_connection(connection, message, answer);
+ ret = ipc_client_send_command_to_connection(connection,
+ message, message_len,
+ answer);
ipc_client_close_connection(connection);
@@ -503,7 +507,7 @@ static int worker_thread__do_io(
if (ret >= 0) {
ret = worker_thread_data->server_data->application_cb(
worker_thread_data->server_data->application_data,
- buf.buf, do_io_reply_callback, &reply_data);
+ buf.buf, buf.len, do_io_reply_callback, &reply_data);
packet_flush_gently(reply_data.fd);
}
diff --git a/compat/simple-ipc/ipc-win32.c b/compat/simple-ipc/ipc-win32.c
index 8dc7bda087..20ea7b65e0 100644
--- a/compat/simple-ipc/ipc-win32.c
+++ b/compat/simple-ipc/ipc-win32.c
@@ -3,6 +3,8 @@
#include "strbuf.h"
#include "pkt-line.h"
#include "thread-utils.h"
+#include "accctrl.h"
+#include "aclapi.h"
#ifndef SUPPORTS_SIMPLE_IPC
/*
@@ -49,6 +51,9 @@ static enum ipc_active_state get_active_state(wchar_t *pipe_path)
if (GetLastError() == ERROR_FILE_NOT_FOUND)
return IPC_STATE__PATH_NOT_FOUND;
+ trace2_data_intmax("ipc-debug", NULL, "getstate/waitpipe/gle",
+ (intmax_t)GetLastError());
+
return IPC_STATE__OTHER_ERROR;
}
@@ -109,9 +114,15 @@ static enum ipc_active_state connect_to_server(
t_start_ms = (DWORD)(getnanotime() / 1000000);
if (!WaitNamedPipeW(wpath, timeout_ms)) {
- if (GetLastError() == ERROR_SEM_TIMEOUT)
+ DWORD gleWait = GetLastError();
+
+ if (gleWait == ERROR_SEM_TIMEOUT)
return IPC_STATE__NOT_LISTENING;
+ trace2_data_intmax("ipc-debug", NULL,
+ "connect/waitpipe/gle",
+ (intmax_t)gleWait);
+
return IPC_STATE__OTHER_ERROR;
}
@@ -133,17 +144,31 @@ static enum ipc_active_state connect_to_server(
break; /* try again */
default:
+ trace2_data_intmax("ipc-debug", NULL,
+ "connect/createfile/gle",
+ (intmax_t)gle);
+
return IPC_STATE__OTHER_ERROR;
}
}
if (!SetNamedPipeHandleState(hPipe, &mode, NULL, NULL)) {
+ gle = GetLastError();
+ trace2_data_intmax("ipc-debug", NULL,
+ "connect/setpipestate/gle",
+ (intmax_t)gle);
+
CloseHandle(hPipe);
return IPC_STATE__OTHER_ERROR;
}
*pfd = _open_osfhandle((intptr_t)hPipe, O_RDWR|O_BINARY);
if (*pfd < 0) {
+ gle = GetLastError();
+ trace2_data_intmax("ipc-debug", NULL,
+ "connect/openosfhandle/gle",
+ (intmax_t)gle);
+
CloseHandle(hPipe);
return IPC_STATE__OTHER_ERROR;
}
@@ -208,7 +233,8 @@ void ipc_client_close_connection(struct ipc_client_connection *connection)
int ipc_client_send_command_to_connection(
struct ipc_client_connection *connection,
- const char *message, struct strbuf *answer)
+ const char *message, size_t message_len,
+ struct strbuf *answer)
{
int ret = 0;
@@ -216,7 +242,7 @@ int ipc_client_send_command_to_connection(
trace2_region_enter("ipc-client", "send-command", NULL);
- if (write_packetized_from_buf_no_flush(message, strlen(message),
+ if (write_packetized_from_buf_no_flush(message, message_len,
connection->fd) < 0 ||
packet_flush_gently(connection->fd) < 0) {
ret = error(_("could not send IPC command"));
@@ -239,7 +265,8 @@ done:
int ipc_client_send_command(const char *path,
const struct ipc_client_connect_options *options,
- const char *message, struct strbuf *response)
+ const char *message, size_t message_len,
+ struct strbuf *response)
{
int ret = -1;
enum ipc_active_state state;
@@ -250,7 +277,9 @@ int ipc_client_send_command(const char *path,
if (state != IPC_STATE__LISTENING)
return ret;
- ret = ipc_client_send_command_to_connection(connection, message, response);
+ ret = ipc_client_send_command_to_connection(connection,
+ message, message_len,
+ response);
ipc_client_close_connection(connection);
@@ -458,7 +487,7 @@ static int do_io(struct ipc_server_thread_data *server_thread_data)
if (ret >= 0) {
ret = server_thread_data->server_data->application_cb(
server_thread_data->server_data->application_data,
- buf.buf, do_io_reply_callback, &reply_data);
+ buf.buf, buf.len, do_io_reply_callback, &reply_data);
packet_flush_gently(reply_data.fd);
@@ -565,11 +594,132 @@ finished:
return NULL;
}
+/*
+ * We need to build a Windows "SECURITY_ATTRIBUTES" object and use it
+ * to apply an ACL when we create the initial instance of the Named
+ * Pipe. The construction is somewhat involved and consists of
+ * several sequential steps and intermediate objects.
+ *
+ * We use this structure to hold these intermediate pointers so that
+ * we can free them as a group. (It is unclear from the docs whether
+ * some of these intermediate pointers can be freed before we are
+ * finished using the "lpSA" member.)
+ */
+struct my_sa_data
+{
+ PSID pEveryoneSID;
+ PACL pACL;
+ PSECURITY_DESCRIPTOR pSD;
+ LPSECURITY_ATTRIBUTES lpSA;
+};
+
+static void init_sa(struct my_sa_data *d)
+{
+ memset(d, 0, sizeof(*d));
+}
+
+static void release_sa(struct my_sa_data *d)
+{
+ if (d->pEveryoneSID)
+ FreeSid(d->pEveryoneSID);
+ if (d->pACL)
+ LocalFree(d->pACL);
+ if (d->pSD)
+ LocalFree(d->pSD);
+ if (d->lpSA)
+ LocalFree(d->lpSA);
+
+ memset(d, 0, sizeof(*d));
+}
+
+/*
+ * Create SECURITY_ATTRIBUTES to apply to the initial named pipe. The
+ * creator of the first server instance gets to set the ACLs on it.
+ *
+ * We allow the well-known group `EVERYONE` to have read+write access
+ * to the named pipe so that clients can send queries to the daemon
+ * and receive the response.
+ *
+ * Normally, this is not necessary since the daemon is usually
+ * automatically started by a foreground command like `git status`,
+ * but in those cases where an elevated Git command started the daemon
+ * (such that the daemon itself runs with elevation), we need to add
+ * the ACL so that non-elevated commands can write to it.
+ *
+ * The following document was helpful:
+ * https://docs.microsoft.com/en-us/windows/win32/secauthz/creating-a-security-descriptor-for-a-new-object-in-c--
+ *
+ * Returns d->lpSA set to a SA or NULL.
+ */
+static LPSECURITY_ATTRIBUTES get_sa(struct my_sa_data *d)
+{
+ SID_IDENTIFIER_AUTHORITY sid_auth_world = SECURITY_WORLD_SID_AUTHORITY;
+#define NR_EA (1)
+ EXPLICIT_ACCESS ea[NR_EA];
+ DWORD dwResult;
+
+ if (!AllocateAndInitializeSid(&sid_auth_world, 1,
+ SECURITY_WORLD_RID, 0,0,0,0,0,0,0,
+ &d->pEveryoneSID)) {
+ DWORD gle = GetLastError();
+ trace2_data_intmax("ipc-debug", NULL, "alloc-world-sid/gle",
+ (intmax_t)gle);
+ goto fail;
+ }
+
+ memset(ea, 0, NR_EA * sizeof(EXPLICIT_ACCESS));
+
+ ea[0].grfAccessPermissions = GENERIC_READ | GENERIC_WRITE;
+ ea[0].grfAccessMode = SET_ACCESS;
+ ea[0].grfInheritance = NO_INHERITANCE;
+ ea[0].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
+ ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+ ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
+ ea[0].Trustee.ptstrName = (LPTSTR)d->pEveryoneSID;
+
+ dwResult = SetEntriesInAcl(NR_EA, ea, NULL, &d->pACL);
+ if (dwResult != ERROR_SUCCESS) {
+ DWORD gle = GetLastError();
+ trace2_data_intmax("ipc-debug", NULL, "set-acl-entry/gle",
+ (intmax_t)gle);
+ trace2_data_intmax("ipc-debug", NULL, "set-acl-entry/dw",
+ (intmax_t)dwResult);
+ goto fail;
+ }
+
+ d->pSD = (PSECURITY_DESCRIPTOR)LocalAlloc(
+ LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
+ if (!InitializeSecurityDescriptor(d->pSD, SECURITY_DESCRIPTOR_REVISION)) {
+ DWORD gle = GetLastError();
+ trace2_data_intmax("ipc-debug", NULL, "init-sd/gle", (intmax_t)gle);
+ goto fail;
+ }
+
+ if (!SetSecurityDescriptorDacl(d->pSD, TRUE, d->pACL, FALSE)) {
+ DWORD gle = GetLastError();
+ trace2_data_intmax("ipc-debug", NULL, "set-sd-dacl/gle", (intmax_t)gle);
+ goto fail;
+ }
+
+ d->lpSA = (LPSECURITY_ATTRIBUTES)LocalAlloc(LPTR, sizeof(SECURITY_ATTRIBUTES));
+ d->lpSA->nLength = sizeof(SECURITY_ATTRIBUTES);
+ d->lpSA->lpSecurityDescriptor = d->pSD;
+ d->lpSA->bInheritHandle = FALSE;
+
+ return d->lpSA;
+
+fail:
+ release_sa(d);
+ return NULL;
+}
+
static HANDLE create_new_pipe(wchar_t *wpath, int is_first)
{
HANDLE hPipe;
DWORD dwOpenMode, dwPipeMode;
- LPSECURITY_ATTRIBUTES lpsa = NULL;
+ struct my_sa_data my_sa_data;
+
+ init_sa(&my_sa_data);
dwOpenMode = PIPE_ACCESS_INBOUND | PIPE_ACCESS_OUTBOUND |
FILE_FLAG_OVERLAPPED;
@@ -585,20 +735,15 @@ static HANDLE create_new_pipe(wchar_t *wpath, int is_first)
* set the ACL / Security Attributes on the named
* pipe; subsequent instances inherit and cannot
* change them.
- *
- * TODO Should we allow the application layer to
- * specify security attributes, such as `LocalService`
- * or `LocalSystem`, when we create the named pipe?
- * This question is probably not important when the
- * daemon is started by a foreground user process and
- * only needs to talk to the current user, but may be
- * if the daemon is run via the Control Panel as a
- * System Service.
*/
+ get_sa(&my_sa_data);
}
hPipe = CreateNamedPipeW(wpath, dwOpenMode, dwPipeMode,
- PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, lpsa);
+ PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0,
+ my_sa_data.lpSA);
+
+ release_sa(&my_sa_data);
return hPipe;
}
diff --git a/compat/stub/procinfo.c b/compat/stub/procinfo.c
new file mode 100644
index 0000000000..12c0a23c9e
--- /dev/null
+++ b/compat/stub/procinfo.c
@@ -0,0 +1,11 @@
+#include "git-compat-util.h"
+
+#include "trace2.h"
+
+/*
+ * Stub. See sample implementations in compat/linux/procinfo.c and
+ * compat/win32/trace2_win32_process_info.c.
+ */
+void trace2_collect_process_info(enum trace2_process_info_reason reason)
+{
+}
diff --git a/compat/vcbuild/README b/compat/vcbuild/README
index 51fb083dbb..29ec1d0f10 100644
--- a/compat/vcbuild/README
+++ b/compat/vcbuild/README
@@ -92,7 +92,7 @@ The Steps of Build Git with VS2008
the git operations.
3. Inside Git's directory run the command:
- make command-list.h config-list.h
+ make generated-hdrs
to generate the header file needed to compile git.
4. Then either build Git with the GNU Make Makefile in the Git projects
diff --git a/compat/win32/lazyload.h b/compat/win32/lazyload.h
index 9e631c8593..2b3637135f 100644
--- a/compat/win32/lazyload.h
+++ b/compat/win32/lazyload.h
@@ -15,10 +15,12 @@
* source, target);
*/
+typedef void (*FARVOIDPROC)(void);
+
struct proc_addr {
const char *const dll;
const char *const function;
- FARPROC pfunction;
+ FARVOIDPROC pfunction;
unsigned initialized : 1;
};
@@ -26,7 +28,8 @@ struct proc_addr {
#define DECLARE_PROC_ADDR(dll, rettype, function, ...) \
static struct proc_addr proc_addr_##function = \
{ #dll, #function, NULL, 0 }; \
- static rettype (WINAPI *function)(__VA_ARGS__)
+ typedef rettype (WINAPI *proc_type_##function)(__VA_ARGS__); \
+ static proc_type_##function function
/*
* Loads a function from a DLL (once-only).
@@ -35,9 +38,9 @@ struct proc_addr {
* This function is not thread-safe.
*/
#define INIT_PROC_ADDR(function) \
- (function = get_proc_addr(&proc_addr_##function))
+ (function = (proc_type_##function)get_proc_addr(&proc_addr_##function))
-static inline void *get_proc_addr(struct proc_addr *proc)
+static inline FARVOIDPROC get_proc_addr(struct proc_addr *proc)
{
/* only do this once */
if (!proc->initialized) {
@@ -46,7 +49,8 @@ static inline void *get_proc_addr(struct proc_addr *proc)
hnd = LoadLibraryExA(proc->dll, NULL,
LOAD_LIBRARY_SEARCH_SYSTEM32);
if (hnd)
- proc->pfunction = GetProcAddress(hnd, proc->function);
+ proc->pfunction = (FARVOIDPROC)GetProcAddress(hnd,
+ proc->function);
}
/* set ENOSYS if DLL or function was not found */
if (!proc->pfunction)
diff --git a/config.c b/config.c
index f33abeab85..2dcbe901b6 100644
--- a/config.c
+++ b/config.c
@@ -76,7 +76,6 @@ static struct key_value_info *current_config_kvi;
*/
static enum config_scope current_parsing_scope;
-static int core_compression_seen;
static int pack_compression_seen;
static int zlib_compression_seen;
@@ -137,7 +136,7 @@ static int handle_path_include(const char *path, struct config_include_data *inc
if (!path)
return config_error_nonbool("include.path");
- expanded = expand_user_path(path, 0);
+ expanded = interpolate_path(path, 0);
if (!expanded)
return error(_("could not expand include path '%s'"), path);
path = expanded;
@@ -185,7 +184,7 @@ static int prepare_include_condition_pattern(struct strbuf *pat)
char *expanded;
int prefix = 0;
- expanded = expand_user_path(pat->buf, 1);
+ expanded = interpolate_path(pat->buf, 1);
if (expanded) {
strbuf_reset(pat);
strbuf_addstr(pat, expanded);
@@ -426,7 +425,7 @@ static inline int iskeychar(int c)
* baselen - pointer to size_t which will hold the length of the
* section + subsection part, can be NULL
*/
-static int git_config_parse_key_1(const char *key, char **store_key, size_t *baselen_, int quiet)
+int git_config_parse_key(const char *key, char **store_key, size_t *baselen_)
{
size_t i, baselen;
int dot;
@@ -438,14 +437,12 @@ static int git_config_parse_key_1(const char *key, char **store_key, size_t *bas
*/
if (last_dot == NULL || last_dot == key) {
- if (!quiet)
- error(_("key does not contain a section: %s"), key);
+ error(_("key does not contain a section: %s"), key);
return -CONFIG_NO_SECTION_OR_NAME;
}
if (!last_dot[1]) {
- if (!quiet)
- error(_("key does not contain variable name: %s"), key);
+ error(_("key does not contain variable name: %s"), key);
return -CONFIG_NO_SECTION_OR_NAME;
}
@@ -456,8 +453,7 @@ static int git_config_parse_key_1(const char *key, char **store_key, size_t *bas
/*
* Validate the key and while at it, lower case it for matching.
*/
- if (store_key)
- *store_key = xmallocz(strlen(key));
+ *store_key = xmallocz(strlen(key));
dot = 0;
for (i = 0; key[i]; i++) {
@@ -468,39 +464,24 @@ static int git_config_parse_key_1(const char *key, char **store_key, size_t *bas
if (!dot || i > baselen) {
if (!iskeychar(c) ||
(i == baselen + 1 && !isalpha(c))) {
- if (!quiet)
- error(_("invalid key: %s"), key);
+ error(_("invalid key: %s"), key);
goto out_free_ret_1;
}
c = tolower(c);
} else if (c == '\n') {
- if (!quiet)
- error(_("invalid key (newline): %s"), key);
+ error(_("invalid key (newline): %s"), key);
goto out_free_ret_1;
}
- if (store_key)
- (*store_key)[i] = c;
+ (*store_key)[i] = c;
}
return 0;
out_free_ret_1:
- if (store_key) {
- FREE_AND_NULL(*store_key);
- }
+ FREE_AND_NULL(*store_key);
return -CONFIG_INVALID_KEY;
}
-int git_config_parse_key(const char *key, char **store_key, size_t *baselen)
-{
- return git_config_parse_key_1(key, store_key, baselen, 0);
-}
-
-int git_config_key_is_valid(const char *key)
-{
- return !git_config_parse_key_1(key, NULL, NULL, 1);
-}
-
static int config_parse_pair(const char *key, const char *value,
config_fn_t fn, void *data)
{
@@ -1270,7 +1251,7 @@ int git_config_pathname(const char **dest, const char *var, const char *value)
{
if (!value)
return config_error_nonbool(var);
- *dest = expand_user_path(value, 0);
+ *dest = interpolate_path(value, 0);
if (!*dest)
die(_("failed to expand user dir in: '%s'"), value);
return 0;
@@ -1400,8 +1381,6 @@ static int git_default_core_config(const char *var, const char *value, void *cb)
level = Z_DEFAULT_COMPRESSION;
else if (level < 0 || level > Z_BEST_COMPRESSION)
die(_("bad zlib compression level %d"), level);
- core_compression_level = level;
- core_compression_seen = 1;
if (!zlib_compression_seen)
zlib_compression_level = level;
if (!pack_compression_seen)
@@ -1796,6 +1775,7 @@ int git_config_from_mem(config_fn_t fn,
int git_config_from_blob_oid(config_fn_t fn,
const char *name,
+ struct repository *repo,
const struct object_id *oid,
void *data)
{
@@ -1804,7 +1784,7 @@ int git_config_from_blob_oid(config_fn_t fn,
unsigned long size;
int ret;
- buf = read_object_file(oid, &type, &size);
+ buf = repo_read_object_file(repo, oid, &type, &size);
if (!buf)
return error(_("unable to load config blob object '%s'"), name);
if (type != OBJ_BLOB) {
@@ -1820,14 +1800,15 @@ int git_config_from_blob_oid(config_fn_t fn,
}
static int git_config_from_blob_ref(config_fn_t fn,
+ struct repository *repo,
const char *name,
void *data)
{
struct object_id oid;
- if (get_oid(name, &oid) < 0)
+ if (repo_get_oid(repo, name, &oid) < 0)
return error(_("unable to resolve config blob '%s'"), name);
- return git_config_from_blob_oid(fn, name, &oid, data);
+ return git_config_from_blob_oid(fn, name, repo, &oid, data);
}
char *git_system_config(void)
@@ -1845,7 +1826,7 @@ void git_global_config(char **user_out, char **xdg_out)
char *xdg_config = NULL;
if (!user_config) {
- user_config = expand_user_path("~/.gitconfig", 0);
+ user_config = interpolate_path("~/.gitconfig", 0);
xdg_config = xdg_config_home("config");
}
@@ -1958,12 +1939,16 @@ int config_with_options(config_fn_t fn, void *data,
* If we have a specific filename, use it. Otherwise, follow the
* regular lookup sequence.
*/
- if (config_source && config_source->use_stdin)
+ if (config_source && config_source->use_stdin) {
return git_config_from_stdin(fn, data);
- else if (config_source && config_source->file)
+ } else if (config_source && config_source->file) {
return git_config_from_file(fn, config_source->file, data);
- else if (config_source && config_source->blob)
- return git_config_from_blob_ref(fn, config_source->blob, data);
+ } else if (config_source && config_source->blob) {
+ struct repository *repo = config_source->repo ?
+ config_source->repo : the_repository;
+ return git_config_from_blob_ref(fn, repo, config_source->blob,
+ data);
+ }
return do_git_config_sequence(opts, fn, data);
}
diff --git a/config.h b/config.h
index a2200f3111..f119de0130 100644
--- a/config.h
+++ b/config.h
@@ -49,6 +49,8 @@ const char *config_scope_name(enum config_scope scope);
struct git_config_source {
unsigned int use_stdin:1;
const char *file;
+ /* The repository if blob is not NULL; leave blank for the_repository */
+ struct repository *repo;
const char *blob;
enum config_scope scope;
};
@@ -136,6 +138,7 @@ int git_config_from_mem(config_fn_t fn,
const char *buf, size_t len,
void *data, const struct config_options *opts);
int git_config_from_blob_oid(config_fn_t fn, const char *name,
+ struct repository *repo,
const struct object_id *oid, void *data);
void git_config_push_parameter(const char *text);
void git_config_push_env(const char *spec);
@@ -256,7 +259,6 @@ int git_config_set_gently(const char *, const char *);
void git_config_set(const char *, const char *);
int git_config_parse_key(const char *, char **, size_t *);
-int git_config_key_is_valid(const char *key);
/*
* The following macros specify flag bits that alter the behavior
@@ -606,7 +608,6 @@ int git_config_get_maybe_bool(const char *key, int *dest);
int git_config_get_pathname(const char *key, const char **dest);
int git_config_get_index_threads(int *dest);
-int git_config_get_untracked_cache(void);
int git_config_get_split_index(void);
int git_config_get_max_percent_split_change(void);
int git_config_get_fsmonitor(void);
diff --git a/config.mak.dev b/config.mak.dev
index 022fb58218..7673fed114 100644
--- a/config.mak.dev
+++ b/config.mak.dev
@@ -1,13 +1,24 @@
+ifndef COMPILER_FEATURES
+COMPILER_FEATURES := $(shell ./detect-compiler $(CC))
+endif
+
ifeq ($(filter no-error,$(DEVOPTS)),)
DEVELOPER_CFLAGS += -Werror
SPARSE_FLAGS += -Wsparse-error
endif
-ifneq ($(filter pedantic,$(DEVOPTS)),)
+
+DEVELOPER_CFLAGS += -Wall
+ifeq ($(filter no-pedantic,$(DEVOPTS)),)
DEVELOPER_CFLAGS += -pedantic
-# don't warn for each N_ use
-DEVELOPER_CFLAGS += -DUSE_PARENS_AROUND_GETTEXT_N=0
+ifneq (($or $(filter gcc5,$(COMPILER_FEATURES)),$(filter clang4,$(COMPILER_FEATURES))),)
+DEVELOPER_CFLAGS += -Wpedantic
+ifneq ($(filter gcc10,$(COMPILER_FEATURES)),)
+ifeq ($(uname_S),MINGW)
+DEVELOPER_CFLAGS += -Wno-pedantic-ms-format
+endif
+endif
+endif
endif
-DEVELOPER_CFLAGS += -Wall
DEVELOPER_CFLAGS += -Wdeclaration-after-statement
DEVELOPER_CFLAGS += -Wformat-security
DEVELOPER_CFLAGS += -Wold-style-definition
@@ -18,10 +29,6 @@ DEVELOPER_CFLAGS += -Wunused
DEVELOPER_CFLAGS += -Wvla
DEVELOPER_CFLAGS += -fno-common
-ifndef COMPILER_FEATURES
-COMPILER_FEATURES := $(shell ./detect-compiler $(CC))
-endif
-
ifneq ($(filter clang4,$(COMPILER_FEATURES)),)
DEVELOPER_CFLAGS += -Wtautological-constant-out-of-range-compare
endif
diff --git a/config.mak.uname b/config.mak.uname
index 69413fb3dc..3236a4918a 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -11,6 +11,10 @@ uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not')
uname_P := $(shell sh -c 'uname -p 2>/dev/null || echo not')
uname_V := $(shell sh -c 'uname -v 2>/dev/null || echo not')
+ifneq ($(findstring MINGW,$(uname_S)),)
+ uname_S := MINGW
+endif
+
ifdef MSVC
# avoid the MingW and Cygwin configuration sections
uname_S := Windows
@@ -58,6 +62,8 @@ ifeq ($(uname_S),Linux)
FREAD_READS_DIRECTORIES = UnfortunatelyYes
BASIC_CFLAGS += -DHAVE_SYSINFO
PROCFS_EXECUTABLE_PATH = /proc/self/exe
+ HAVE_PLATFORM_PROCINFO = YesPlease
+ COMPAT_OBJS += compat/linux/procinfo.o
endif
ifeq ($(uname_S),GNU/kFreeBSD)
HAVE_ALLOCA_H = YesPlease
@@ -586,7 +592,7 @@ ifeq ($(uname_S),NONSTOP_KERNEL)
SANE_TOOL_PATH = /usr/coreutils/bin:/usr/local/bin
SHELL_PATH = /usr/coreutils/bin/bash
endif
-ifneq (,$(findstring MINGW,$(uname_S)))
+ifeq ($(uname_S),MINGW)
pathsep = ;
HAVE_ALLOCA_H = YesPlease
NO_PREAD = YesPlease
@@ -617,6 +623,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
ETAGS_TARGET = ETAGS
NO_POSIX_GOODIES = UnfortunatelyYes
DEFAULT_HELP_FORMAT = html
+ HAVE_PLATFORM_PROCINFO = YesPlease
BASIC_LDFLAGS += -municode
COMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32
COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
@@ -732,9 +739,9 @@ vcxproj:
echo '</Project>') >git-remote-http/LinkOrCopyRemoteHttp.targets
git add -f git/LinkOrCopyBuiltins.targets git-remote-http/LinkOrCopyRemoteHttp.targets
- # Add command-list.h and config-list.h
- $(MAKE) MSVC=1 SKIP_VCPKG=1 prefix=/mingw64 config-list.h command-list.h
- git add -f config-list.h command-list.h
+ # Add generated headers
+ $(MAKE) MSVC=1 SKIP_VCPKG=1 prefix=/mingw64 $(GENERATED_H)
+ git add -f $(GENERATED_H)
# Add scripts
rm -f perl/perl.mak
diff --git a/connect.c b/connect.c
index 70b13389ba..eaf7d6d261 100644
--- a/connect.c
+++ b/connect.c
@@ -164,6 +164,8 @@ enum protocol_version discover_version(struct packet_reader *reader)
BUG("unknown protocol version");
}
+ trace2_data_intmax("transfer", NULL, "negotiated-version", version);
+
return version;
}
@@ -555,6 +557,8 @@ const char *parse_feature_value(const char *feature_list, const char *feature, i
if (!*value || isspace(*value)) {
if (lenp)
*lenp = 0;
+ if (offset)
+ *offset = found + len - feature_list;
return value;
}
/* feature with a value (e.g., "agent=git/1.2.3") */
diff --git a/connected.c b/connected.c
index b18299fdf0..cf68e37a97 100644
--- a/connected.c
+++ b/connected.c
@@ -24,7 +24,7 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
struct child_process rev_list = CHILD_PROCESS_INIT;
FILE *rev_list_in;
struct check_connected_options defaults = CHECK_CONNECTED_INIT;
- struct object_id oid;
+ const struct object_id *oid;
int err = 0;
struct packed_git *new_pack = NULL;
struct transport *transport;
@@ -34,7 +34,8 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
opt = &defaults;
transport = opt->transport;
- if (fn(cb_data, &oid)) {
+ oid = fn(cb_data);
+ if (!oid) {
if (opt->err_fd)
close(opt->err_fd);
return err;
@@ -73,7 +74,7 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
for (p = get_all_packs(the_repository); p; p = p->next) {
if (!p->pack_promisor)
continue;
- if (find_pack_entry_one(oid.hash, p))
+ if (find_pack_entry_one(oid->hash, p))
goto promisor_pack_found;
}
/*
@@ -83,7 +84,7 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
goto no_promisor_pack_found;
promisor_pack_found:
;
- } while (!fn(cb_data, &oid));
+ } while ((oid = fn(cb_data)) != NULL);
return 0;
}
@@ -106,6 +107,7 @@ no_promisor_pack_found:
if (opt->progress)
strvec_pushf(&rev_list.args, "--progress=%s",
_("Checking connectivity"));
+ strvec_push(&rev_list.args, "--unsorted-input");
rev_list.git_cmd = 1;
rev_list.env = opt->env;
@@ -132,12 +134,12 @@ no_promisor_pack_found:
* are sure the ref is good and not sending it to
* rev-list for verification.
*/
- if (new_pack && find_pack_entry_one(oid.hash, new_pack))
+ if (new_pack && find_pack_entry_one(oid->hash, new_pack))
continue;
- if (fprintf(rev_list_in, "%s\n", oid_to_hex(&oid)) < 0)
+ if (fprintf(rev_list_in, "%s\n", oid_to_hex(oid)) < 0)
break;
- } while (!fn(cb_data, &oid));
+ } while ((oid = fn(cb_data)) != NULL);
if (ferror(rev_list_in) || fflush(rev_list_in)) {
if (errno != EPIPE && errno != EINVAL)
diff --git a/connected.h b/connected.h
index 8d5a6b3ad6..6e59c92aa3 100644
--- a/connected.h
+++ b/connected.h
@@ -9,7 +9,7 @@ struct transport;
* When called after returning the name for the last object, return -1
* to signal EOF, otherwise return 0.
*/
-typedef int (*oid_iterate_fn)(void *, struct object_id *oid);
+typedef const struct object_id *(*oid_iterate_fn)(void *);
/*
* Named-arguments struct for check_connected. All arguments are
diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt
index 171b4124af..fd1399c440 100644
--- a/contrib/buildsystems/CMakeLists.txt
+++ b/contrib/buildsystems/CMakeLists.txt
@@ -624,6 +624,13 @@ if(NOT EXISTS ${CMAKE_BINARY_DIR}/config-list.h)
OUTPUT_FILE ${CMAKE_BINARY_DIR}/config-list.h)
endif()
+if(NOT EXISTS ${CMAKE_BINARY_DIR}/hook-list.h)
+ message("Generating hook-list.h")
+ execute_process(COMMAND ${SH_EXE} ${CMAKE_SOURCE_DIR}/generate-hooklist.sh
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ OUTPUT_FILE ${CMAKE_BINARY_DIR}/hook-list.h)
+endif()
+
include_directories(${CMAKE_BINARY_DIR})
#build
diff --git a/contrib/coccinelle/xopen.cocci b/contrib/coccinelle/xopen.cocci
new file mode 100644
index 0000000000..b71db67019
--- /dev/null
+++ b/contrib/coccinelle/xopen.cocci
@@ -0,0 +1,19 @@
+@@
+identifier fd;
+identifier die_fn =~ "^(die|die_errno)$";
+@@
+ int fd =
+- open
++ xopen
+ (...);
+- if ( \( fd < 0 \| fd == -1 \) ) { die_fn(...); }
+
+@@
+expression fd;
+identifier die_fn =~ "^(die|die_errno)$";
+@@
+ fd =
+- open
++ xopen
+ (...);
+- if ( \( fd < 0 \| fd == -1 \) ) { die_fn(...); }
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 4bdd27ddc8..eb5fd4783d 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -356,7 +356,7 @@ __gitcomp ()
local cur_="${3-$cur}"
case "$cur_" in
- --*=)
+ *=)
;;
--no-*)
local c i=0 IFS=$' \t\n'
@@ -421,7 +421,7 @@ __gitcomp_builtin ()
local incl="${2-}"
local excl="${3-}"
- local var=__gitcomp_builtin_"${cmd/-/_}"
+ local var=__gitcomp_builtin_"${cmd//-/_}"
local options
eval "options=\${$var-}"
@@ -2503,7 +2503,14 @@ __git_config_vars=
__git_compute_config_vars ()
{
test -n "$__git_config_vars" ||
- __git_config_vars="$(git help --config-for-completion | sort -u)"
+ __git_config_vars="$(git help --config-for-completion)"
+}
+
+__git_config_sections=
+__git_compute_config_sections ()
+{
+ test -n "$__git_config_sections" ||
+ __git_config_sections="$(git help --config-sections-for-completion)"
}
# Completes possible values of various configuration variables.
@@ -2543,7 +2550,7 @@ __git_complete_config_variable_value ()
return
;;
branch.*.rebase)
- __gitcomp "false true merges preserve interactive" "" "$cur_"
+ __gitcomp "false true merges interactive" "" "$cur_"
return
;;
remote.pushdefault)
@@ -2650,10 +2657,10 @@ __git_complete_config_variable_name ()
return
;;
branch.*)
- local pfx="${cur%.*}."
- cur_="${cur#*.}"
+ local pfx="${cur_%.*}."
+ cur_="${cur_#*.}"
__gitcomp_direct "$(__git_heads "$pfx" "$cur_" ".")"
- __gitcomp_nl_append $'autoSetupMerge\nautoSetupRebase\n' "$pfx" "$cur_" "$sfx"
+ __gitcomp_nl_append $'autoSetupMerge\nautoSetupRebase\n' "$pfx" "$cur_" "${sfx- }"
return
;;
guitool.*.*)
@@ -2687,7 +2694,7 @@ __git_complete_config_variable_name ()
local pfx="${cur_%.*}."
cur_="${cur_#*.}"
__git_compute_all_commands
- __gitcomp_nl "$__git_all_commands" "$pfx" "$cur_" "$sfx"
+ __gitcomp_nl "$__git_all_commands" "$pfx" "$cur_" "${sfx- }"
return
;;
remote.*.*)
@@ -2703,7 +2710,7 @@ __git_complete_config_variable_name ()
local pfx="${cur_%.*}."
cur_="${cur_#*.}"
__gitcomp_nl "$(__git_remotes)" "$pfx" "$cur_" "."
- __gitcomp_nl_append "pushDefault" "$pfx" "$cur_" "$sfx"
+ __gitcomp_nl_append "pushDefault" "$pfx" "$cur_" "${sfx- }"
return
;;
url.*.*)
@@ -2717,16 +2724,8 @@ __git_complete_config_variable_name ()
__gitcomp "$__git_config_vars" "" "$cur_" "$sfx"
;;
*)
- __git_compute_config_vars
- __gitcomp "$(echo "$__git_config_vars" |
- awk -F . '{
- sections[$1] = 1
- }
- END {
- for (s in sections)
- print s "."
- }
- ')" "" "$cur_"
+ __git_compute_config_sections
+ __gitcomp "$__git_config_sections" "" "$cur_" "."
;;
esac
}
diff --git a/contrib/completion/git-completion.tcsh b/contrib/completion/git-completion.tcsh
index 4a790d8f4e..ba797e5b3c 100644
--- a/contrib/completion/git-completion.tcsh
+++ b/contrib/completion/git-completion.tcsh
@@ -80,8 +80,9 @@ else
COMP_CWORD=\$((\${#COMP_WORDS[@]}-1))
fi
-# Call _git() or _gitk() of the bash script, based on the first argument
-_\${1}
+# Call __git_wrap__git_main() or __git_wrap__gitk_main() of the bash script,
+# based on the first argument
+__git_wrap__\${1}_main
IFS=\$'\n'
if [ \${#COMPREPLY[*]} -eq 0 ]; then
diff --git a/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c b/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c
index d389bfadce..5927e27ae6 100644
--- a/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c
+++ b/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c
@@ -138,7 +138,7 @@ struct credential {
char *password;
};
-#define CREDENTIAL_INIT { NULL, NULL, 0, NULL, NULL, NULL }
+#define CREDENTIAL_INIT { 0 }
typedef int (*credential_op_cb)(struct credential *);
diff --git a/contrib/credential/libsecret/git-credential-libsecret.c b/contrib/credential/libsecret/git-credential-libsecret.c
index e6598b6383..2c5d76d789 100644
--- a/contrib/credential/libsecret/git-credential-libsecret.c
+++ b/contrib/credential/libsecret/git-credential-libsecret.c
@@ -41,7 +41,7 @@ struct credential {
char *password;
};
-#define CREDENTIAL_INIT { NULL, NULL, 0, NULL, NULL, NULL }
+#define CREDENTIAL_INIT { 0 }
typedef int (*credential_op_cb)(struct credential *);
diff --git a/contrib/rerere-train.sh b/contrib/rerere-train.sh
index eeee45dd34..75125d6ae0 100755
--- a/contrib/rerere-train.sh
+++ b/contrib/rerere-train.sh
@@ -91,7 +91,7 @@ do
git checkout -q $commit -- .
git rerere
fi
- git reset -q --hard
+ git reset -q --hard # Might nuke untracked files...
done
if test -z "$branch"
diff --git a/credential.c b/credential.c
index 3c05c7c669..000ac7a8d4 100644
--- a/credential.c
+++ b/credential.c
@@ -128,6 +128,7 @@ static void credential_apply_config(struct credential *c)
normalized_url = url_normalize(url.buf, &config.url);
git_config(urlmatch_config_entry, &config);
+ string_list_clear(&config.vars, 1);
free(normalized_url);
strbuf_release(&url);
diff --git a/csum-file.c b/csum-file.c
index c951cf8277..26e8a6df44 100644
--- a/csum-file.c
+++ b/csum-file.c
@@ -131,12 +131,8 @@ struct hashfile *hashfd_check(const char *name)
int sink, check;
struct hashfile *f;
- sink = open("/dev/null", O_WRONLY);
- if (sink < 0)
- die_errno("unable to open /dev/null");
- check = open(name, O_RDONLY);
- if (check < 0)
- die_errno("unable to open '%s'", name);
+ sink = xopen("/dev/null", O_WRONLY);
+ check = xopen(name, O_RDONLY);
f = hashfd(sink, name);
f->check_fd = check;
f->check_buffer = xmalloc(f->buffer_len);
diff --git a/detect-compiler b/detect-compiler
index 70b754481c..11d60da5b7 100755
--- a/detect-compiler
+++ b/detect-compiler
@@ -13,11 +13,11 @@ get_version_line() {
}
get_family() {
- get_version_line | sed 's/^\(.*\) version [0-9][^ ]* .*/\1/'
+ get_version_line | sed 's/^\(.*\) version [0-9].*/\1/'
}
get_version() {
- get_version_line | sed 's/^.* version \([0-9][^ ]*\) .*/\1/'
+ get_version_line | sed 's/^.* version \([0-9][^ ]*\).*/\1/'
}
print_flags() {
@@ -38,10 +38,7 @@ case "$(get_family)" in
gcc)
print_flags gcc
;;
-clang)
- print_flags clang
- ;;
-"FreeBSD clang")
+clang | *" clang")
print_flags clang
;;
"Apple LLVM")
diff --git a/diff-lib.c b/diff-lib.c
index f9eadc4fc1..ca085a03ef 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -117,6 +117,10 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
if (!ce_path_match(istate, ce, &revs->prune_data, NULL))
continue;
+ if (revs->diffopt.prefix &&
+ strncmp(ce->name, revs->diffopt.prefix, revs->diffopt.prefix_length))
+ continue;
+
if (ce_stage(ce)) {
struct combine_diff_path *dpath;
struct diff_filepair *pair;
diff --git a/diff-merges.c b/diff-merges.c
index d897fd8a29..5060ccd890 100644
--- a/diff-merges.c
+++ b/diff-merges.c
@@ -6,7 +6,7 @@ typedef void (*diff_merges_setup_func_t)(struct rev_info *);
static void set_separate(struct rev_info *revs);
static diff_merges_setup_func_t set_to_default = set_separate;
-static int suppress_parsing;
+static int suppress_m_parsing;
static void suppress(struct rev_info *revs)
{
@@ -91,9 +91,9 @@ int diff_merges_config(const char *value)
return 0;
}
-void diff_merges_suppress_options_parsing(void)
+void diff_merges_suppress_m_parsing(void)
{
- suppress_parsing = 1;
+ suppress_m_parsing = 1;
}
int diff_merges_parse_opts(struct rev_info *revs, const char **argv)
@@ -102,10 +102,7 @@ int diff_merges_parse_opts(struct rev_info *revs, const char **argv)
const char *optarg;
const char *arg = argv[0];
- if (suppress_parsing)
- return 0;
-
- if (!strcmp(arg, "-m")) {
+ if (!suppress_m_parsing && !strcmp(arg, "-m")) {
set_to_default(revs);
} else if (!strcmp(arg, "-c")) {
set_combined(revs);
@@ -153,9 +150,6 @@ void diff_merges_set_dense_combined_if_unset(struct rev_info *revs)
void diff_merges_setup_revs(struct rev_info *revs)
{
- if (suppress_parsing)
- return;
-
if (revs->combine_merges == 0)
revs->dense_combined_merges = 0;
if (revs->separate_merges == 0)
diff --git a/diff-merges.h b/diff-merges.h
index b5d57f6563..19639689bb 100644
--- a/diff-merges.h
+++ b/diff-merges.h
@@ -11,7 +11,7 @@ struct rev_info;
int diff_merges_config(const char *value);
-void diff_merges_suppress_options_parsing(void);
+void diff_merges_suppress_m_parsing(void);
int diff_merges_parse_opts(struct rev_info *revs, const char **argv);
diff --git a/diff.c b/diff.c
index a8113f1707..861282db1c 100644
--- a/diff.c
+++ b/diff.c
@@ -26,6 +26,7 @@
#include "parse-options.h"
#include "help.h"
#include "promisor-remote.h"
+#include "dir.h"
#ifdef NO_FAST_WORKING_DIRECTORY
#define FAST_WORKING_DIRECTORY 0
@@ -774,13 +775,13 @@ struct emitted_diff_symbol {
int indent_width; /* The visual width of the indentation */
enum diff_symbol s;
};
-#define EMITTED_DIFF_SYMBOL_INIT {NULL}
+#define EMITTED_DIFF_SYMBOL_INIT { 0 }
struct emitted_diff_symbols {
struct emitted_diff_symbol *buf;
int nr, alloc;
};
-#define EMITTED_DIFF_SYMBOLS_INIT {NULL, 0, 0}
+#define EMITTED_DIFF_SYMBOLS_INIT { 0 }
static void append_emitted_diff_symbol(struct diff_options *o,
struct emitted_diff_symbol *e)
@@ -3907,6 +3908,13 @@ static int reuse_worktree_file(struct index_state *istate,
if (!want_file && would_convert_to_git(istate, name))
return 0;
+ /*
+ * If this path does not match our sparse-checkout definition,
+ * then the file will not be in the working directory.
+ */
+ if (!path_in_sparse_checkout(name, istate))
+ return 0;
+
len = strlen(name);
pos = index_name_pos(istate, name, len);
if (pos < 0)
diff --git a/diffcore-rename.c b/diffcore-rename.c
index c95857b51f..bebd4ed6a4 100644
--- a/diffcore-rename.c
+++ b/diffcore-rename.c
@@ -317,10 +317,11 @@ static int find_identical_files(struct hashmap *srcs,
}
static void insert_file_table(struct repository *r,
+ struct mem_pool *pool,
struct hashmap *table, int index,
struct diff_filespec *filespec)
{
- struct file_similarity *entry = xmalloc(sizeof(*entry));
+ struct file_similarity *entry = mem_pool_alloc(pool, sizeof(*entry));
entry->index = index;
entry->filespec = filespec;
@@ -336,7 +337,8 @@ static void insert_file_table(struct repository *r,
* and then during the second round we try to match
* cache-dirty entries as well.
*/
-static int find_exact_renames(struct diff_options *options)
+static int find_exact_renames(struct diff_options *options,
+ struct mem_pool *pool)
{
int i, renames = 0;
struct hashmap file_table;
@@ -346,7 +348,7 @@ static int find_exact_renames(struct diff_options *options)
*/
hashmap_init(&file_table, NULL, NULL, rename_src_nr);
for (i = rename_src_nr-1; i >= 0; i--)
- insert_file_table(options->repo,
+ insert_file_table(options->repo, pool,
&file_table, i,
rename_src[i].p->one);
@@ -354,8 +356,8 @@ static int find_exact_renames(struct diff_options *options)
for (i = 0; i < rename_dst_nr; i++)
renames += find_identical_files(&file_table, i, options);
- /* Free the hash data structure and entries */
- hashmap_clear_and_free(&file_table, struct file_similarity, entry);
+ /* Free the hash data structure (entries will be freed with the pool) */
+ hashmap_clear(&file_table);
return renames;
}
@@ -1330,7 +1332,47 @@ static void handle_early_known_dir_renames(struct dir_rename_info *info,
rename_src_nr = new_num_src;
}
+static void free_filespec_data(struct diff_filespec *spec)
+{
+ if (!--spec->count)
+ diff_free_filespec_data(spec);
+}
+
+static void pool_free_filespec(struct mem_pool *pool,
+ struct diff_filespec *spec)
+{
+ if (!pool) {
+ free_filespec(spec);
+ return;
+ }
+
+ /*
+ * Similar to free_filespec(), but only frees the data. The spec
+ * itself was allocated in the pool and should not be individually
+ * freed.
+ */
+ free_filespec_data(spec);
+}
+
+void pool_diff_free_filepair(struct mem_pool *pool,
+ struct diff_filepair *p)
+{
+ if (!pool) {
+ diff_free_filepair(p);
+ return;
+ }
+
+ /*
+ * Similar to diff_free_filepair() but only frees the data from the
+ * filespecs; not the filespecs or the filepair which were
+ * allocated from the pool.
+ */
+ free_filespec_data(p->one);
+ free_filespec_data(p->two);
+}
+
void diffcore_rename_extended(struct diff_options *options,
+ struct mem_pool *pool,
struct strintmap *relevant_sources,
struct strintmap *dirs_removed,
struct strmap *dir_rename_count,
@@ -1345,6 +1387,7 @@ void diffcore_rename_extended(struct diff_options *options,
int num_destinations, dst_cnt;
int num_sources, want_copies;
struct progress *progress = NULL;
+ struct mem_pool local_pool;
struct dir_rename_info info;
struct diff_populate_filespec_options dpf_options = {
.check_binary = 0,
@@ -1413,11 +1456,18 @@ void diffcore_rename_extended(struct diff_options *options,
goto cleanup; /* nothing to do */
trace2_region_enter("diff", "exact renames", options->repo);
+ mem_pool_init(&local_pool, 32*1024);
/*
* We really want to cull the candidates list early
* with cheap tests in order to avoid doing deltas.
*/
- rename_count = find_exact_renames(options);
+ rename_count = find_exact_renames(options, &local_pool);
+ /*
+ * Discard local_pool immediately instead of at "cleanup:" in order
+ * to reduce maximum memory usage; inexact rename detection uses up
+ * a fair amount of memory, and mem_pools can too.
+ */
+ mem_pool_discard(&local_pool, 0);
trace2_region_leave("diff", "exact renames", options->repo);
/* Did we only want exact renames? */
@@ -1636,7 +1686,7 @@ void diffcore_rename_extended(struct diff_options *options,
pair_to_free = p;
if (pair_to_free)
- diff_free_filepair(pair_to_free);
+ pool_diff_free_filepair(pool, pair_to_free);
}
diff_debug_queue("done copying original", &outq);
@@ -1646,7 +1696,7 @@ void diffcore_rename_extended(struct diff_options *options,
for (i = 0; i < rename_dst_nr; i++)
if (rename_dst[i].filespec_to_free)
- free_filespec(rename_dst[i].filespec_to_free);
+ pool_free_filespec(pool, rename_dst[i].filespec_to_free);
cleanup_dir_rename_info(&info, dirs_removed, dir_rename_count != NULL);
FREE_AND_NULL(rename_dst);
@@ -1663,5 +1713,5 @@ void diffcore_rename_extended(struct diff_options *options,
void diffcore_rename(struct diff_options *options)
{
- diffcore_rename_extended(options, NULL, NULL, NULL, NULL);
+ diffcore_rename_extended(options, NULL, NULL, NULL, NULL, NULL);
}
diff --git a/diffcore.h b/diffcore.h
index 533b30e21e..badc2261c2 100644
--- a/diffcore.h
+++ b/diffcore.h
@@ -127,6 +127,8 @@ struct diff_filepair {
#define DIFF_PAIR_MODE_CHANGED(p) ((p)->one->mode != (p)->two->mode)
void diff_free_filepair(struct diff_filepair *);
+void pool_diff_free_filepair(struct mem_pool *pool,
+ struct diff_filepair *p);
int diff_unmodified_pair(struct diff_filepair *);
@@ -179,6 +181,7 @@ void partial_clear_dir_rename_count(struct strmap *dir_rename_count);
void diffcore_break(struct repository *, int);
void diffcore_rename(struct diff_options *);
void diffcore_rename_extended(struct diff_options *options,
+ struct mem_pool *pool,
struct strintmap *relevant_sources,
struct strintmap *dirs_removed,
struct strmap *dir_rename_count,
diff --git a/dir.c b/dir.c
index 03c4d21267..a4306ab874 100644
--- a/dir.c
+++ b/dir.c
@@ -1294,7 +1294,7 @@ int match_pathname(const char *pathname, int pathlen,
* then our prefix match is all we need; we
* do not need to call fnmatch at all.
*/
- if (!patternlen && !namelen)
+ if (!patternlen && (!namelen || (flags & PATTERN_FLAG_MUSTBEDIR)))
return 1;
}
@@ -1303,6 +1303,44 @@ int match_pathname(const char *pathname, int pathlen,
WM_PATHNAME) == 0;
}
+static int path_matches_dir_pattern(const char *pathname,
+ int pathlen,
+ struct strbuf **path_parent,
+ int *dtype,
+ struct path_pattern *pattern,
+ struct index_state *istate)
+{
+ if (!*path_parent) {
+ char *slash;
+ CALLOC_ARRAY(*path_parent, 1);
+ strbuf_add(*path_parent, pathname, pathlen);
+ slash = find_last_dir_sep((*path_parent)->buf);
+
+ if (slash)
+ strbuf_setlen(*path_parent, slash - (*path_parent)->buf);
+ else
+ strbuf_setlen(*path_parent, 0);
+ }
+
+ /*
+ * If the parent directory matches the pattern, then we do not
+ * need to check for dtype.
+ */
+ if ((*path_parent)->len &&
+ match_pathname((*path_parent)->buf, (*path_parent)->len,
+ pattern->base,
+ pattern->baselen ? pattern->baselen - 1 : 0,
+ pattern->pattern, pattern->nowildcardlen,
+ pattern->patternlen, pattern->flags))
+ return 1;
+
+ *dtype = resolve_dtype(*dtype, istate, pathname, pathlen);
+ if (*dtype != DT_DIR)
+ return 0;
+
+ return 1;
+}
+
/*
* Scan the given exclude list in reverse to see whether pathname
* should be ignored. The first match (i.e. the last on the list), if
@@ -1318,6 +1356,7 @@ static struct path_pattern *last_matching_pattern_from_list(const char *pathname
{
struct path_pattern *res = NULL; /* undecided */
int i;
+ struct strbuf *path_parent = NULL;
if (!pl->nr)
return NULL; /* undefined */
@@ -1327,11 +1366,10 @@ static struct path_pattern *last_matching_pattern_from_list(const char *pathname
const char *exclude = pattern->pattern;
int prefix = pattern->nowildcardlen;
- if (pattern->flags & PATTERN_FLAG_MUSTBEDIR) {
- *dtype = resolve_dtype(*dtype, istate, pathname, pathlen);
- if (*dtype != DT_DIR)
- continue;
- }
+ if (pattern->flags & PATTERN_FLAG_MUSTBEDIR &&
+ !path_matches_dir_pattern(pathname, pathlen, &path_parent,
+ dtype, pattern, istate))
+ continue;
if (pattern->flags & PATTERN_FLAG_NODIR) {
if (match_basename(basename,
@@ -1355,6 +1393,12 @@ static struct path_pattern *last_matching_pattern_from_list(const char *pathname
break;
}
}
+
+ if (path_parent) {
+ strbuf_release(path_parent);
+ free(path_parent);
+ }
+
return res;
}
@@ -1439,6 +1483,58 @@ done:
return result;
}
+int init_sparse_checkout_patterns(struct index_state *istate)
+{
+ if (!core_apply_sparse_checkout)
+ return 1;
+ if (istate->sparse_checkout_patterns)
+ return 0;
+
+ CALLOC_ARRAY(istate->sparse_checkout_patterns, 1);
+
+ if (get_sparse_checkout_patterns(istate->sparse_checkout_patterns) < 0) {
+ FREE_AND_NULL(istate->sparse_checkout_patterns);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int path_in_sparse_checkout_1(const char *path,
+ struct index_state *istate,
+ int require_cone_mode)
+{
+ const char *base;
+ int dtype = DT_REG;
+
+ /*
+ * We default to accepting a path if there are no patterns or
+ * they are of the wrong type.
+ */
+ if (init_sparse_checkout_patterns(istate) ||
+ (require_cone_mode &&
+ !istate->sparse_checkout_patterns->use_cone_patterns))
+ return 1;
+
+ base = strrchr(path, '/');
+ return path_matches_pattern_list(path, strlen(path), base ? base + 1 : path,
+ &dtype,
+ istate->sparse_checkout_patterns,
+ istate) > 0;
+}
+
+int path_in_sparse_checkout(const char *path,
+ struct index_state *istate)
+{
+ return path_in_sparse_checkout_1(path, istate, 0);
+}
+
+int path_in_cone_mode_sparse_checkout(const char *path,
+ struct index_state *istate)
+{
+ return path_in_sparse_checkout_1(path, istate, 1);
+}
+
static struct path_pattern *last_matching_pattern_from_lists(
struct dir_struct *dir, struct index_state *istate,
const char *pathname, int pathlen,
@@ -2970,6 +3066,120 @@ int is_empty_dir(const char *path)
return ret;
}
+char *git_url_basename(const char *repo, int is_bundle, int is_bare)
+{
+ const char *end = repo + strlen(repo), *start, *ptr;
+ size_t len;
+ char *dir;
+
+ /*
+ * Skip scheme.
+ */
+ start = strstr(repo, "://");
+ if (start == NULL)
+ start = repo;
+ else
+ start += 3;
+
+ /*
+ * Skip authentication data. The stripping does happen
+ * greedily, such that we strip up to the last '@' inside
+ * the host part.
+ */
+ for (ptr = start; ptr < end && !is_dir_sep(*ptr); ptr++) {
+ if (*ptr == '@')
+ start = ptr + 1;
+ }
+
+ /*
+ * Strip trailing spaces, slashes and /.git
+ */
+ while (start < end && (is_dir_sep(end[-1]) || isspace(end[-1])))
+ end--;
+ if (end - start > 5 && is_dir_sep(end[-5]) &&
+ !strncmp(end - 4, ".git", 4)) {
+ end -= 5;
+ while (start < end && is_dir_sep(end[-1]))
+ end--;
+ }
+
+ /*
+ * Strip trailing port number if we've got only a
+ * hostname (that is, there is no dir separator but a
+ * colon). This check is required such that we do not
+ * strip URI's like '/foo/bar:2222.git', which should
+ * result in a dir '2222' being guessed due to backwards
+ * compatibility.
+ */
+ if (memchr(start, '/', end - start) == NULL
+ && memchr(start, ':', end - start) != NULL) {
+ ptr = end;
+ while (start < ptr && isdigit(ptr[-1]) && ptr[-1] != ':')
+ ptr--;
+ if (start < ptr && ptr[-1] == ':')
+ end = ptr - 1;
+ }
+
+ /*
+ * Find last component. To remain backwards compatible we
+ * also regard colons as path separators, such that
+ * cloning a repository 'foo:bar.git' would result in a
+ * directory 'bar' being guessed.
+ */
+ ptr = end;
+ while (start < ptr && !is_dir_sep(ptr[-1]) && ptr[-1] != ':')
+ ptr--;
+ start = ptr;
+
+ /*
+ * Strip .{bundle,git}.
+ */
+ len = end - start;
+ strip_suffix_mem(start, &len, is_bundle ? ".bundle" : ".git");
+
+ if (!len || (len == 1 && *start == '/'))
+ die(_("No directory name could be guessed.\n"
+ "Please specify a directory on the command line"));
+
+ if (is_bare)
+ dir = xstrfmt("%.*s.git", (int)len, start);
+ else
+ dir = xstrndup(start, len);
+ /*
+ * Replace sequences of 'control' characters and whitespace
+ * with one ascii space, remove leading and trailing spaces.
+ */
+ if (*dir) {
+ char *out = dir;
+ int prev_space = 1 /* strip leading whitespace */;
+ for (end = dir; *end; ++end) {
+ char ch = *end;
+ if ((unsigned char)ch < '\x20')
+ ch = '\x20';
+ if (isspace(ch)) {
+ if (prev_space)
+ continue;
+ prev_space = 1;
+ } else
+ prev_space = 0;
+ *out++ = ch;
+ }
+ *out = '\0';
+ if (out > dir && prev_space)
+ out[-1] = '\0';
+ }
+ return dir;
+}
+
+void strip_dir_trailing_slashes(char *dir)
+{
+ char *end = dir + strlen(dir);
+
+ while (dir < end - 1 && is_dir_sep(end[-1]))
+ end--;
+ *end = '\0';
+}
+
static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
{
DIR *dir;
@@ -3633,7 +3843,7 @@ static void connect_wt_gitdir_in_nested(const char *sub_worktree,
strbuf_reset(&sub_wt);
strbuf_reset(&sub_gd);
strbuf_addf(&sub_wt, "%s/%s", sub_worktree, sub->path);
- strbuf_addf(&sub_gd, "%s/modules/%s", sub_gitdir, sub->name);
+ submodule_name_to_gitdir(&sub_gd, &subrepo, sub->name);
connect_work_tree_and_git_dir(sub_wt.buf, sub_gd.buf, 1);
}
diff --git a/dir.h b/dir.h
index b3e1a54a97..83f46c0fb4 100644
--- a/dir.h
+++ b/dir.h
@@ -394,6 +394,14 @@ enum pattern_match_result path_matches_pattern_list(const char *pathname,
const char *basename, int *dtype,
struct pattern_list *pl,
struct index_state *istate);
+
+int init_sparse_checkout_patterns(struct index_state *state);
+
+int path_in_sparse_checkout(const char *path,
+ struct index_state *istate);
+int path_in_cone_mode_sparse_checkout(const char *path,
+ struct index_state *istate);
+
struct dir_entry *dir_add_ignored(struct dir_struct *dir,
struct index_state *istate,
const char *pathname, int len);
@@ -453,6 +461,17 @@ static inline int is_dot_or_dotdot(const char *name)
int is_empty_dir(const char *dir);
+/*
+ * Retrieve the "humanish" basename of the given Git URL.
+ *
+ * For example:
+ * /path/to/repo.git => "repo"
+ * host.xz:foo/.git => "foo"
+ * http://example.com/user/bar.baz => "bar.baz"
+ */
+char *git_url_basename(const char *repo, int is_bundle, int is_bare);
+void strip_dir_trailing_slashes(char *dir);
+
void setup_standard_excludes(struct dir_struct *dir);
char *get_sparse_checkout_filename(void);
diff --git a/editor.c b/editor.c
index 6303ae0ab0..fdd3eeafa9 100644
--- a/editor.c
+++ b/editor.c
@@ -58,7 +58,7 @@ static int launch_specified_editor(const char *editor, const char *path,
const char *args[] = { editor, NULL, NULL };
struct child_process p = CHILD_PROCESS_INIT;
int ret, sig;
- int print_waiting_for_editor = advice_waiting_for_editor && isatty(2);
+ int print_waiting_for_editor = advice_enabled(ADVICE_WAITING_FOR_EDITOR) && isatty(2);
if (print_waiting_for_editor) {
/*
diff --git a/entry.c b/entry.c
index 125fabdbd5..9b0f968a70 100644
--- a/entry.c
+++ b/entry.c
@@ -159,25 +159,25 @@ static int remove_available_paths(struct string_list_item *item, void *cb_data)
return !available;
}
-int finish_delayed_checkout(struct checkout *state, int *nr_checkouts)
+int finish_delayed_checkout(struct checkout *state, int *nr_checkouts,
+ int show_progress)
{
int errs = 0;
- unsigned delayed_object_count;
+ unsigned processed_paths = 0;
off_t filtered_bytes = 0;
struct string_list_item *filter, *path;
- struct progress *progress;
+ struct progress *progress = NULL;
struct delayed_checkout *dco = state->delayed_checkout;
if (!state->delayed_checkout)
return errs;
dco->state = CE_RETRY;
- delayed_object_count = dco->paths.nr;
- progress = start_delayed_progress(_("Filtering content"), delayed_object_count);
+ if (show_progress)
+ progress = start_delayed_progress(_("Filtering content"), dco->paths.nr);
while (dco->filters.nr > 0) {
for_each_string_list_item(filter, &dco->filters) {
struct string_list available_paths = STRING_LIST_INIT_NODUP;
- display_progress(progress, delayed_object_count - dco->paths.nr);
if (!async_query_available_blobs(filter->string, &available_paths)) {
/* Filter reported an error */
@@ -224,6 +224,7 @@ int finish_delayed_checkout(struct checkout *state, int *nr_checkouts)
ce = index_file_exists(state->istate, path->string,
strlen(path->string), 0);
if (ce) {
+ display_progress(progress, ++processed_paths);
errs |= checkout_entry(ce, state, NULL, nr_checkouts);
filtered_bytes += ce->ce_stat_data.sd_size;
display_throughput(progress, filtered_bytes);
diff --git a/entry.h b/entry.h
index b8c0e170dc..2254c62727 100644
--- a/entry.h
+++ b/entry.h
@@ -16,7 +16,7 @@ struct checkout {
clone:1,
refresh_cache:1;
};
-#define CHECKOUT_INIT { NULL, "" }
+#define CHECKOUT_INIT { .base_dir = "" }
#define TEMPORARY_FILENAME_LENGTH 25
/*
@@ -43,7 +43,8 @@ static inline int checkout_entry(struct cache_entry *ce,
}
void enable_delayed_checkout(struct checkout *state);
-int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);
+int finish_delayed_checkout(struct checkout *state, int *nr_checkouts,
+ int show_progress);
/*
* Unlink the last component and schedule the leading directories for
diff --git a/environment.c b/environment.c
index d6b22ede7e..9da7f3c1a1 100644
--- a/environment.c
+++ b/environment.c
@@ -31,7 +31,6 @@ int prefer_symlink_refs;
int is_bare_repository_cfg = -1; /* unspecified */
int warn_ambiguous_refs = 1;
int warn_on_object_refname_ambiguity = 1;
-int ref_paranoia = -1;
int repository_format_precious_objects;
int repository_format_worktree_config;
const char *git_commit_encoding;
@@ -41,7 +40,6 @@ char *apply_default_ignorewhitespace;
const char *git_attributes_file;
const char *git_hooks_path;
int zlib_compression_level = Z_BEST_SPEED;
-int core_compression_level;
int pack_compression_level = Z_DEFAULT_COMPRESSION;
int fsync_object_files;
size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE;
@@ -96,13 +94,6 @@ int auto_comment_line_char;
/* Parallel index stat data preload? */
int core_preload_index = 1;
-/*
- * This is a hack for test programs like test-dump-untracked-cache to
- * ensure that they do not modify the untracked cache when reading it.
- * Do not use it otherwise!
- */
-int ignore_untracked_cache_config;
-
/* This is set by setup_git_dir_gently() and/or git_default_config() */
char *git_work_tree_cfg;
@@ -330,8 +321,7 @@ char *get_graft_file(struct repository *r)
static void set_git_dir_1(const char *path)
{
- if (setenv(GIT_DIR_ENVIRONMENT, path, 1))
- die(_("could not set GIT_DIR to '%s'"), path);
+ xsetenv(GIT_DIR_ENVIRONMENT, path, 1);
setup_git_env(path);
}
diff --git a/fetch-negotiator.c b/fetch-negotiator.c
index 57ed5784e1..273390229f 100644
--- a/fetch-negotiator.c
+++ b/fetch-negotiator.c
@@ -19,7 +19,6 @@ void fetch_negotiator_init(struct repository *r,
return;
case FETCH_NEGOTIATION_DEFAULT:
- default:
default_negotiator_init(negotiator);
return;
}
diff --git a/fetch-pack.c b/fetch-pack.c
index b0c7be717c..a9604f35a3 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -119,6 +119,11 @@ static struct commit *deref_without_lazy_fetch(const struct object_id *oid,
{
enum object_type type;
struct object_info info = { .typep = &type };
+ struct commit *commit;
+
+ commit = lookup_commit_in_graph(the_repository, oid);
+ if (commit)
+ return commit;
while (1) {
if (oid_object_info_extended(the_repository, oid, &info,
@@ -137,8 +142,14 @@ static struct commit *deref_without_lazy_fetch(const struct object_id *oid,
break;
}
}
- if (type == OBJ_COMMIT)
- return (struct commit *) parse_object(the_repository, oid);
+
+ if (type == OBJ_COMMIT) {
+ struct commit *commit = lookup_commit(the_repository, oid);
+ if (!commit || repo_parse_commit(the_repository, commit))
+ return NULL;
+ return commit;
+ }
+
return NULL;
}
@@ -1906,16 +1917,15 @@ static void update_shallow(struct fetch_pack_args *args,
oid_array_clear(&ref);
}
-static int iterate_ref_map(void *cb_data, struct object_id *oid)
+static const struct object_id *iterate_ref_map(void *cb_data)
{
struct ref **rm = cb_data;
struct ref *ref = *rm;
if (!ref)
- return -1; /* end of the list */
+ return NULL;
*rm = ref->next;
- oidcpy(oid, &ref->old_oid);
- return 0;
+ return &ref->old_oid;
}
struct ref *fetch_pack(struct fetch_pack_args *args,
diff --git a/generate-hooklist.sh b/generate-hooklist.sh
new file mode 100755
index 0000000000..2f9f54eb54
--- /dev/null
+++ b/generate-hooklist.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+#
+# Usage: ./generate-hooklist.sh >hook-list.h
+
+cat <<EOF
+/* Automatically generated by generate-hooklist.sh */
+
+static const char *hook_name_list[] = {
+EOF
+
+sed -n \
+ -e '/^~~~~*$/ {x; s/^.*$/ "&",/; p;}' \
+ -e 'x' \
+ <Documentation/githooks.txt |
+ LC_ALL=C sort
+
+cat <<EOF
+ NULL,
+};
+EOF
diff --git a/gettext.h b/gettext.h
index c8b34fd612..d209911ebb 100644
--- a/gettext.h
+++ b/gettext.h
@@ -55,31 +55,7 @@ const char *Q_(const char *msgid, const char *plu, unsigned long n)
}
/* Mark msgid for translation but do not translate it. */
-#if !USE_PARENS_AROUND_GETTEXT_N
#define N_(msgid) msgid
-#else
-/*
- * Strictly speaking, this will lead to invalid C when
- * used this way:
- * static const char s[] = N_("FOO");
- * which will expand to
- * static const char s[] = ("FOO");
- * and in valid C, the initializer on the right hand side must
- * be without the parentheses. But many compilers do accept it
- * as a language extension and it will allow us to catch mistakes
- * like:
- * static const char *msgs[] = {
- * N_("one")
- * N_("two"),
- * N_("three"),
- * NULL
- * };
- * (notice the missing comma on one of the lines) by forcing
- * a compilation error, because parenthesised ("one") ("two")
- * will not get silently turned into ("onetwo").
- */
-#define N_(msgid) (msgid)
-#endif
const char *get_preferred_languages(void);
int is_utf8_locale(void);
diff --git a/git-bisect.sh b/git-bisect.sh
index 6a7afaea8d..405cf76f2a 100755
--- a/git-bisect.sh
+++ b/git-bisect.sh
@@ -34,94 +34,9 @@ Please use "git help bisect" to get the full man page.'
OPTIONS_SPEC=
. git-sh-setup
-_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
-_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
TERM_BAD=bad
TERM_GOOD=good
-bisect_visualize() {
- git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD fail || exit
-
- if test $# = 0
- then
- if test -n "${DISPLAY+set}${SESSIONNAME+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" &&
- type gitk >/dev/null 2>&1
- then
- set gitk
- else
- set git log
- fi
- else
- case "$1" in
- git*|tig) ;;
- -*) set git log "$@" ;;
- *) set git "$@" ;;
- esac
- fi
-
- eval '"$@"' --bisect -- $(cat "$GIT_DIR/BISECT_NAMES")
-}
-
-bisect_run () {
- git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD fail || exit
-
- test -n "$*" || die "$(gettext "bisect run failed: no command provided.")"
-
- while true
- do
- command="$@"
- eval_gettextln "running \$command"
- "$@"
- res=$?
-
- # Check for really bad run error.
- if [ $res -lt 0 -o $res -ge 128 ]
- then
- eval_gettextln "bisect run failed:
-exit code \$res from '\$command' is < 0 or >= 128" >&2
- exit $res
- fi
-
- # Find current state depending on run success or failure.
- # A special exit code of 125 means cannot test.
- if [ $res -eq 125 ]
- then
- state='skip'
- elif [ $res -gt 0 ]
- then
- state="$TERM_BAD"
- else
- state="$TERM_GOOD"
- fi
-
- git bisect--helper --bisect-state $state >"$GIT_DIR/BISECT_RUN"
- res=$?
-
- cat "$GIT_DIR/BISECT_RUN"
-
- if sane_grep "first $TERM_BAD commit could be any of" "$GIT_DIR/BISECT_RUN" \
- >/dev/null
- then
- gettextln "bisect run cannot continue any more" >&2
- exit $res
- fi
-
- if [ $res -ne 0 ]
- then
- eval_gettextln "bisect run failed:
-'bisect-state \$state' exited with error code \$res" >&2
- exit $res
- fi
-
- if sane_grep "is the first $TERM_BAD commit" "$GIT_DIR/BISECT_RUN" >/dev/null
- then
- gettextln "bisect run success"
- exit 0;
- fi
-
- done
-}
-
get_terms () {
if test -s "$GIT_DIR/BISECT_TERMS"
then
@@ -152,7 +67,7 @@ case "$#" in
# Not sure we want "next" at the UI level anymore.
git bisect--helper --bisect-next "$@" || exit ;;
visualize|view)
- bisect_visualize "$@" ;;
+ git bisect--helper --bisect-visualize "$@" || exit;;
reset)
git bisect--helper --bisect-reset "$@" ;;
replay)
@@ -160,7 +75,7 @@ case "$#" in
log)
git bisect--helper --bisect-log || exit ;;
run)
- bisect_run "$@" ;;
+ git bisect--helper --bisect-run "$@" || exit;;
terms)
git bisect--helper --bisect-terms "$@" || exit;;
*)
diff --git a/git-compat-util.h b/git-compat-util.h
index b46605300a..141bb86351 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -160,6 +160,9 @@
# endif
#define WIN32_LEAN_AND_MEAN /* stops windows.h including winsock.h */
#include <winsock2.h>
+#ifndef NO_UNIX_SOCKETS
+#include <afunix.h>
+#endif
#include <windows.h>
#define GIT_WINDOWS_NATIVE
#endif
@@ -875,6 +878,8 @@ void *xmemdupz(const void *data, size_t len);
char *xstrndup(const char *str, size_t len);
void *xrealloc(void *ptr, size_t size);
void *xcalloc(size_t nmemb, size_t size);
+void xsetenv(const char *name, const char *value, int overwrite);
+void xunsetenv(const char *name);
void *xmmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
const char *mmap_os_err(void);
void *xmmap_gently(void *start, size_t length, int prot, int flags, int fd, off_t offset);
@@ -1253,10 +1258,6 @@ int warn_on_fopen_errors(const char *path);
*/
int open_nofollow(const char *path, int flags);
-#if !defined(USE_PARENS_AROUND_GETTEXT_N) && defined(__GNUC__)
-#define USE_PARENS_AROUND_GETTEXT_N 1
-#endif
-
#ifndef SHELL_PATH
# define SHELL_PATH "/bin/sh"
#endif
diff --git a/git-curl-compat.h b/git-curl-compat.h
new file mode 100644
index 0000000000..56a83b6bbd
--- /dev/null
+++ b/git-curl-compat.h
@@ -0,0 +1,129 @@
+#ifndef GIT_CURL_COMPAT_H
+#define GIT_CURL_COMPAT_H
+#include <curl/curl.h>
+
+/**
+ * This header centralizes the declaration of our libcurl dependencies
+ * to make it easy to discover the oldest versions we support, and to
+ * inform decisions about removing support for older libcurl in the
+ * future.
+ *
+ * The oldest supported version of curl is documented in the "INSTALL"
+ * document.
+ *
+ * The source of truth for what versions have which symbols is
+ * https://github.com/curl/curl/blob/master/docs/libcurl/symbols-in-versions;
+ * the release dates are taken from curl.git (at
+ * https://github.com/curl/curl/).
+ *
+ * For each X symbol we need from curl we define our own
+ * GIT_CURL_HAVE_X. If multiple similar symbols with the same prefix
+ * were defined in the same version we pick one and check for that name.
+ *
+ * We may also define a missing CURL_* symbol to its known value, if
+ * doing so is sufficient to add support for it to older versions that
+ * don't have it.
+ *
+ * Keep any symbols in date order of when their support was
+ * introduced, oldest first, in the official version of cURL library.
+ */
+
+/**
+ * CURL_SOCKOPT_OK was added in 7.21.5, released in April 2011.
+ */
+#if LIBCURL_VERSION_NUM < 0x071505
+#define CURL_SOCKOPT_OK 0
+#endif
+
+/**
+ * CURLOPT_TCP_KEEPALIVE was added in 7.25.0, released in March 2012.
+ */
+#if LIBCURL_VERSION_NUM >= 0x071900
+#define GITCURL_HAVE_CURLOPT_TCP_KEEPALIVE 1
+#endif
+
+
+/**
+ * CURLOPT_LOGIN_OPTIONS was added in 7.34.0, released in December
+ * 2013.
+ *
+ * If we start requiring 7.34.0 we might also be able to remove the
+ * code conditional on USE_CURL_FOR_IMAP_SEND in imap-send.c, see
+ * 1e16b255b95 (git-imap-send: use libcurl for implementation,
+ * 2014-11-09) and the check it added for "072200" in the Makefile.
+
+ */
+#if LIBCURL_VERSION_NUM >= 0x072200
+#define GIT_CURL_HAVE_CURLOPT_LOGIN_OPTIONS 1
+#endif
+
+/**
+ * CURL_SSLVERSION_TLSv1_[012] was added in 7.34.0, released in
+ * December 2013.
+ */
+#if LIBCURL_VERSION_NUM >= 0x072200
+#define GIT_CURL_HAVE_CURL_SSLVERSION_TLSv1_0
+#endif
+
+/**
+ * CURLOPT_PINNEDPUBLICKEY was added in 7.39.0, released in November
+ * 2014. CURLE_SSL_PINNEDPUBKEYNOTMATCH was added in that same version.
+ */
+#if LIBCURL_VERSION_NUM >= 0x072c00
+#define GIT_CURL_HAVE_CURLOPT_PINNEDPUBLICKEY 1
+#define GIT_CURL_HAVE_CURLE_SSL_PINNEDPUBKEYNOTMATCH 1
+#endif
+
+/**
+ * CURL_HTTP_VERSION_2 was added in 7.43.0, released in June 2015.
+ *
+ * The CURL_HTTP_VERSION_2 alias (but not CURL_HTTP_VERSION_2_0) has
+ * always been a macro, not an enum field (checked on curl version
+ * 7.78.0)
+ */
+#if LIBCURL_VERSION_NUM >= 0x072b00
+#define GIT_CURL_HAVE_CURL_HTTP_VERSION_2 1
+#endif
+
+/**
+ * CURLSSLOPT_NO_REVOKE was added in 7.44.0, released in August 2015.
+ *
+ * The CURLSSLOPT_NO_REVOKE is, has always been a macro, not an enum
+ * field (checked on curl version 7.78.0)
+ */
+#if LIBCURL_VERSION_NUM >= 0x072c00
+#define GIT_CURL_HAVE_CURLSSLOPT_NO_REVOKE 1
+#endif
+
+/**
+ * CURLOPT_PROXY_CAINFO was added in 7.52.0, released in August 2017.
+ */
+#if LIBCURL_VERSION_NUM >= 0x073400
+#define GIT_CURL_HAVE_CURLOPT_PROXY_CAINFO 1
+#endif
+
+/**
+ * CURLOPT_PROXY_{KEYPASSWD,SSLCERT,SSLKEY} was added in 7.52.0,
+ * released in August 2017.
+ */
+#if LIBCURL_VERSION_NUM >= 0x073400
+#define GIT_CURL_HAVE_CURLOPT_PROXY_KEYPASSWD 1
+#endif
+
+/**
+ * CURL_SSLVERSION_TLSv1_3 was added in 7.53.0, released in February
+ * 2017.
+ */
+#if LIBCURL_VERSION_NUM >= 0x073400
+#define GIT_CURL_HAVE_CURL_SSLVERSION_TLSv1_3 1
+#endif
+
+/**
+ * CURLSSLSET_{NO_BACKENDS,OK,TOO_LATE,UNKNOWN_BACKEND} were added in
+ * 7.56.0, released in September 2017.
+ */
+#if LIBCURL_VERSION_NUM >= 0x073800
+#define GIT_CURL_HAVE_CURLSSLSET_NO_BACKENDS
+#endif
+
+#endif
diff --git a/git-cvsserver.perl b/git-cvsserver.perl
index ed035f32c2..64319bed43 100755
--- a/git-cvsserver.perl
+++ b/git-cvsserver.perl
@@ -222,10 +222,11 @@ if ($state->{method} eq 'pserver') {
open my $passwd, "<", $authdb or die $!;
while (<$passwd>) {
if (m{^\Q$user\E:(.*)}) {
- if (crypt($user, descramble($password)) eq $1) {
+ my $hash = crypt(descramble($password), $1);
+ if (defined $hash and $hash eq $1) {
$auth_ok = 1;
}
- };
+ }
}
close $passwd;
diff --git a/git-rebase--preserve-merges.sh b/git-rebase--preserve-merges.sh
deleted file mode 100644
index b9c71d2a71..0000000000
--- a/git-rebase--preserve-merges.sh
+++ /dev/null
@@ -1,1057 +0,0 @@
-# This shell script fragment is sourced by git-rebase to implement its
-# preserve-merges mode.
-#
-# Copyright (c) 2006 Johannes E. Schindelin
-#
-# The file containing rebase commands, comments, and empty lines.
-# This file is created by "git rebase -i" then edited by the user. As
-# the lines are processed, they are removed from the front of this
-# file and written to the tail of $done.
-todo="$state_dir"/git-rebase-todo
-
-# The rebase command lines that have already been processed. A line
-# is moved here when it is first handled, before any associated user
-# actions.
-done="$state_dir"/done
-
-# The commit message that is planned to be used for any changes that
-# need to be committed following a user interaction.
-msg="$state_dir"/message
-
-# The file into which is accumulated the suggested commit message for
-# squash/fixup commands. When the first of a series of squash/fixups
-# is seen, the file is created and the commit message from the
-# previous commit and from the first squash/fixup commit are written
-# to it. The commit message for each subsequent squash/fixup commit
-# is appended to the file as it is processed.
-#
-# The first line of the file is of the form
-# # This is a combination of $count commits.
-# where $count is the number of commits whose messages have been
-# written to the file so far (including the initial "pick" commit).
-# Each time that a commit message is processed, this line is read and
-# updated. It is deleted just before the combined commit is made.
-squash_msg="$state_dir"/message-squash
-
-# If the current series of squash/fixups has not yet included a squash
-# command, then this file exists and holds the commit message of the
-# original "pick" commit. (If the series ends without a "squash"
-# command, then this can be used as the commit message of the combined
-# commit without opening the editor.)
-fixup_msg="$state_dir"/message-fixup
-
-# $rewritten is the name of a directory containing files for each
-# commit that is reachable by at least one merge base of $head and
-# $upstream. They are not necessarily rewritten, but their children
-# might be. This ensures that commits on merged, but otherwise
-# unrelated side branches are left alone. (Think "X" in the man page's
-# example.)
-rewritten="$state_dir"/rewritten
-
-dropped="$state_dir"/dropped
-
-end="$state_dir"/end
-msgnum="$state_dir"/msgnum
-
-# A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
-# GIT_AUTHOR_DATE that will be used for the commit that is currently
-# being rebased.
-author_script="$state_dir"/author-script
-
-# When an "edit" rebase command is being processed, the SHA1 of the
-# commit to be edited is recorded in this file. When "git rebase
-# --continue" is executed, if there are any staged changes then they
-# will be amended to the HEAD commit, but only provided the HEAD
-# commit is still the commit to be edited. When any other rebase
-# command is processed, this file is deleted.
-amend="$state_dir"/amend
-
-# For the post-rewrite hook, we make a list of rewritten commits and
-# their new sha1s. The rewritten-pending list keeps the sha1s of
-# commits that have been processed, but not committed yet,
-# e.g. because they are waiting for a 'squash' command.
-rewritten_list="$state_dir"/rewritten-list
-rewritten_pending="$state_dir"/rewritten-pending
-
-# Work around Git for Windows' Bash whose "read" does not strip CRLF
-# and leaves CR at the end instead.
-cr=$(printf "\015")
-
-resolvemsg="
-$(gettext 'Resolve all conflicts manually, mark them as resolved with
-"git add/rm <conflicted_files>", then run "git rebase --continue".
-You can instead skip this commit: run "git rebase --skip".
-To abort and get back to the state before "git rebase", run "git rebase --abort".')
-"
-
-write_basic_state () {
- echo "$head_name" > "$state_dir"/head-name &&
- echo "$onto" > "$state_dir"/onto &&
- echo "$orig_head" > "$state_dir"/orig-head &&
- test t = "$GIT_QUIET" && : > "$state_dir"/quiet
- test t = "$verbose" && : > "$state_dir"/verbose
- test -n "$strategy" && echo "$strategy" > "$state_dir"/strategy
- test -n "$strategy_opts" && echo "$strategy_opts" > \
- "$state_dir"/strategy_opts
- test -n "$allow_rerere_autoupdate" && echo "$allow_rerere_autoupdate" > \
- "$state_dir"/allow_rerere_autoupdate
- test -n "$gpg_sign_opt" && echo "$gpg_sign_opt" > "$state_dir"/gpg_sign_opt
- test -n "$signoff" && echo "$signoff" >"$state_dir"/signoff
- test -n "$reschedule_failed_exec" && : > "$state_dir"/reschedule-failed-exec
-}
-
-apply_autostash () {
- if test -f "$state_dir/autostash"
- then
- stash_sha1=$(cat "$state_dir/autostash")
- if git stash apply $stash_sha1 >/dev/null 2>&1
- then
- echo "$(gettext 'Applied autostash.')" >&2
- else
- git stash store -m "autostash" -q $stash_sha1 ||
- die "$(eval_gettext "Cannot store \$stash_sha1")"
- gettext '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.
-' >&2
- fi
- fi
-}
-
-output () {
- case "$verbose" in
- '')
- output=$("$@" 2>&1 )
- status=$?
- test $status != 0 && printf "%s\n" "$output"
- return $status
- ;;
- *)
- "$@"
- ;;
- esac
-}
-
-strategy_args=${strategy:+--strategy=$strategy}
-test -n "$strategy_opts" &&
-eval '
- for strategy_opt in '"$strategy_opts"'
- do
- strategy_args="$strategy_args -X$(git rev-parse --sq-quote "${strategy_opt#--}")"
- done
-'
-
-GIT_CHERRY_PICK_HELP="$resolvemsg"
-export GIT_CHERRY_PICK_HELP
-
-comment_char=$(git config --get core.commentchar 2>/dev/null)
-case "$comment_char" in
-'' | auto)
- comment_char="#"
- ;;
-?)
- ;;
-*)
- comment_char=$(echo "$comment_char" | cut -c1)
- ;;
-esac
-
-warn () {
- printf '%s\n' "$*" >&2
-}
-
-# Output the commit message for the specified commit.
-commit_message () {
- git cat-file commit "$1" | sed "1,/^$/d"
-}
-
-orig_reflog_action="$GIT_REFLOG_ACTION"
-
-comment_for_reflog () {
- case "$orig_reflog_action" in
- ''|rebase*)
- GIT_REFLOG_ACTION="rebase -i ($1)"
- export GIT_REFLOG_ACTION
- ;;
- esac
-}
-
-last_count=
-mark_action_done () {
- sed -e 1q < "$todo" >> "$done"
- sed -e 1d < "$todo" >> "$todo".new
- mv -f "$todo".new "$todo"
- new_count=$(( $(git stripspace --strip-comments <"$done" | wc -l) ))
- echo $new_count >"$msgnum"
- total=$(($new_count + $(git stripspace --strip-comments <"$todo" | wc -l)))
- echo $total >"$end"
- if test "$last_count" != "$new_count"
- then
- last_count=$new_count
- eval_gettext "Rebasing (\$new_count/\$total)"; printf "\r"
- test -z "$verbose" || echo
- fi
-}
-
-append_todo_help () {
- gettext "
-Commands:
-p, pick <commit> = use commit
-r, reword <commit> = use commit, but edit the commit message
-e, edit <commit> = use commit, but stop for amending
-s, squash <commit> = use commit, but meld into previous commit
-f, fixup <commit> = like \"squash\", but discard this commit's log message
-x, exec <commit> = run command (the rest of the line) using shell
-d, drop <commit> = remove commit
-l, label <label> = label current HEAD with a name
-t, reset <label> = reset HEAD to a label
-m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
-. create a merge commit using the original merge commit's
-. message (or the oneline, if no original merge commit was
-. specified). Use -c <commit> to reword the commit message.
-
-These lines can be re-ordered; they are executed from top to bottom.
-" | git stripspace --comment-lines >>"$todo"
-
- if test $(get_missing_commit_check_level) = error
- then
- gettext "
-Do not remove any line. Use 'drop' explicitly to remove a commit.
-" | git stripspace --comment-lines >>"$todo"
- else
- gettext "
-If you remove a line here THAT COMMIT WILL BE LOST.
-" | git stripspace --comment-lines >>"$todo"
- fi
-}
-
-make_patch () {
- sha1_and_parents="$(git rev-list --parents -1 "$1")"
- case "$sha1_and_parents" in
- ?*' '?*' '?*)
- git diff --cc $sha1_and_parents
- ;;
- ?*' '?*)
- git diff-tree -p "$1^!"
- ;;
- *)
- echo "Root commit"
- ;;
- esac > "$state_dir"/patch
- test -f "$msg" ||
- commit_message "$1" > "$msg"
- test -f "$author_script" ||
- get_author_ident_from_commit "$1" > "$author_script"
-}
-
-die_with_patch () {
- echo "$1" > "$state_dir"/stopped-sha
- git update-ref REBASE_HEAD "$1"
- make_patch "$1"
- die "$2"
-}
-
-exit_with_patch () {
- echo "$1" > "$state_dir"/stopped-sha
- git update-ref REBASE_HEAD "$1"
- make_patch $1
- git rev-parse --verify HEAD > "$amend"
- gpg_sign_opt_quoted=${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")}
- warn "$(eval_gettext "\
-You can amend the commit now, with
-
- git commit --amend \$gpg_sign_opt_quoted
-
-Once you are satisfied with your changes, run
-
- git rebase --continue")"
- warn
- exit $2
-}
-
-die_abort () {
- apply_autostash
- rm -rf "$state_dir"
- die "$1"
-}
-
-has_action () {
- test -n "$(git stripspace --strip-comments <"$1")"
-}
-
-is_empty_commit() {
- tree=$(git rev-parse -q --verify "$1"^{tree} 2>/dev/null) || {
- sha1=$1
- die "$(eval_gettext "\$sha1: not a commit that can be picked")"
- }
- ptree=$(git rev-parse -q --verify "$1"^^{tree} 2>/dev/null) ||
- ptree=4b825dc642cb6eb9a060e54bf8d69288fbee4904
- test "$tree" = "$ptree"
-}
-
-is_merge_commit()
-{
- git rev-parse --verify --quiet "$1"^2 >/dev/null 2>&1
-}
-
-# Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
-# GIT_AUTHOR_DATE exported from the current environment.
-do_with_author () {
- (
- export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
- "$@"
- )
-}
-
-git_sequence_editor () {
- if test -z "$GIT_SEQUENCE_EDITOR"
- then
- GIT_SEQUENCE_EDITOR="$(git config sequence.editor)"
- if [ -z "$GIT_SEQUENCE_EDITOR" ]
- then
- GIT_SEQUENCE_EDITOR="$(git var GIT_EDITOR)" || return $?
- fi
- fi
-
- eval "$GIT_SEQUENCE_EDITOR" '"$@"'
-}
-
-pick_one () {
- ff=--ff
-
- case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
- case "$force_rebase" in '') ;; ?*) ff= ;; esac
- output git rev-parse --verify $sha1 || die "$(eval_gettext "Invalid commit name: \$sha1")"
-
- if is_empty_commit "$sha1"
- then
- empty_args="--allow-empty"
- fi
-
- pick_one_preserving_merges "$@"
-}
-
-pick_one_preserving_merges () {
- fast_forward=t
- case "$1" in
- -n)
- fast_forward=f
- sha1=$2
- ;;
- *)
- sha1=$1
- ;;
- esac
- sha1=$(git rev-parse $sha1)
-
- if test -f "$state_dir"/current-commit && test "$fast_forward" = t
- then
- while read current_commit
- do
- git rev-parse HEAD > "$rewritten"/$current_commit
- done <"$state_dir"/current-commit
- rm "$state_dir"/current-commit ||
- die "$(gettext "Cannot write current commit's replacement sha1")"
- fi
-
- echo $sha1 >> "$state_dir"/current-commit
-
- # rewrite parents; if none were rewritten, we can fast-forward.
- new_parents=
- pend=" $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)"
- if test "$pend" = " "
- then
- pend=" root"
- fi
- while [ "$pend" != "" ]
- do
- p=$(expr "$pend" : ' \([^ ]*\)')
- pend="${pend# $p}"
-
- if test -f "$rewritten"/$p
- then
- new_p=$(cat "$rewritten"/$p)
-
- # If the todo reordered commits, and our parent is marked for
- # rewriting, but hasn't been gotten to yet, assume the user meant to
- # drop it on top of the current HEAD
- if test -z "$new_p"
- then
- new_p=$(git rev-parse HEAD)
- fi
-
- test $p != $new_p && fast_forward=f
- case "$new_parents" in
- *$new_p*)
- ;; # do nothing; that parent is already there
- *)
- new_parents="$new_parents $new_p"
- ;;
- esac
- else
- if test -f "$dropped"/$p
- then
- fast_forward=f
- replacement="$(cat "$dropped"/$p)"
- test -z "$replacement" && replacement=root
- pend=" $replacement$pend"
- else
- new_parents="$new_parents $p"
- fi
- fi
- done
- case $fast_forward in
- t)
- output warn "$(eval_gettext "Fast-forward to \$sha1")"
- output git reset --hard $sha1 ||
- die "$(eval_gettext "Cannot fast-forward to \$sha1")"
- ;;
- f)
- first_parent=$(expr "$new_parents" : ' \([^ ]*\)')
-
- if [ "$1" != "-n" ]
- then
- # detach HEAD to current parent
- output git checkout $first_parent 2> /dev/null ||
- die "$(eval_gettext "Cannot move HEAD to \$first_parent")"
- fi
-
- case "$new_parents" in
- ' '*' '*)
- test "a$1" = a-n && die "$(eval_gettext "Refusing to squash a merge: \$sha1")"
-
- # redo merge
- author_script_content=$(get_author_ident_from_commit $sha1)
- eval "$author_script_content"
- msg_content="$(commit_message $sha1)"
- # No point in merging the first parent, that's HEAD
- new_parents=${new_parents# $first_parent}
- merge_args="--no-log --no-ff"
- if ! do_with_author output eval \
- git merge ${gpg_sign_opt:+$(git rev-parse \
- --sq-quote "$gpg_sign_opt")} \
- $allow_rerere_autoupdate "$merge_args" \
- "$strategy_args" \
- -m "$(git rev-parse --sq-quote "$msg_content")" \
- "$new_parents"
- then
- printf "%s\n" "$msg_content" > "$GIT_DIR"/MERGE_MSG
- die_with_patch $sha1 "$(eval_gettext "Error redoing merge \$sha1")"
- fi
- echo "$sha1 $(git rev-parse HEAD^0)" >> "$rewritten_list"
- ;;
- *)
- output eval git cherry-pick $allow_rerere_autoupdate \
- $allow_empty_message \
- ${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} \
- "$strategy_args" "$@" ||
- die_with_patch $sha1 "$(eval_gettext "Could not pick \$sha1")"
- ;;
- esac
- ;;
- esac
-}
-
-this_nth_commit_message () {
- n=$1
- eval_gettext "This is the commit message #\${n}:"
-}
-
-skip_nth_commit_message () {
- n=$1
- eval_gettext "The commit message #\${n} will be skipped:"
-}
-
-update_squash_messages () {
- if test -f "$squash_msg"; then
- mv "$squash_msg" "$squash_msg".bak || exit
- count=$(($(sed -n \
- -e "1s/^$comment_char[^0-9]*\([0-9][0-9]*\).*/\1/p" \
- -e "q" < "$squash_msg".bak)+1))
- {
- printf '%s\n' "$comment_char $(eval_ngettext \
- "This is a combination of \$count commit." \
- "This is a combination of \$count commits." \
- $count)"
- sed -e 1d -e '2,/^./{
- /^$/d
- }' <"$squash_msg".bak
- } >"$squash_msg"
- else
- commit_message HEAD >"$fixup_msg" ||
- die "$(eval_gettext "Cannot write \$fixup_msg")"
- count=2
- {
- printf '%s\n' "$comment_char $(gettext "This is a combination of 2 commits.")"
- printf '%s\n' "$comment_char $(gettext "This is the 1st commit message:")"
- echo
- cat "$fixup_msg"
- } >"$squash_msg"
- fi
- case $1 in
- squash)
- rm -f "$fixup_msg"
- echo
- printf '%s\n' "$comment_char $(this_nth_commit_message $count)"
- echo
- commit_message $2
- ;;
- fixup)
- echo
- printf '%s\n' "$comment_char $(skip_nth_commit_message $count)"
- echo
- # Change the space after the comment character to TAB:
- commit_message $2 | git stripspace --comment-lines | sed -e 's/ / /'
- ;;
- esac >>"$squash_msg"
-}
-
-peek_next_command () {
- git stripspace --strip-comments <"$todo" | sed -n -e 's/ .*//p' -e q
-}
-
-# A squash/fixup has failed. Prepare the long version of the squash
-# commit message, then die_with_patch. This code path requires the
-# user to edit the combined commit message for all commits that have
-# been squashed/fixedup so far. So also erase the old squash
-# messages, effectively causing the combined commit to be used as the
-# new basis for any further squash/fixups. Args: sha1 rest
-die_failed_squash() {
- sha1=$1
- rest=$2
- mv "$squash_msg" "$msg" || exit
- rm -f "$fixup_msg"
- cp "$msg" "$GIT_DIR"/MERGE_MSG || exit
- warn
- warn "$(eval_gettext "Could not apply \$sha1... \$rest")"
- die_with_patch $sha1 ""
-}
-
-flush_rewritten_pending() {
- test -s "$rewritten_pending" || return
- newsha1="$(git rev-parse HEAD^0)"
- sed "s/$/ $newsha1/" < "$rewritten_pending" >> "$rewritten_list"
- rm -f "$rewritten_pending"
-}
-
-record_in_rewritten() {
- oldsha1="$(git rev-parse $1)"
- echo "$oldsha1" >> "$rewritten_pending"
-
- case "$(peek_next_command)" in
- squash|s|fixup|f)
- ;;
- *)
- flush_rewritten_pending
- ;;
- esac
-}
-
-do_pick () {
- sha1=$1
- rest=$2
- if test "$(git rev-parse HEAD)" = "$squash_onto"
- then
- # Set the correct commit message and author info on the
- # sentinel root before cherry-picking the original changes
- # without committing (-n). Finally, update the sentinel again
- # to include these changes. If the cherry-pick results in a
- # conflict, this means our behaviour is similar to a standard
- # failed cherry-pick during rebase, with a dirty index to
- # resolve before manually running git commit --amend then git
- # rebase --continue.
- git commit --allow-empty --allow-empty-message --amend \
- --no-post-rewrite -n -q -C $sha1 $signoff &&
- pick_one -n $sha1 &&
- git commit --allow-empty --allow-empty-message \
- --amend --no-post-rewrite -n -q -C $sha1 $signoff \
- ${gpg_sign_opt:+"$gpg_sign_opt"} ||
- die_with_patch $sha1 "$(eval_gettext "Could not apply \$sha1... \$rest")"
- else
- pick_one $sha1 ||
- die_with_patch $sha1 "$(eval_gettext "Could not apply \$sha1... \$rest")"
- fi
-}
-
-do_next () {
- rm -f "$msg" "$author_script" "$amend" "$state_dir"/stopped-sha || exit
- read -r command sha1 rest < "$todo"
- case "$command" in
- "$comment_char"*|''|noop|drop|d)
- mark_action_done
- ;;
- "$cr")
- # Work around CR left by "read" (e.g. with Git for Windows' Bash).
- mark_action_done
- ;;
- pick|p)
- comment_for_reflog pick
-
- mark_action_done
- do_pick $sha1 "$rest"
- record_in_rewritten $sha1
- ;;
- reword|r)
- comment_for_reflog reword
-
- mark_action_done
- do_pick $sha1 "$rest"
- git commit --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} \
- $allow_empty_message || {
- warn "$(eval_gettext "\
-Could not amend commit after successfully picking \$sha1... \$rest
-This is most likely due to an empty commit message, or the pre-commit hook
-failed. If the pre-commit hook failed, you may need to resolve the issue before
-you are able to reword the commit.")"
- exit_with_patch $sha1 1
- }
- record_in_rewritten $sha1
- ;;
- edit|e)
- comment_for_reflog edit
-
- mark_action_done
- do_pick $sha1 "$rest"
- sha1_abbrev=$(git rev-parse --short $sha1)
- warn "$(eval_gettext "Stopped at \$sha1_abbrev... \$rest")"
- exit_with_patch $sha1 0
- ;;
- squash|s|fixup|f)
- case "$command" in
- squash|s)
- squash_style=squash
- ;;
- fixup|f)
- squash_style=fixup
- ;;
- esac
- comment_for_reflog $squash_style
-
- test -f "$done" && has_action "$done" ||
- die "$(eval_gettext "Cannot '\$squash_style' without a previous commit")"
-
- mark_action_done
- update_squash_messages $squash_style $sha1
- author_script_content=$(get_author_ident_from_commit HEAD)
- echo "$author_script_content" > "$author_script"
- eval "$author_script_content"
- if ! pick_one -n $sha1
- then
- git rev-parse --verify HEAD >"$amend"
- die_failed_squash $sha1 "$rest"
- fi
- case "$(peek_next_command)" in
- squash|s|fixup|f)
- # This is an intermediate commit; its message will only be
- # used in case of trouble. So use the long version:
- do_with_author output git commit --amend --no-verify -F "$squash_msg" \
- ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message ||
- die_failed_squash $sha1 "$rest"
- ;;
- *)
- # This is the final command of this squash/fixup group
- if test -f "$fixup_msg"
- then
- do_with_author git commit --amend --no-verify -F "$fixup_msg" \
- ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message ||
- die_failed_squash $sha1 "$rest"
- else
- cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit
- rm -f "$GIT_DIR"/MERGE_MSG
- do_with_author git commit --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \
- ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message ||
- die_failed_squash $sha1 "$rest"
- fi
- rm -f "$squash_msg" "$fixup_msg"
- ;;
- esac
- record_in_rewritten $sha1
- ;;
- x|"exec")
- read -r command rest < "$todo"
- mark_action_done
- eval_gettextln "Executing: \$rest"
- "${SHELL:-@SHELL_PATH@}" -c "$rest" # Actual execution
- status=$?
- # Run in subshell because require_clean_work_tree can die.
- dirty=f
- (require_clean_work_tree "rebase" 2>/dev/null) || dirty=t
- if test "$status" -ne 0
- then
- warn "$(eval_gettext "Execution failed: \$rest")"
- test "$dirty" = f ||
- warn "$(gettext "and made changes to the index and/or the working tree")"
-
- warn "$(gettext "\
-You can fix the problem, and then run
-
- git rebase --continue")"
- warn
- if test $status -eq 127 # command not found
- then
- status=1
- fi
- exit "$status"
- elif test "$dirty" = t
- then
- # TRANSLATORS: after these lines is a command to be issued by the user
- warn "$(eval_gettext "\
-Execution succeeded: \$rest
-but left changes to the index and/or the working tree
-Commit or stash your changes, and then run
-
- git rebase --continue")"
- warn
- exit 1
- fi
- ;;
- *)
- warn "$(eval_gettext "Unknown command: \$command \$sha1 \$rest")"
- fixtodo="$(gettext "Please fix this using 'git rebase --edit-todo'.")"
- if git rev-parse --verify -q "$sha1" >/dev/null
- then
- die_with_patch $sha1 "$fixtodo"
- else
- die "$fixtodo"
- fi
- ;;
- esac
- test -s "$todo" && return
-
- comment_for_reflog finish &&
- newhead=$(git rev-parse HEAD) &&
- case $head_name in
- refs/*)
- message="$GIT_REFLOG_ACTION: $head_name onto $onto" &&
- git update-ref -m "$message" $head_name $newhead $orig_head &&
- git symbolic-ref \
- -m "$GIT_REFLOG_ACTION: returning to $head_name" \
- HEAD $head_name
- ;;
- esac && {
- test ! -f "$state_dir"/verbose ||
- git diff-tree --stat $orig_head..HEAD
- } &&
- {
- test -s "$rewritten_list" &&
- git notes copy --for-rewrite=rebase < "$rewritten_list" ||
- true # we don't care if this copying failed
- } &&
- hook="$(git rev-parse --git-path hooks/post-rewrite)"
- if test -x "$hook" && test -s "$rewritten_list"; then
- "$hook" rebase < "$rewritten_list"
- true # we don't care if this hook failed
- fi &&
- warn "$(eval_gettext "Successfully rebased and updated \$head_name.")"
-
- return 1 # not failure; just to break the do_rest loop
-}
-
-# can only return 0, when the infinite loop breaks
-do_rest () {
- while :
- do
- do_next || break
- done
-}
-
-expand_todo_ids() {
- git rebase--interactive --expand-ids
-}
-
-collapse_todo_ids() {
- git rebase--interactive --shorten-ids
-}
-
-# Switch to the branch in $into and notify it in the reflog
-checkout_onto () {
- GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name"
- output git checkout $onto || die_abort "$(gettext "could not detach HEAD")"
- git update-ref ORIG_HEAD $orig_head
-}
-
-get_missing_commit_check_level () {
- check_level=$(git config --get rebase.missingCommitsCheck)
- check_level=${check_level:-ignore}
- # Don't be case sensitive
- printf '%s' "$check_level" | tr 'A-Z' 'a-z'
-}
-
-# Initiate an action. If the cannot be any
-# further action it may exec a command
-# or exit and not return.
-#
-# TODO: Consider a cleaner return model so it
-# never exits and always return 0 if process
-# is complete.
-#
-# Parameter 1 is the action to initiate.
-#
-# Returns 0 if the action was able to complete
-# and if 1 if further processing is required.
-initiate_action () {
- case "$1" in
- continue)
- # do we have anything to commit?
- if git diff-index --cached --quiet HEAD --
- then
- # Nothing to commit -- skip this commit
-
- test ! -f "$GIT_DIR"/CHERRY_PICK_HEAD ||
- rm "$GIT_DIR"/CHERRY_PICK_HEAD ||
- die "$(gettext "Could not remove CHERRY_PICK_HEAD")"
- else
- if ! test -f "$author_script"
- then
- gpg_sign_opt_quoted=${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")}
- die "$(eval_gettext "\
-You have staged changes in your working tree.
-If these changes are meant to be
-squashed into the previous commit, run:
-
- git commit --amend \$gpg_sign_opt_quoted
-
-If they are meant to go into a new commit, run:
-
- git commit \$gpg_sign_opt_quoted
-
-In both cases, once you're done, continue with:
-
- git rebase --continue
-")"
- fi
- . "$author_script" ||
- die "$(gettext "Error trying to find the author identity to amend commit")"
- if test -f "$amend"
- then
- current_head=$(git rev-parse --verify HEAD)
- test "$current_head" = $(cat "$amend") ||
- die "$(gettext "\
-You have uncommitted changes in your working tree. Please commit them
-first and then run 'git rebase --continue' again.")"
- do_with_author git commit --amend --no-verify -F "$msg" -e \
- ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message ||
- die "$(gettext "Could not commit staged changes.")"
- else
- do_with_author git commit --no-verify -F "$msg" -e \
- ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message ||
- die "$(gettext "Could not commit staged changes.")"
- fi
- fi
-
- if test -r "$state_dir"/stopped-sha
- then
- record_in_rewritten "$(cat "$state_dir"/stopped-sha)"
- fi
-
- require_clean_work_tree "rebase"
- do_rest
- return 0
- ;;
- skip)
- git rerere clear
- do_rest
- return 0
- ;;
- edit-todo)
- git stripspace --strip-comments <"$todo" >"$todo".new
- mv -f "$todo".new "$todo"
- collapse_todo_ids
- append_todo_help
- gettext "
-You are editing the todo file of an ongoing interactive rebase.
-To continue rebase after editing, run:
- git rebase --continue
-
-" | git stripspace --comment-lines >>"$todo"
-
- git_sequence_editor "$todo" ||
- die "$(gettext "Could not execute editor")"
- expand_todo_ids
-
- exit
- ;;
- show-current-patch)
- exec git show REBASE_HEAD --
- ;;
- *)
- return 1 # continue
- ;;
- esac
-}
-
-setup_reflog_action () {
- comment_for_reflog start
-
- if test ! -z "$switch_to"
- then
- GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $switch_to"
- output git checkout "$switch_to" -- ||
- die "$(eval_gettext "Could not checkout \$switch_to")"
-
- comment_for_reflog start
- fi
-}
-
-init_basic_state () {
- orig_head=$(git rev-parse --verify HEAD) || die "$(gettext "No HEAD?")"
- mkdir -p "$state_dir" || die "$(eval_gettext "Could not create temporary \$state_dir")"
- rm -f "$(git rev-parse --git-path REBASE_HEAD)"
-
- : > "$state_dir"/interactive || die "$(gettext "Could not mark as interactive")"
- write_basic_state
-}
-
-init_revisions_and_shortrevisions () {
- shorthead=$(git rev-parse --short $orig_head)
- shortonto=$(git rev-parse --short $onto)
- if test -z "$rebase_root"
- # this is now equivalent to ! -z "$upstream"
- then
- shortupstream=$(git rev-parse --short $upstream)
- revisions=$upstream...$orig_head
- shortrevisions=$shortupstream..$shorthead
- else
- revisions=$onto...$orig_head
- shortrevisions=$shorthead
- test -z "$squash_onto" ||
- echo "$squash_onto" >"$state_dir"/squash-onto
- fi
-}
-
-complete_action() {
- test -s "$todo" || echo noop >> "$todo"
- test -z "$autosquash" || git rebase--interactive --rearrange-squash || exit
- test -n "$cmd" && git rebase--interactive --add-exec-commands --cmd "$cmd"
-
- todocount=$(git stripspace --strip-comments <"$todo" | wc -l)
- todocount=${todocount##* }
-
-cat >>"$todo" <<EOF
-
-$comment_char $(eval_ngettext \
- "Rebase \$shortrevisions onto \$shortonto (\$todocount command)" \
- "Rebase \$shortrevisions onto \$shortonto (\$todocount commands)" \
- "$todocount")
-EOF
- append_todo_help
- gettext "
-However, if you remove everything, the rebase will be aborted.
-
-" | git stripspace --comment-lines >>"$todo"
-
- if test -z "$keep_empty"
- then
- printf '%s\n' "$comment_char $(gettext "Note that empty commits are commented out")" >>"$todo"
- fi
-
-
- has_action "$todo" ||
- return 2
-
- cp "$todo" "$todo".backup
- collapse_todo_ids
- git_sequence_editor "$todo" ||
- die_abort "$(gettext "Could not execute editor")"
-
- has_action "$todo" ||
- return 2
-
- git rebase--interactive --check-todo-list || {
- ret=$?
- checkout_onto
- exit $ret
- }
-
- expand_todo_ids
- checkout_onto
- do_rest
-}
-
-git_rebase__preserve_merges () {
- initiate_action "$action"
- ret=$?
- if test $ret = 0; then
- return 0
- fi
-
- setup_reflog_action
- init_basic_state
-
- if test -z "$rebase_root"
- then
- mkdir "$rewritten" &&
- for c in $(git merge-base --all $orig_head $upstream)
- do
- echo $onto > "$rewritten"/$c ||
- die "$(gettext "Could not init rewritten commits")"
- done
- else
- mkdir "$rewritten" &&
- echo $onto > "$rewritten"/root ||
- die "$(gettext "Could not init rewritten commits")"
- fi
-
- init_revisions_and_shortrevisions
-
- format=$(git config --get rebase.instructionFormat)
- # the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse
- git rev-list --format="%m%H ${format:-%s}" \
- --reverse --left-right --topo-order \
- $revisions ${restrict_revision+^$restrict_revision} | \
- sed -n "s/^>//p" |
- while read -r sha1 rest
- do
- if test -z "$keep_empty" && is_empty_commit $sha1 && ! is_merge_commit $sha1
- then
- comment_out="$comment_char "
- else
- comment_out=
- fi
-
- if test -z "$rebase_root"
- then
- preserve=t
- for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)
- do
- if test -f "$rewritten"/$p
- then
- preserve=f
- fi
- done
- else
- preserve=f
- fi
- if test f = "$preserve"
- then
- touch "$rewritten"/$sha1
- printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo"
- fi
- done
-
- # Watch for commits that been dropped by --cherry-pick
- mkdir "$dropped"
- # Save all non-cherry-picked changes
- git rev-list $revisions --left-right --cherry-pick | \
- sed -n "s/^>//p" > "$state_dir"/not-cherry-picks
- # Now all commits and note which ones are missing in
- # not-cherry-picks and hence being dropped
- git rev-list $revisions |
- while read rev
- do
- if test -f "$rewritten"/$rev &&
- ! sane_grep "$rev" "$state_dir"/not-cherry-picks >/dev/null
- then
- # Use -f2 because if rev-list is telling us this commit is
- # not worthwhile, we don't want to track its multiple heads,
- # just the history of its first-parent for others that will
- # be rebasing on top of it
- git rev-list --parents -1 $rev | cut -d' ' -s -f2 > "$dropped"/$rev
- sha1=$(git rev-list -1 $rev)
- sane_grep -v "^[a-z][a-z]* $sha1" <"$todo" > "${todo}2" ; mv "${todo}2" "$todo"
- rm "$rewritten"/$rev
- fi
- done
-
- complete_action
-}
diff --git a/git-send-email.perl b/git-send-email.perl
index e65d969d0b..5262d88ee3 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -376,7 +376,7 @@ sub read_config {
@$target = @values;
}
else {
- my $v = $known_keys->{$key}->[0];
+ my $v = $known_keys->{$key}->[-1];
next unless defined $v;
next if $configured->{$setting}++;
$$target = $v;
@@ -1697,7 +1697,6 @@ EOF
$in_reply_to = $initial_in_reply_to;
$references = $initial_in_reply_to || '';
-$subject = $initial_subject;
$message_num = 0;
# Prepares the email, prompts the user, sends it out
@@ -1720,6 +1719,7 @@ sub process_file {
@xh = ();
my $input_format = undef;
my @header = ();
+ $subject = $initial_subject;
$message = "";
$message_num++;
# First unfold multiline header fields
@@ -1926,15 +1926,23 @@ sub process_file {
}
# set up for the next message
- if ($thread && $message_was_sent &&
- ($chain_reply_to || !defined $in_reply_to || length($in_reply_to) == 0 ||
- $message_num == 1)) {
- $in_reply_to = $message_id;
- if (length $references > 0) {
- $references .= "\n $message_id";
- } else {
- $references = "$message_id";
+ if ($thread) {
+ if ($message_was_sent &&
+ ($chain_reply_to || !defined $in_reply_to || length($in_reply_to) == 0 ||
+ $message_num == 1)) {
+ $in_reply_to = $message_id;
+ if (length $references > 0) {
+ $references .= "\n $message_id";
+ } else {
+ $references = "$message_id";
+ }
}
+ } elsif (!defined $initial_in_reply_to) {
+ # --thread and --in-reply-to manage the "In-Reply-To" header and by
+ # extension the "References" header. If these commands are not used, reset
+ # the header values to their defaults.
+ $in_reply_to = undef;
+ $references = '';
}
$message_id = undef;
$num_sent++;
diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index 10d9764185..cee053cdc3 100644
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -223,9 +223,6 @@ require_clean_work_tree () {
"rewrite branches")
gettextln "Cannot rewrite branches: You have unstaged changes." >&2
;;
- "pull with rebase")
- gettextln "Cannot pull with rebase: You have unstaged changes." >&2
- ;;
*)
eval_gettextln "Cannot \$action: You have unstaged changes." >&2
;;
@@ -242,9 +239,6 @@ require_clean_work_tree () {
rebase)
gettextln "Cannot rebase: Your index contains uncommitted changes." >&2
;;
- "pull with rebase")
- gettextln "Cannot pull with rebase: Your index contains uncommitted changes." >&2
- ;;
*)
eval_gettextln "Cannot \$action: Your index contains uncommitted changes." >&2
;;
diff --git a/git-submodule.sh b/git-submodule.sh
index dbd2ec2050..652861aa66 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -63,11 +63,6 @@ isnumber()
n=$(($1 + 0)) 2>/dev/null && test "$n" = "$1"
}
-# Given a full hex object ID, is this the zero OID?
-is_zero_oid () {
- echo "$1" | sane_egrep '^0+$' >/dev/null 2>&1
-}
-
# Sanitize the local git environment for use within a submodule. We
# can't simply use clear_local_git_env since we want to preserve some
# of the settings from GIT_CONFIG_PARAMETERS.
@@ -145,130 +140,12 @@ cmd_add()
shift
done
- if ! git submodule--helper config --check-writeable >/dev/null 2>&1
- then
- die "fatal: $(eval_gettext "please make sure that the .gitmodules file is in the working tree")"
- fi
-
- if test -n "$reference_path"
+ if test -z "$1"
then
- is_absolute_path "$reference_path" ||
- reference_path="$wt_prefix$reference_path"
-
- reference="--reference=$reference_path"
- fi
-
- repo=$1
- sm_path=$2
-
- if test -z "$sm_path"; then
- sm_path=$(printf '%s\n' "$repo" |
- sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
- fi
-
- if test -z "$repo" || test -z "$sm_path"; then
usage
fi
- is_absolute_path "$sm_path" || sm_path="$wt_prefix$sm_path"
-
- # assure repo is absolute or relative to parent
- case "$repo" in
- ./*|../*)
- test -z "$wt_prefix" ||
- die "fatal: $(gettext "Relative path can only be used from the toplevel of the working tree")"
-
- # dereference source url relative to parent's url
- realrepo=$(git submodule--helper resolve-relative-url "$repo") || exit
- ;;
- *:*|/*)
- # absolute url
- realrepo=$repo
- ;;
- *)
- die "fatal: $(eval_gettext "repo URL: '\$repo' must be absolute or begin with ./|../")"
- ;;
- esac
-
- # normalize path:
- # multiple //; leading ./; /./; /../; trailing /
- sm_path=$(printf '%s/\n' "$sm_path" |
- sed -e '
- s|//*|/|g
- s|^\(\./\)*||
- s|/\(\./\)*|/|g
- :start
- s|\([^/]*\)/\.\./||
- tstart
- s|/*$||
- ')
- if test -z "$force"
- then
- git ls-files --error-unmatch "$sm_path" > /dev/null 2>&1 &&
- die "fatal: $(eval_gettext "'\$sm_path' already exists in the index")"
- else
- git ls-files -s "$sm_path" | sane_grep -v "^160000" > /dev/null 2>&1 &&
- die "fatal: $(eval_gettext "'\$sm_path' already exists in the index and is not a submodule")"
- fi
-
- if test -d "$sm_path" &&
- test -z $(git -C "$sm_path" rev-parse --show-cdup 2>/dev/null)
- then
- git -C "$sm_path" rev-parse --verify -q HEAD >/dev/null ||
- die "fatal: $(eval_gettext "'\$sm_path' does not have a commit checked out")"
- fi
-
- if test -z "$force"
- then
- dryerr=$(git add --dry-run --ignore-missing --no-warn-embedded-repo "$sm_path" 2>&1 >/dev/null)
- res=$?
- if test $res -ne 0
- then
- echo >&2 "$dryerr"
- exit $res
- fi
- fi
-
- if test -n "$custom_name"
- then
- sm_name="$custom_name"
- else
- sm_name="$sm_path"
- fi
-
- if ! git submodule--helper check-name "$sm_name"
- then
- die "fatal: $(eval_gettext "'$sm_name' is not a valid submodule name")"
- fi
-
- git submodule--helper add-clone ${GIT_QUIET:+--quiet} ${force:+"--force"} ${progress:+"--progress"} ${branch:+--branch "$branch"} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" ${reference:+"$reference"} ${dissociate:+"--dissociate"} ${depth:+"$depth"} || exit
- git config submodule."$sm_name".url "$realrepo"
-
- git add --no-warn-embedded-repo $force "$sm_path" ||
- die "fatal: $(eval_gettext "Failed to add submodule '\$sm_path'")"
-
- git submodule--helper config submodule."$sm_name".path "$sm_path" &&
- git submodule--helper config submodule."$sm_name".url "$repo" &&
- if test -n "$branch"
- then
- git submodule--helper config submodule."$sm_name".branch "$branch"
- fi &&
- git add --force .gitmodules ||
- die "fatal: $(eval_gettext "Failed to register submodule '\$sm_path'")"
-
- # NEEDSWORK: In a multi-working-tree world, this needs to be
- # set in the per-worktree config.
- if git config --get submodule.active >/dev/null
- then
- # If the submodule being adding isn't already covered by the
- # current configured pathspec, set the submodule's active flag
- if ! git submodule--helper is-active "$sm_path"
- then
- git config submodule."$sm_name".active "true"
- fi
- else
- git config submodule."$sm_name".active "true"
- fi
+ git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper add ${GIT_QUIET:+--quiet} ${force:+--force} ${progress:+"--progress"} ${branch:+--branch "$branch"} ${reference_path:+--reference "$reference_path"} ${dissociate:+--dissociate} ${custom_name:+--name "$custom_name"} ${depth:+"$depth"} -- "$@"
}
#
@@ -369,13 +246,6 @@ cmd_deinit()
git ${wt_prefix:+-C "$wt_prefix"} submodule--helper deinit ${GIT_QUIET:+--quiet} ${force:+--force} ${deinit_all:+--all} -- "$@"
}
-is_tip_reachable () (
- sanitize_submodule_env &&
- cd "$1" &&
- rev=$(git rev-list -n 1 "$2" --not --all 2>/dev/null) &&
- test -z "$rev"
-)
-
# usage: fetch_in_submodule <module_path> [<depth>] [<sha1>]
# Because arguments are positional, use an empty string to omit <depth>
# but include <sha1>.
@@ -519,14 +389,13 @@ cmd_update()
git submodule--helper ensure-core-worktree "$sm_path" || exit 1
- update_module=$(git submodule--helper update-module-mode $just_cloned "$sm_path" $update)
-
displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
if test $just_cloned -eq 1
then
subsha1=
else
+ just_cloned=
subsha1=$(sanitize_submodule_env; cd "$sm_path" &&
git rev-parse --verify HEAD) ||
die "fatal: $(eval_gettext "Unable to find current revision in submodule path '\$displaypath'")"
@@ -547,70 +416,38 @@ cmd_update()
die "fatal: $(eval_gettext "Unable to find current \${remote_name}/\${branch} revision in submodule path '\$sm_path'")"
fi
- if test "$subsha1" != "$sha1" || test -n "$force"
- then
- subforce=$force
- # If we don't already have a -f flag and the submodule has never been checked out
- if test -z "$subsha1" && test -z "$force"
- then
- subforce="-f"
- fi
-
- if test -z "$nofetch"
- then
- # Run fetch only if $sha1 isn't present or it
- # is not reachable from a ref.
- is_tip_reachable "$sm_path" "$sha1" ||
- fetch_in_submodule "$sm_path" $depth ||
- say "$(eval_gettext "Unable to fetch in submodule path '\$displaypath'; trying to directly fetch \$sha1:")"
-
- # Now we tried the usual fetch, but $sha1 may
- # not be reachable from any of the refs
- is_tip_reachable "$sm_path" "$sha1" ||
- fetch_in_submodule "$sm_path" "$depth" "$sha1" ||
- die "fatal: $(eval_gettext "Fetched in submodule path '\$displaypath', but it did not contain \$sha1. Direct fetching of that commit failed.")"
- fi
-
- must_die_on_failure=
- case "$update_module" in
- checkout)
- command="git checkout $subforce -q"
- die_msg="fatal: $(eval_gettext "Unable to checkout '\$sha1' in submodule path '\$displaypath'")"
- say_msg="$(eval_gettext "Submodule path '\$displaypath': checked out '\$sha1'")"
- ;;
- rebase)
- command="git rebase ${GIT_QUIET:+--quiet}"
- die_msg="fatal: $(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$displaypath'")"
- say_msg="$(eval_gettext "Submodule path '\$displaypath': rebased into '\$sha1'")"
- must_die_on_failure=yes
- ;;
- merge)
- command="git merge ${GIT_QUIET:+--quiet}"
- die_msg="fatal: $(eval_gettext "Unable to merge '\$sha1' in submodule path '\$displaypath'")"
- say_msg="$(eval_gettext "Submodule path '\$displaypath': merged in '\$sha1'")"
- must_die_on_failure=yes
- ;;
- !*)
- command="${update_module#!}"
- die_msg="fatal: $(eval_gettext "Execution of '\$command \$sha1' failed in submodule path '\$displaypath'")"
- say_msg="$(eval_gettext "Submodule path '\$displaypath': '\$command \$sha1'")"
- must_die_on_failure=yes
- ;;
- *)
- die "fatal: $(eval_gettext "Invalid update mode '$update_module' for submodule path '$path'")"
- esac
-
- if (sanitize_submodule_env; cd "$sm_path" && $command "$sha1")
- then
- say "$say_msg"
- elif test -n "$must_die_on_failure"
- then
- die_with_status 2 "$die_msg"
- else
- err="${err};$die_msg"
- continue
- fi
- fi
+ out=$(git submodule--helper run-update-procedure \
+ ${wt_prefix:+--prefix "$wt_prefix"} \
+ ${GIT_QUIET:+--quiet} \
+ ${force:+--force} \
+ ${just_cloned:+--just-cloned} \
+ ${nofetch:+--no-fetch} \
+ ${depth:+"$depth"} \
+ ${update:+--update "$update"} \
+ ${prefix:+--recursive-prefix "$prefix"} \
+ ${sha1:+--oid "$sha1"} \
+ ${subsha1:+--suboid "$subsha1"} \
+ "--" \
+ "$sm_path")
+
+ # exit codes for run-update-procedure:
+ # 0: update was successful, say command output
+ # 1: update procedure failed, but should not die
+ # 2 or 128: subcommand died during execution
+ # 3: no update procedure was run
+ res="$?"
+ case $res in
+ 0)
+ say "$out"
+ ;;
+ 1)
+ err="${err};fatal: $out"
+ continue
+ ;;
+ 2|128)
+ die_with_status $res "fatal: $out"
+ ;;
+ esac
if test -n "$recursive"
then
diff --git a/git-svn.perl b/git-svn.perl
index 70cb5e2a83..be987e316f 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -273,7 +273,6 @@ my %cmd = (
'fetch-all|all' => \$_fetch_all,
'dry-run|n' => \$_dry_run,
'rebase-merges|p' => \$_rebase_merges,
- 'preserve-merges|p' => \$_rebase_merges,
%fc_opts } ],
'commit-diff' => [ \&cmd_commit_diff,
'Commit a diff between two trees',
diff --git a/git.c b/git.c
index 60c2784be4..5ff21be21f 100644
--- a/git.c
+++ b/git.c
@@ -577,7 +577,6 @@ static struct cmd_struct commands[] = {
{ "range-diff", cmd_range_diff, RUN_SETUP | USE_PAGER },
{ "read-tree", cmd_read_tree, RUN_SETUP | SUPPORT_SUPER_PREFIX},
{ "rebase", cmd_rebase, RUN_SETUP | NEED_WORK_TREE },
- { "rebase--interactive", cmd_rebase__interactive, RUN_SETUP | NEED_WORK_TREE },
{ "receive-pack", cmd_receive_pack },
{ "reflog", cmd_reflog, RUN_SETUP },
{ "remote", cmd_remote, RUN_SETUP },
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index e09e024a09..fbd1c20a23 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -3796,7 +3796,8 @@ sub git_get_heads_list {
my @headslist;
open my $fd, '-|', git_cmd(), 'for-each-ref',
- ($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate',
+ ($limit ? '--count='.($limit+1) : ()),
+ '--sort=-HEAD', '--sort=-committerdate',
'--format=%(objectname) %(refname) %(subject)%00%(committer)',
@patterns
or return;
diff --git a/grep.c b/grep.c
index 424a39591b..14fe8a0fd2 100644
--- a/grep.c
+++ b/grep.c
@@ -867,7 +867,7 @@ void free_grep_patterns(struct grep_opt *opt)
free_pattern_expr(opt->pattern_expression);
}
-static char *end_of_line(char *cp, unsigned long *left)
+static const char *end_of_line(const char *cp, unsigned long *left)
{
unsigned long l = *left;
while (l && *cp != '\n') {
@@ -908,7 +908,8 @@ static void show_name(struct grep_opt *opt, const char *name)
opt->output(opt, opt->null_following_name ? "\0" : "\n", 1);
}
-static int patmatch(struct grep_pat *p, char *line, char *eol,
+static int patmatch(struct grep_pat *p,
+ const char *line, const char *eol,
regmatch_t *match, int eflags)
{
int hit;
@@ -922,20 +923,16 @@ static int patmatch(struct grep_pat *p, char *line, char *eol,
return hit;
}
-static int strip_timestamp(char *bol, char **eol_p)
+static void strip_timestamp(const char *bol, const char **eol_p)
{
- char *eol = *eol_p;
- int ch;
+ const char *eol = *eol_p;
while (bol < --eol) {
if (*eol != '>')
continue;
*eol_p = ++eol;
- ch = *eol;
- *eol = '\0';
- return ch;
+ break;
}
- return 0;
}
static struct {
@@ -947,12 +944,12 @@ static struct {
{ "reflog ", 7 },
};
-static int match_one_pattern(struct grep_pat *p, char *bol, char *eol,
+static int match_one_pattern(struct grep_pat *p,
+ const char *bol, const char *eol,
enum grep_context ctx,
regmatch_t *pmatch, int eflags)
{
int hit = 0;
- int saved_ch = 0;
const char *start = bol;
if ((p->token != GREP_PATTERN) &&
@@ -971,7 +968,7 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol,
switch (p->field) {
case GREP_HEADER_AUTHOR:
case GREP_HEADER_COMMITTER:
- saved_ch = strip_timestamp(bol, &eol);
+ strip_timestamp(bol, &eol);
break;
default:
break;
@@ -1021,8 +1018,6 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol,
goto again;
}
}
- if (p->token == GREP_PATTERN_HEAD && saved_ch)
- *eol = saved_ch;
if (hit) {
pmatch[0].rm_so += bol - start;
pmatch[0].rm_eo += bol - start;
@@ -1030,8 +1025,9 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol,
return hit;
}
-static int match_expr_eval(struct grep_opt *opt, struct grep_expr *x, char *bol,
- char *eol, enum grep_context ctx, ssize_t *col,
+static int match_expr_eval(struct grep_opt *opt, struct grep_expr *x,
+ const char *bol, const char *eol,
+ enum grep_context ctx, ssize_t *col,
ssize_t *icol, int collect_hits)
{
int h = 0;
@@ -1098,7 +1094,8 @@ static int match_expr_eval(struct grep_opt *opt, struct grep_expr *x, char *bol,
return h;
}
-static int match_expr(struct grep_opt *opt, char *bol, char *eol,
+static int match_expr(struct grep_opt *opt,
+ const char *bol, const char *eol,
enum grep_context ctx, ssize_t *col,
ssize_t *icol, int collect_hits)
{
@@ -1106,7 +1103,8 @@ static int match_expr(struct grep_opt *opt, char *bol, char *eol,
return match_expr_eval(opt, x, bol, eol, ctx, col, icol, collect_hits);
}
-static int match_line(struct grep_opt *opt, char *bol, char *eol,
+static int match_line(struct grep_opt *opt,
+ const char *bol, const char *eol,
ssize_t *col, ssize_t *icol,
enum grep_context ctx, int collect_hits)
{
@@ -1138,7 +1136,8 @@ static int match_line(struct grep_opt *opt, char *bol, char *eol,
return hit;
}
-static int match_next_pattern(struct grep_pat *p, char *bol, char *eol,
+static int match_next_pattern(struct grep_pat *p,
+ const char *bol, const char *eol,
enum grep_context ctx,
regmatch_t *pmatch, int eflags)
{
@@ -1159,7 +1158,8 @@ static int match_next_pattern(struct grep_pat *p, char *bol, char *eol,
return 1;
}
-static int next_match(struct grep_opt *opt, char *bol, char *eol,
+static int next_match(struct grep_opt *opt,
+ const char *bol, const char *eol,
enum grep_context ctx, regmatch_t *pmatch, int eflags)
{
struct grep_pat *p;
@@ -1215,7 +1215,8 @@ static void show_line_header(struct grep_opt *opt, const char *name,
}
}
-static void show_line(struct grep_opt *opt, char *bol, char *eol,
+static void show_line(struct grep_opt *opt,
+ const char *bol, const char *eol,
const char *name, unsigned lno, ssize_t cno, char sign)
{
int rest = eol - bol;
@@ -1246,7 +1247,6 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
if (opt->color || opt->only_matching) {
regmatch_t match;
enum grep_context ctx = GREP_CONTEXT_BODY;
- int ch = *eol;
int eflags = 0;
if (opt->color) {
@@ -1261,7 +1261,6 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
else if (sign == '=')
line_color = opt->colors[GREP_COLOR_FUNCTION];
}
- *eol = '\0';
while (next_match(opt, bol, eol, ctx, &match, eflags)) {
if (match.rm_so == match.rm_eo)
break;
@@ -1279,7 +1278,6 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
rest -= match.rm_eo;
eflags = REG_NOTBOL;
}
- *eol = ch;
}
if (!opt->only_matching) {
output_color(opt, bol, rest, line_color);
@@ -1307,7 +1305,8 @@ static inline void grep_attr_unlock(void)
pthread_mutex_unlock(&grep_attr_mutex);
}
-static int match_funcname(struct grep_opt *opt, struct grep_source *gs, char *bol, char *eol)
+static int match_funcname(struct grep_opt *opt, struct grep_source *gs,
+ const char *bol, const char *eol)
{
xdemitconf_t *xecfg = opt->priv;
if (xecfg && !xecfg->find_func) {
@@ -1334,10 +1333,10 @@ static int match_funcname(struct grep_opt *opt, struct grep_source *gs, char *bo
}
static void show_funcname_line(struct grep_opt *opt, struct grep_source *gs,
- char *bol, unsigned lno)
+ const char *bol, unsigned lno)
{
while (bol > gs->buf) {
- char *eol = --bol;
+ const char *eol = --bol;
while (bol > gs->buf && bol[-1] != '\n')
bol--;
@@ -1356,7 +1355,7 @@ static void show_funcname_line(struct grep_opt *opt, struct grep_source *gs,
static int is_empty_line(const char *bol, const char *eol);
static void show_pre_context(struct grep_opt *opt, struct grep_source *gs,
- char *bol, char *end, unsigned lno)
+ const char *bol, const char *end, unsigned lno)
{
unsigned cur = lno, from = 1, funcname_lno = 0, orig_from;
int funcname_needed = !!opt->funcname, comment_needed = 0;
@@ -1376,8 +1375,8 @@ static void show_pre_context(struct grep_opt *opt, struct grep_source *gs,
/* Rewind. */
while (bol > gs->buf && cur > from) {
- char *next_bol = bol;
- char *eol = --bol;
+ const char *next_bol = bol;
+ const char *eol = --bol;
while (bol > gs->buf && bol[-1] != '\n')
bol--;
@@ -1408,7 +1407,7 @@ static void show_pre_context(struct grep_opt *opt, struct grep_source *gs,
/* Back forward. */
while (cur < lno) {
- char *eol = bol, sign = (cur == funcname_lno) ? '=' : '-';
+ const char *eol = bol, sign = (cur == funcname_lno) ? '=' : '-';
while (*eol != '\n')
eol++;
@@ -1436,12 +1435,12 @@ static int should_lookahead(struct grep_opt *opt)
static int look_ahead(struct grep_opt *opt,
unsigned long *left_p,
unsigned *lno_p,
- char **bol_p)
+ const char **bol_p)
{
unsigned lno = *lno_p;
- char *bol = *bol_p;
+ const char *bol = *bol_p;
struct grep_pat *p;
- char *sp, *last_bol;
+ const char *sp, *last_bol;
regoff_t earliest = -1;
for (p = opt->pattern_list; p; p = p->next) {
@@ -1543,8 +1542,8 @@ static int is_empty_line(const char *bol, const char *eol)
static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int collect_hits)
{
- char *bol;
- char *peek_bol = NULL;
+ const char *bol;
+ const char *peek_bol = NULL;
unsigned long left;
unsigned lno = 1;
unsigned last_hit = 0;
@@ -1626,7 +1625,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
bol = gs->buf;
left = gs->size;
while (left) {
- char *eol, ch;
+ const char *eol;
int hit;
ssize_t cno;
ssize_t col = -1, icol = -1;
@@ -1647,14 +1646,11 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
&& look_ahead(opt, &left, &lno, &bol))
break;
eol = end_of_line(bol, &left);
- ch = *eol;
- *eol = 0;
if ((ctx == GREP_CONTEXT_HEAD) && (eol == bol))
ctx = GREP_CONTEXT_BODY;
hit = match_line(opt, bol, eol, &col, &icol, ctx, collect_hits);
- *eol = ch;
if (collect_hits)
goto next_line;
@@ -1713,7 +1709,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
}
if (show_function && (!peek_bol || peek_bol < bol)) {
unsigned long peek_left = left;
- char *peek_eol = eol;
+ const char *peek_eol = eol;
/*
* Trailing empty lines are not interesting.
@@ -1825,14 +1821,25 @@ int grep_source(struct grep_opt *opt, struct grep_source *gs)
return grep_source_1(opt, gs, 0);
}
-int grep_buffer(struct grep_opt *opt, char *buf, unsigned long size)
+static void grep_source_init_buf(struct grep_source *gs,
+ const char *buf,
+ unsigned long size)
+{
+ gs->type = GREP_SOURCE_BUF;
+ gs->name = NULL;
+ gs->path = NULL;
+ gs->buf = buf;
+ gs->size = size;
+ gs->driver = NULL;
+ gs->identifier = NULL;
+}
+
+int grep_buffer(struct grep_opt *opt, const char *buf, unsigned long size)
{
struct grep_source gs;
int r;
- grep_source_init(&gs, GREP_SOURCE_BUF, NULL, NULL, NULL);
- gs.buf = buf;
- gs.size = size;
+ grep_source_init_buf(&gs, buf, size);
r = grep_source(opt, &gs);
@@ -1840,28 +1847,30 @@ int grep_buffer(struct grep_opt *opt, char *buf, unsigned long size)
return r;
}
-void grep_source_init(struct grep_source *gs, enum grep_source_type type,
- const char *name, const char *path,
- const void *identifier)
+void grep_source_init_file(struct grep_source *gs, const char *name,
+ const char *path)
{
- gs->type = type;
+ gs->type = GREP_SOURCE_FILE;
gs->name = xstrdup_or_null(name);
gs->path = xstrdup_or_null(path);
gs->buf = NULL;
gs->size = 0;
gs->driver = NULL;
+ gs->identifier = xstrdup(path);
+}
- switch (type) {
- case GREP_SOURCE_FILE:
- gs->identifier = xstrdup(identifier);
- break;
- case GREP_SOURCE_OID:
- gs->identifier = oiddup(identifier);
- break;
- case GREP_SOURCE_BUF:
- gs->identifier = NULL;
- break;
- }
+void grep_source_init_oid(struct grep_source *gs, const char *name,
+ const char *path, const struct object_id *oid,
+ struct repository *repo)
+{
+ gs->type = GREP_SOURCE_OID;
+ gs->name = xstrdup_or_null(name);
+ gs->path = xstrdup_or_null(path);
+ gs->buf = NULL;
+ gs->size = 0;
+ gs->driver = NULL;
+ gs->identifier = oiddup(oid);
+ gs->repo = repo;
}
void grep_source_clear(struct grep_source *gs)
@@ -1877,7 +1886,9 @@ void grep_source_clear_data(struct grep_source *gs)
switch (gs->type) {
case GREP_SOURCE_FILE:
case GREP_SOURCE_OID:
- FREE_AND_NULL(gs->buf);
+ /* these types own the buffer */
+ free((char *)gs->buf);
+ gs->buf = NULL;
gs->size = 0;
break;
case GREP_SOURCE_BUF:
@@ -1890,7 +1901,8 @@ static int grep_source_load_oid(struct grep_source *gs)
{
enum object_type type;
- gs->buf = read_object_file(gs->identifier, &type, &gs->size);
+ gs->buf = repo_read_object_file(gs->repo, gs->identifier, &type,
+ &gs->size);
if (!gs->buf)
return error(_("'%s': unable to read %s"),
gs->name,
diff --git a/grep.h b/grep.h
index 72f82b1e30..3c75ed1fd8 100644
--- a/grep.h
+++ b/grep.h
@@ -120,7 +120,20 @@ struct grep_opt {
struct grep_pat *header_list;
struct grep_pat **header_tail;
struct grep_expr *pattern_expression;
+
+ /*
+ * NEEDSWORK: See if we can remove this field, because the repository
+ * should probably be per-source. That is, grep.c functions using this
+ * field should probably start using "repo" in "struct grep_source"
+ * instead.
+ *
+ * This is potentially the cause of at least one bug - "git grep"
+ * using the textconv attributes from the superproject on the
+ * submodules. See the failing "git grep --textconv" tests in
+ * t7814-grep-recurse-submodules.sh for more information.
+ */
struct repository *repo;
+
const char *prefix;
int prefix_length;
regex_t regexp;
@@ -176,7 +189,7 @@ void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *orig
void append_header_grep_pattern(struct grep_opt *, enum grep_header_field, const char *);
void compile_grep_patterns(struct grep_opt *opt);
void free_grep_patterns(struct grep_opt *opt);
-int grep_buffer(struct grep_opt *opt, char *buf, unsigned long size);
+int grep_buffer(struct grep_opt *opt, const char *buf, unsigned long size);
struct grep_source {
char *name;
@@ -187,17 +200,20 @@ struct grep_source {
GREP_SOURCE_BUF,
} type;
void *identifier;
+ struct repository *repo; /* if GREP_SOURCE_OID */
- char *buf;
+ const char *buf;
unsigned long size;
char *path; /* for attribute lookups */
struct userdiff_driver *driver;
};
-void grep_source_init(struct grep_source *gs, enum grep_source_type type,
- const char *name, const char *path,
- const void *identifier);
+void grep_source_init_file(struct grep_source *gs, const char *name,
+ const char *path);
+void grep_source_init_oid(struct grep_source *gs, const char *name,
+ const char *path, const struct object_id *oid,
+ struct repository *repo);
void grep_source_clear_data(struct grep_source *gs);
void grep_source_clear(struct grep_source *gs);
void grep_source_load_driver(struct grep_source *gs,
@@ -207,7 +223,6 @@ void grep_source_load_driver(struct grep_source *gs,
int grep_source(struct grep_opt *opt, struct grep_source *gs);
struct grep_opt *grep_opt_dup(const struct grep_opt *opt);
-int grep_threads_ok(const struct grep_opt *opt);
/*
* Mutex used around access to the attributes machinery if
diff --git a/help.c b/help.c
index 3c3bdec213..973e47cdc3 100644
--- a/help.c
+++ b/help.c
@@ -11,6 +11,7 @@
#include "version.h"
#include "refs.h"
#include "parse-options.h"
+#include "prompt.h"
struct category_description {
uint32_t category;
@@ -292,9 +293,21 @@ void load_command_list(const char *prefix,
exclude_cmds(other_cmds, main_cmds);
}
-void list_commands(unsigned int colopts,
- struct cmdnames *main_cmds, struct cmdnames *other_cmds)
+static int get_colopts(const char *var, const char *value, void *data)
{
+ unsigned int *colopts = data;
+
+ if (starts_with(var, "column."))
+ return git_column_config(var, value, "help", colopts);
+
+ return 0;
+}
+
+void list_commands(struct cmdnames *main_cmds, struct cmdnames *other_cmds)
+{
+ unsigned int colopts = 0;
+ git_config(get_colopts, &colopts);
+
if (main_cmds->cnt) {
const char *exec_path = git_exec_path();
printf_ln(_("available git commands in '%s'"), exec_path);
@@ -472,6 +485,7 @@ int is_in_cmdlist(struct cmdnames *c, const char *s)
static int autocorrect;
static struct cmdnames aliases;
+#define AUTOCORRECT_PROMPT (-3)
#define AUTOCORRECT_NEVER (-2)
#define AUTOCORRECT_IMMEDIATELY (-1)
@@ -486,6 +500,8 @@ static int git_unknown_cmd_config(const char *var, const char *value, void *cb)
autocorrect = AUTOCORRECT_NEVER;
} else if (!strcmp(value, "immediate")) {
autocorrect = AUTOCORRECT_IMMEDIATELY;
+ } else if (!strcmp(value, "prompt")) {
+ autocorrect = AUTOCORRECT_PROMPT;
} else {
int v = git_config_int(var, value);
autocorrect = (v < 0)
@@ -539,6 +555,12 @@ const char *help_unknown_cmd(const char *cmd)
read_early_config(git_unknown_cmd_config, NULL);
+ /*
+ * Disable autocorrection prompt in a non-interactive session
+ */
+ if ((autocorrect == AUTOCORRECT_PROMPT) && (!isatty(0) || !isatty(2)))
+ autocorrect = AUTOCORRECT_NEVER;
+
if (autocorrect == AUTOCORRECT_NEVER) {
fprintf_ln(stderr, _("git: '%s' is not a git command. See 'git --help'."), cmd);
exit(1);
@@ -618,7 +640,16 @@ const char *help_unknown_cmd(const char *cmd)
_("Continuing under the assumption that "
"you meant '%s'."),
assumed);
- else {
+ else if (autocorrect == AUTOCORRECT_PROMPT) {
+ char *answer;
+ struct strbuf msg = STRBUF_INIT;
+ strbuf_addf(&msg, _("Run '%s' instead? (y/N)"), assumed);
+ answer = git_prompt(msg.buf, PROMPT_ECHO);
+ strbuf_release(&msg);
+ if (!(starts_with(answer, "y") ||
+ starts_with(answer, "Y")))
+ exit(1);
+ } else {
fprintf_ln(stderr,
_("Continuing in %0.1f seconds, "
"assuming that you meant '%s'."),
diff --git a/help.h b/help.h
index 5871e93ba2..9d383f1a0b 100644
--- a/help.h
+++ b/help.h
@@ -37,7 +37,7 @@ void add_cmdname(struct cmdnames *cmds, const char *name, int len);
/* Here we require that excludes is a sorted list. */
void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes);
int is_in_cmdlist(struct cmdnames *cmds, const char *name);
-void list_commands(unsigned int colopts, struct cmdnames *main_cmds, struct cmdnames *other_cmds);
+void list_commands(struct cmdnames *main_cmds, struct cmdnames *other_cmds);
void get_version_info(struct strbuf *buf, int show_build_options);
/*
diff --git a/hook.c b/hook.c
new file mode 100644
index 0000000000..55e1145a4b
--- /dev/null
+++ b/hook.c
@@ -0,0 +1,42 @@
+#include "cache.h"
+#include "hook.h"
+#include "run-command.h"
+
+const char *find_hook(const char *name)
+{
+ static struct strbuf path = STRBUF_INIT;
+
+ strbuf_reset(&path);
+ strbuf_git_path(&path, "hooks/%s", name);
+ if (access(path.buf, X_OK) < 0) {
+ int err = errno;
+
+#ifdef STRIP_EXTENSION
+ strbuf_addstr(&path, STRIP_EXTENSION);
+ if (access(path.buf, X_OK) >= 0)
+ return path.buf;
+ if (errno == EACCES)
+ err = errno;
+#endif
+
+ if (err == EACCES && advice_enabled(ADVICE_IGNORED_HOOK)) {
+ static struct string_list advise_given = STRING_LIST_INIT_DUP;
+
+ if (!string_list_lookup(&advise_given, name)) {
+ string_list_insert(&advise_given, name);
+ advise(_("The '%s' hook was ignored because "
+ "it's not set as executable.\n"
+ "You can disable this warning with "
+ "`git config advice.ignoredHook false`."),
+ path.buf);
+ }
+ }
+ return NULL;
+ }
+ return path.buf;
+}
+
+int hook_exists(const char *name)
+{
+ return !!find_hook(name);
+}
diff --git a/hook.h b/hook.h
new file mode 100644
index 0000000000..6aa36fc7ff
--- /dev/null
+++ b/hook.h
@@ -0,0 +1,16 @@
+#ifndef HOOK_H
+#define HOOK_H
+
+/*
+ * Returns the path to the hook file, or NULL if the hook is missing
+ * or disabled. Note that this points to static storage that will be
+ * overwritten by further calls to find_hook and run_hook_*.
+ */
+const char *find_hook(const char *name);
+
+/**
+ * A boolean version of find_hook()
+ */
+int hook_exists(const char *hookname);
+
+#endif
diff --git a/http-backend.c b/http-backend.c
index b329bf63f0..e7c0eeab23 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -534,7 +534,7 @@ static void get_info_refs(struct strbuf *hdr, char *arg)
if (service_name) {
const char *argv[] = {NULL /* service name */,
- "--stateless-rpc", "--advertise-refs",
+ "--http-backend-info-refs",
".", NULL};
struct rpc_service *svc = select_service(hdr, service_name);
@@ -739,6 +739,7 @@ static int bad_request(struct strbuf *hdr, const struct service_cmd *c)
int cmd_main(int argc, const char **argv)
{
char *method = getenv("REQUEST_METHOD");
+ const char *proto_header;
char *dir;
struct service_cmd *cmd = NULL;
char *cmd_arg = NULL;
@@ -789,6 +790,9 @@ int cmd_main(int argc, const char **argv)
http_config();
max_request_buffer = git_env_ulong("GIT_HTTP_MAX_REQUEST_BUFFER",
max_request_buffer);
+ proto_header = getenv("HTTP_GIT_PROTOCOL");
+ if (proto_header)
+ setenv(GIT_PROTOCOL_ENVIRONMENT, proto_header, 0);
cmd->imp(&hdr, cmd_arg);
return 0;
diff --git a/http-push.c b/http-push.c
index d7cb1675a2..3309aaf004 100644
--- a/http-push.c
+++ b/http-push.c
@@ -203,10 +203,8 @@ static void curl_setup_http(CURL *curl, const char *url,
curl_easy_setopt(curl, CURLOPT_INFILE, buffer);
curl_easy_setopt(curl, CURLOPT_INFILESIZE, buffer->buf.len);
curl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);
-#ifndef NO_CURL_IOCTL
curl_easy_setopt(curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
curl_easy_setopt(curl, CURLOPT_IOCTLDATA, buffer);
-#endif
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_fn);
curl_easy_setopt(curl, CURLOPT_NOBODY, 0);
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, custom_req);
@@ -249,8 +247,6 @@ static void process_response(void *callback_data)
finish_request(request);
}
-#ifdef USE_CURL_MULTI
-
static void start_fetch_loose(struct transfer_request *request)
{
struct active_request_slot *slot;
@@ -299,7 +295,6 @@ static void start_mkcol(struct transfer_request *request)
FREE_AND_NULL(request->url);
}
}
-#endif
static void start_fetch_packed(struct transfer_request *request)
{
@@ -605,7 +600,6 @@ static void finish_request(struct transfer_request *request)
}
}
-#ifdef USE_CURL_MULTI
static int is_running_queue;
static int fill_active_slot(void *unused)
{
@@ -629,7 +623,6 @@ static int fill_active_slot(void *unused)
}
return 0;
}
-#endif
static void get_remote_object_list(unsigned char parent);
@@ -658,10 +651,8 @@ static void add_fetch_request(struct object *obj)
request->next = request_queue_head;
request_queue_head = request;
-#ifdef USE_CURL_MULTI
fill_active_slots();
step_active_slots();
-#endif
}
static int add_send_request(struct object *obj, struct remote_lock *lock)
@@ -696,10 +687,8 @@ static int add_send_request(struct object *obj, struct remote_lock *lock)
request->next = request_queue_head;
request_queue_head = request;
-#ifdef USE_CURL_MULTI
fill_active_slots();
step_active_slots();
-#endif
return 1;
}
@@ -894,7 +883,7 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
slot->results = &results;
curl_setup_http(slot->curl, url, DAV_LOCK, &out_buffer, fwrite_buffer);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
- curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEDATA, &in_buffer);
CALLOC_ARRAY(lock, 1);
lock->timeout = -1;
@@ -1153,7 +1142,7 @@ static void remote_ls(const char *path, int flags,
curl_setup_http(slot->curl, url, DAV_PROPFIND,
&out_buffer, fwrite_buffer);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
- curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEDATA, &in_buffer);
if (start_active_slot(slot)) {
run_active_slot(slot);
@@ -1227,7 +1216,7 @@ static int locking_available(void)
curl_setup_http(slot->curl, repo->url, DAV_PROPFIND,
&out_buffer, fwrite_buffer);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
- curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEDATA, &in_buffer);
if (start_active_slot(slot)) {
run_active_slot(slot);
@@ -1682,21 +1671,15 @@ static int delete_remote_branch(const char *pattern, int force)
static void run_request_queue(void)
{
-#ifdef USE_CURL_MULTI
is_running_queue = 1;
fill_active_slots();
add_fill_function(NULL, fill_active_slot);
-#endif
do {
finish_all_active_slots();
-#ifdef USE_CURL_MULTI
fill_active_slots();
-#endif
} while (request_queue_head && !aborted);
-#ifdef USE_CURL_MULTI
is_running_queue = 0;
-#endif
}
int cmd_main(int argc, const char **argv)
@@ -1770,10 +1753,6 @@ int cmd_main(int argc, const char **argv)
break;
}
-#ifndef USE_CURL_MULTI
- die("git-push is not available for http/https repository when not compiled with USE_CURL_MULTI");
-#endif
-
if (!repo->url)
usage(http_push_usage);
@@ -1786,9 +1765,7 @@ int cmd_main(int argc, const char **argv)
http_init(NULL, repo->url, 1);
-#ifdef USE_CURL_MULTI
is_running_queue = 0;
-#endif
/* Verify DAV compliance/lock support */
if (!locking_available()) {
diff --git a/http-walker.c b/http-walker.c
index 90d8ecb57e..910fae539b 100644
--- a/http-walker.c
+++ b/http-walker.c
@@ -127,7 +127,6 @@ static void release_object_request(struct object_request *obj_req)
free(obj_req);
}
-#ifdef USE_CURL_MULTI
static int fill_active_slot(struct walker *walker)
{
struct object_request *obj_req;
@@ -146,7 +145,6 @@ static int fill_active_slot(struct walker *walker)
}
return 0;
}
-#endif
static void prefetch(struct walker *walker, unsigned char *sha1)
{
@@ -163,10 +161,8 @@ static void prefetch(struct walker *walker, unsigned char *sha1)
http_is_verbose = walker->get_verbosely;
list_add_tail(&newreq->node, &object_queue_head);
-#ifdef USE_CURL_MULTI
fill_active_slots();
step_active_slots();
-#endif
}
static int is_alternate_allowed(const char *url)
@@ -357,11 +353,9 @@ static void fetch_alternates(struct walker *walker, const char *base)
* wait for them to arrive and return to processing this request's
* curl message
*/
-#ifdef USE_CURL_MULTI
while (cdata->got_alternates == 0) {
step_active_slots();
}
-#endif
/* Nothing to do if they've already been fetched */
if (cdata->got_alternates == 1)
@@ -384,7 +378,7 @@ static void fetch_alternates(struct walker *walker, const char *base)
alt_req.walker = walker;
slot->callback_data = &alt_req;
- curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEDATA, &buffer);
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
curl_easy_setopt(slot->curl, CURLOPT_URL, url.buf);
@@ -505,12 +499,8 @@ static int fetch_object(struct walker *walker, unsigned char *hash)
return 0;
}
-#ifdef USE_CURL_MULTI
while (obj_req->state == WAITING)
step_active_slots();
-#else
- start_object_request(walker, obj_req);
-#endif
/*
* obj_req->req might change when fetching alternates in the callback
@@ -623,9 +613,7 @@ struct walker *get_http_walker(const char *url)
walker->cleanup = cleanup;
walker->data = data;
-#ifdef USE_CURL_MULTI
add_fill_function(walker, (int (*)(void *)) fill_active_slot);
-#endif
return walker;
}
diff --git a/http.c b/http.c
index 8119247149..53cf7ad570 100644
--- a/http.c
+++ b/http.c
@@ -1,4 +1,5 @@
#include "git-compat-util.h"
+#include "git-curl-compat.h"
#include "http.h"
#include "config.h"
#include "pack.h"
@@ -19,28 +20,16 @@
static struct trace_key trace_curl = TRACE_KEY_INIT(CURL);
static int trace_curl_data = 1;
static int trace_curl_redact = 1;
-#if LIBCURL_VERSION_NUM >= 0x070a08
long int git_curl_ipresolve = CURL_IPRESOLVE_WHATEVER;
-#else
-long int git_curl_ipresolve;
-#endif
int active_requests;
int http_is_verbose;
ssize_t http_post_buffer = 16 * LARGE_PACKET_MAX;
-#if LIBCURL_VERSION_NUM >= 0x070a06
-#define LIBCURL_CAN_HANDLE_AUTH_ANY
-#endif
-
static int min_curl_sessions = 1;
static int curl_session_count;
-#ifdef USE_CURL_MULTI
static int max_requests = -1;
static CURLM *curlm;
-#endif
-#ifndef NO_CURL_EASY_DUPHANDLE
static CURL *curl_default;
-#endif
#define PREV_BUF_SIZE 4096
@@ -59,25 +48,19 @@ static struct {
{ "sslv2", CURL_SSLVERSION_SSLv2 },
{ "sslv3", CURL_SSLVERSION_SSLv3 },
{ "tlsv1", CURL_SSLVERSION_TLSv1 },
-#if LIBCURL_VERSION_NUM >= 0x072200
+#ifdef GIT_CURL_HAVE_CURL_SSLVERSION_TLSv1_0
{ "tlsv1.0", CURL_SSLVERSION_TLSv1_0 },
{ "tlsv1.1", CURL_SSLVERSION_TLSv1_1 },
{ "tlsv1.2", CURL_SSLVERSION_TLSv1_2 },
#endif
-#if LIBCURL_VERSION_NUM >= 0x073400
+#ifdef GIT_CURL_HAVE_CURL_SSLVERSION_TLSv1_3
{ "tlsv1.3", CURL_SSLVERSION_TLSv1_3 },
#endif
};
-#if LIBCURL_VERSION_NUM >= 0x070903
static const char *ssl_key;
-#endif
-#if LIBCURL_VERSION_NUM >= 0x070908
static const char *ssl_capath;
-#endif
-#if LIBCURL_VERSION_NUM >= 0x071304
static const char *curl_no_proxy;
-#endif
-#if LIBCURL_VERSION_NUM >= 0x072c00
+#ifdef GIT_CURL_HAVE_CURLOPT_PINNEDPUBLICKEY
static const char *ssl_pinnedkey;
#endif
static const char *ssl_cainfo;
@@ -101,9 +84,7 @@ static struct {
{ "digest", CURLAUTH_DIGEST },
{ "negotiate", CURLAUTH_GSSNEGOTIATE },
{ "ntlm", CURLAUTH_NTLM },
-#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY
{ "anyauth", CURLAUTH_ANY },
-#endif
/*
* CURLAUTH_DIGEST_IE has no corresponding command-line option in
* curl(1) and is not included in CURLAUTH_ANY, so we leave it out
@@ -133,27 +114,15 @@ static int curl_empty_auth = -1;
enum http_follow_config http_follow_config = HTTP_FOLLOW_INITIAL;
-#if LIBCURL_VERSION_NUM >= 0x071700
-/* Use CURLOPT_KEYPASSWD as is */
-#elif LIBCURL_VERSION_NUM >= 0x070903
-#define CURLOPT_KEYPASSWD CURLOPT_SSLKEYPASSWD
-#else
-#define CURLOPT_KEYPASSWD CURLOPT_SSLCERTPASSWD
-#endif
-
static struct credential cert_auth = CREDENTIAL_INIT;
static int ssl_cert_password_required;
-#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY
static unsigned long http_auth_methods = CURLAUTH_ANY;
static int http_auth_methods_restricted;
/* Modes for which empty_auth cannot actually help us. */
static unsigned long empty_auth_useless =
CURLAUTH_BASIC
-#ifdef CURLAUTH_DIGEST_IE
| CURLAUTH_DIGEST_IE
-#endif
| CURLAUTH_DIGEST;
-#endif
static struct curl_slist *pragma_header;
static struct curl_slist *no_pragma_header;
@@ -186,7 +155,6 @@ size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_)
return size / eltsize;
}
-#ifndef NO_CURL_IOCTL
curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp)
{
struct buffer *buffer = clientp;
@@ -203,7 +171,6 @@ curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp)
return CURLIOE_UNKNOWNCMD;
}
}
-#endif
size_t fwrite_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_)
{
@@ -237,12 +204,8 @@ static void finish_active_slot(struct active_request_slot *slot)
if (slot->results != NULL) {
slot->results->curl_result = slot->curl_result;
slot->results->http_code = slot->http_code;
-#if LIBCURL_VERSION_NUM >= 0x070a08
curl_easy_getinfo(slot->curl, CURLINFO_HTTPAUTH_AVAIL,
&slot->results->auth_avail);
-#else
- slot->results->auth_avail = 0;
-#endif
curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CONNECTCODE,
&slot->results->http_connectcode);
@@ -255,12 +218,9 @@ static void finish_active_slot(struct active_request_slot *slot)
static void xmulti_remove_handle(struct active_request_slot *slot)
{
-#ifdef USE_CURL_MULTI
curl_multi_remove_handle(curlm, slot->curl);
-#endif
}
-#ifdef USE_CURL_MULTI
static void process_curl_messages(void)
{
int num_messages;
@@ -288,7 +248,6 @@ static void process_curl_messages(void)
curl_message = curl_multi_info_read(curlm, &num_messages);
}
}
-#endif
static int http_options(const char *var, const char *value, void *cb)
{
@@ -305,14 +264,10 @@ static int http_options(const char *var, const char *value, void *cb)
return git_config_string(&ssl_version, var, value);
if (!strcmp("http.sslcert", var))
return git_config_pathname(&ssl_cert, var, value);
-#if LIBCURL_VERSION_NUM >= 0x070903
if (!strcmp("http.sslkey", var))
return git_config_pathname(&ssl_key, var, value);
-#endif
-#if LIBCURL_VERSION_NUM >= 0x070908
if (!strcmp("http.sslcapath", var))
return git_config_pathname(&ssl_capath, var, value);
-#endif
if (!strcmp("http.sslcainfo", var))
return git_config_pathname(&ssl_cainfo, var, value);
if (!strcmp("http.sslcertpasswordprotected", var)) {
@@ -341,18 +296,14 @@ static int http_options(const char *var, const char *value, void *cb)
if (!strcmp("http.minsessions", var)) {
min_curl_sessions = git_config_int(var, value);
-#ifndef USE_CURL_MULTI
if (min_curl_sessions > 1)
min_curl_sessions = 1;
-#endif
return 0;
}
-#ifdef USE_CURL_MULTI
if (!strcmp("http.maxrequests", var)) {
max_requests = git_config_int(var, value);
return 0;
}
-#endif
if (!strcmp("http.lowspeedlimit", var)) {
curl_low_speed_limit = (long)git_config_int(var, value);
return 0;
@@ -423,10 +374,10 @@ static int http_options(const char *var, const char *value, void *cb)
}
if (!strcmp("http.pinnedpubkey", var)) {
-#if LIBCURL_VERSION_NUM >= 0x072c00
+#ifdef GIT_CURL_HAVE_CURLOPT_PINNEDPUBLICKEY
return git_config_pathname(&ssl_pinnedkey, var, value);
#else
- warning(_("Public key pinning not supported with cURL < 7.44.0"));
+ warning(_("Public key pinning not supported with cURL < 7.39.0"));
return 0;
#endif
}
@@ -461,12 +412,6 @@ static int curl_empty_auth_enabled(void)
if (curl_empty_auth >= 0)
return curl_empty_auth;
-#ifndef LIBCURL_CAN_HANDLE_AUTH_ANY
- /*
- * Our libcurl is too old to do AUTH_ANY in the first place;
- * just default to turning the feature off.
- */
-#else
/*
* In the automatic case, kick in the empty-auth
* hack as long as we would potentially try some
@@ -479,7 +424,6 @@ static int curl_empty_auth_enabled(void)
if (http_auth_methods_restricted &&
(http_auth_methods & ~empty_auth_useless))
return 1;
-#endif
return 0;
}
@@ -493,24 +437,8 @@ static void init_curl_http_auth(CURL *result)
credential_fill(&http_auth);
-#if LIBCURL_VERSION_NUM >= 0x071301
curl_easy_setopt(result, CURLOPT_USERNAME, http_auth.username);
curl_easy_setopt(result, CURLOPT_PASSWORD, http_auth.password);
-#else
- {
- static struct strbuf up = STRBUF_INIT;
- /*
- * Note that we assume we only ever have a single set of
- * credentials in a given program run, so we do not have
- * to worry about updating this buffer, only setting its
- * initial value.
- */
- if (!up.len)
- strbuf_addf(&up, "%s:%s",
- http_auth.username, http_auth.password);
- curl_easy_setopt(result, CURLOPT_USERPWD, up.buf);
- }
-#endif
}
/* *var must be free-able */
@@ -524,22 +452,10 @@ static void var_override(const char **var, char *value)
static void set_proxyauth_name_password(CURL *result)
{
-#if LIBCURL_VERSION_NUM >= 0x071301
curl_easy_setopt(result, CURLOPT_PROXYUSERNAME,
proxy_auth.username);
curl_easy_setopt(result, CURLOPT_PROXYPASSWORD,
proxy_auth.password);
-#else
- struct strbuf s = STRBUF_INIT;
-
- strbuf_addstr_urlencode(&s, proxy_auth.username,
- is_rfc3986_unreserved);
- strbuf_addch(&s, ':');
- strbuf_addstr_urlencode(&s, proxy_auth.password,
- is_rfc3986_unreserved);
- curl_proxyuserpwd = strbuf_detach(&s, NULL);
- curl_easy_setopt(result, CURLOPT_PROXYUSERPWD, curl_proxyuserpwd);
-#endif
}
static void init_curl_proxy_auth(CURL *result)
@@ -552,7 +468,6 @@ static void init_curl_proxy_auth(CURL *result)
var_override(&http_proxy_authmethod, getenv("GIT_HTTP_PROXY_AUTHMETHOD"));
-#if LIBCURL_VERSION_NUM >= 0x070a07 /* CURLOPT_PROXYAUTH and CURLAUTH_ANY */
if (http_proxy_authmethod) {
int i;
for (i = 0; i < ARRAY_SIZE(proxy_authmethods); i++) {
@@ -570,7 +485,6 @@ static void init_curl_proxy_auth(CURL *result)
}
else
curl_easy_setopt(result, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
-#endif
}
static int has_cert_password(void)
@@ -587,7 +501,7 @@ static int has_cert_password(void)
return 1;
}
-#if LIBCURL_VERSION_NUM >= 0x073400
+#ifdef GIT_CURL_HAVE_CURLOPT_PROXY_KEYPASSWD
static int has_proxy_cert_password(void)
{
if (http_proxy_ssl_cert == NULL || proxy_ssl_cert_password_required != 1)
@@ -603,13 +517,13 @@ static int has_proxy_cert_password(void)
}
#endif
-#if LIBCURL_VERSION_NUM >= 0x071900
+#ifdef GITCURL_HAVE_CURLOPT_TCP_KEEPALIVE
static void set_curl_keepalive(CURL *c)
{
curl_easy_setopt(c, CURLOPT_TCP_KEEPALIVE, 1);
}
-#elif LIBCURL_VERSION_NUM >= 0x071000
+#else
static int sockopt_callback(void *client, curl_socket_t fd, curlsocktype type)
{
int ka = 1;
@@ -623,19 +537,13 @@ static int sockopt_callback(void *client, curl_socket_t fd, curlsocktype type)
if (rc < 0)
warning_errno("unable to set SO_KEEPALIVE on socket");
- return 0; /* CURL_SOCKOPT_OK only exists since curl 7.21.5 */
+ return CURL_SOCKOPT_OK;
}
static void set_curl_keepalive(CURL *c)
{
curl_easy_setopt(c, CURLOPT_SOCKOPTFUNCTION, sockopt_callback);
}
-
-#else
-static void set_curl_keepalive(CURL *c)
-{
- /* not supported on older curl versions */
-}
#endif
static void redact_sensitive_header(struct strbuf *header)
@@ -643,8 +551,8 @@ static void redact_sensitive_header(struct strbuf *header)
const char *sensitive_header;
if (trace_curl_redact &&
- (skip_prefix(header->buf, "Authorization:", &sensitive_header) ||
- skip_prefix(header->buf, "Proxy-Authorization:", &sensitive_header))) {
+ (skip_iprefix(header->buf, "Authorization:", &sensitive_header) ||
+ skip_iprefix(header->buf, "Proxy-Authorization:", &sensitive_header))) {
/* The first token is the type, which is OK to log */
while (isspace(*sensitive_header))
sensitive_header++;
@@ -654,7 +562,7 @@ static void redact_sensitive_header(struct strbuf *header)
strbuf_setlen(header, sensitive_header - header->buf);
strbuf_addstr(header, " <redacted>");
} else if (trace_curl_redact &&
- skip_prefix(header->buf, "Cookie:", &sensitive_header)) {
+ skip_iprefix(header->buf, "Cookie:", &sensitive_header)) {
struct strbuf redacted_header = STRBUF_INIT;
const char *cookie;
@@ -809,7 +717,6 @@ void setup_curl_trace(CURL *handle)
curl_easy_setopt(handle, CURLOPT_DEBUGDATA, NULL);
}
-#ifdef CURLPROTO_HTTP
static long get_curl_allowed_protocols(int from_user)
{
long allowed_protocols = 0;
@@ -825,9 +732,8 @@ static long get_curl_allowed_protocols(int from_user)
return allowed_protocols;
}
-#endif
-#if LIBCURL_VERSION_NUM >=0x072f00
+#ifdef GIT_CURL_HAVE_CURL_HTTP_VERSION_2
static int get_curl_http_version_opt(const char *version_string, long *opt)
{
int i;
@@ -869,7 +775,7 @@ static CURL *get_curl_handle(void)
curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2);
}
-#if LIBCURL_VERSION_NUM >= 0x072f00 // 7.47.0
+#ifdef GIT_CURL_HAVE_CURL_HTTP_VERSION_2
if (curl_http_version) {
long opt;
if (!get_curl_http_version_opt(curl_http_version, &opt)) {
@@ -879,12 +785,8 @@ static CURL *get_curl_handle(void)
}
#endif
-#if LIBCURL_VERSION_NUM >= 0x070907
curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
-#endif
-#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY
curl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
-#endif
#ifdef CURLGSSAPI_DELEGATION_FLAG
if (curl_deleg) {
@@ -904,7 +806,7 @@ static CURL *get_curl_handle(void)
if (http_ssl_backend && !strcmp("schannel", http_ssl_backend) &&
!http_schannel_check_revoke) {
-#if LIBCURL_VERSION_NUM >= 0x072c00
+#ifdef GIT_CURL_HAVE_CURLSSLOPT_NO_REVOKE
curl_easy_setopt(result, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE);
#else
warning(_("CURLSSLOPT_NO_REVOKE not supported with cURL < 7.44.0"));
@@ -940,28 +842,24 @@ static CURL *get_curl_handle(void)
curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
if (has_cert_password())
curl_easy_setopt(result, CURLOPT_KEYPASSWD, cert_auth.password);
-#if LIBCURL_VERSION_NUM >= 0x070903
if (ssl_key != NULL)
curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key);
-#endif
-#if LIBCURL_VERSION_NUM >= 0x070908
if (ssl_capath != NULL)
curl_easy_setopt(result, CURLOPT_CAPATH, ssl_capath);
-#endif
-#if LIBCURL_VERSION_NUM >= 0x072c00
+#ifdef GIT_CURL_HAVE_CURLOPT_PINNEDPUBLICKEY
if (ssl_pinnedkey != NULL)
curl_easy_setopt(result, CURLOPT_PINNEDPUBLICKEY, ssl_pinnedkey);
#endif
if (http_ssl_backend && !strcmp("schannel", http_ssl_backend) &&
!http_schannel_use_ssl_cainfo) {
curl_easy_setopt(result, CURLOPT_CAINFO, NULL);
-#if LIBCURL_VERSION_NUM >= 0x073400
+#ifdef GIT_CURL_HAVE_CURLOPT_PROXY_CAINFO
curl_easy_setopt(result, CURLOPT_PROXY_CAINFO, NULL);
#endif
} else if (ssl_cainfo != NULL || http_proxy_ssl_ca_info != NULL) {
if (ssl_cainfo != NULL)
curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo);
-#if LIBCURL_VERSION_NUM >= 0x073400
+#ifdef GIT_CURL_HAVE_CURLOPT_PROXY_CAINFO
if (http_proxy_ssl_ca_info != NULL)
curl_easy_setopt(result, CURLOPT_PROXY_CAINFO, http_proxy_ssl_ca_info);
#endif
@@ -975,19 +873,11 @@ static CURL *get_curl_handle(void)
}
curl_easy_setopt(result, CURLOPT_MAXREDIRS, 20);
-#if LIBCURL_VERSION_NUM >= 0x071301
curl_easy_setopt(result, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL);
-#elif LIBCURL_VERSION_NUM >= 0x071101
- curl_easy_setopt(result, CURLOPT_POST301, 1);
-#endif
-#ifdef CURLPROTO_HTTP
curl_easy_setopt(result, CURLOPT_REDIR_PROTOCOLS,
get_curl_allowed_protocols(0));
curl_easy_setopt(result, CURLOPT_PROTOCOLS,
get_curl_allowed_protocols(-1));
-#else
- warning(_("Protocol restrictions not supported with cURL < 7.19.4"));
-#endif
if (getenv("GIT_CURL_VERBOSE"))
http_trace_curl_no_data();
setup_curl_trace(result);
@@ -1002,10 +892,8 @@ static CURL *get_curl_handle(void)
if (curl_ftp_no_epsv)
curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0);
-#ifdef CURLOPT_USE_SSL
if (curl_ssl_try)
curl_easy_setopt(result, CURLOPT_USE_SSL, CURLUSESSL_TRY);
-#endif
/*
* CURL also examines these variables as a fallback; but we need to query
@@ -1040,7 +928,6 @@ static CURL *get_curl_handle(void)
*/
curl_easy_setopt(result, CURLOPT_PROXY, "");
} else if (curl_http_proxy) {
-#if LIBCURL_VERSION_NUM >= 0x071800
if (starts_with(curl_http_proxy, "socks5h"))
curl_easy_setopt(result,
CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME);
@@ -1053,8 +940,7 @@ static CURL *get_curl_handle(void)
else if (starts_with(curl_http_proxy, "socks"))
curl_easy_setopt(result,
CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);
-#endif
-#if LIBCURL_VERSION_NUM >= 0x073400
+#ifdef GIT_CURL_HAVE_CURLOPT_PROXY_KEYPASSWD
else if (starts_with(curl_http_proxy, "https")) {
curl_easy_setopt(result, CURLOPT_PROXYTYPE, CURLPROXY_HTTPS);
@@ -1081,11 +967,9 @@ static CURL *get_curl_handle(void)
die("Invalid proxy URL '%s'", curl_http_proxy);
curl_easy_setopt(result, CURLOPT_PROXY, proxy_auth.host);
-#if LIBCURL_VERSION_NUM >= 0x071304
var_override(&curl_no_proxy, getenv("NO_PROXY"));
var_override(&curl_no_proxy, getenv("no_proxy"));
curl_easy_setopt(result, CURLOPT_NOPROXY, curl_no_proxy);
-#endif
}
init_curl_proxy_auth(result);
@@ -1121,7 +1005,7 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
free(normalized_url);
string_list_clear(&config.vars, 1);
-#if LIBCURL_VERSION_NUM >= 0x073800
+#ifdef GIT_CURL_HAVE_CURLSSLSET_NO_BACKENDS
if (http_ssl_backend) {
const curl_ssl_backend **backends;
struct strbuf buf = STRBUF_INIT;
@@ -1164,7 +1048,6 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
no_pragma_header = curl_slist_append(http_copy_default_headers(),
"Pragma:");
-#ifdef USE_CURL_MULTI
{
char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS");
if (http_max_requests != NULL)
@@ -1174,18 +1057,13 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
curlm = curl_multi_init();
if (!curlm)
die("curl_multi_init failed");
-#endif
if (getenv("GIT_SSL_NO_VERIFY"))
curl_ssl_verify = 0;
set_from_env(&ssl_cert, "GIT_SSL_CERT");
-#if LIBCURL_VERSION_NUM >= 0x070903
set_from_env(&ssl_key, "GIT_SSL_KEY");
-#endif
-#if LIBCURL_VERSION_NUM >= 0x070908
set_from_env(&ssl_capath, "GIT_SSL_CAPATH");
-#endif
set_from_env(&ssl_cainfo, "GIT_SSL_CAINFO");
set_from_env(&user_agent, "GIT_HTTP_USER_AGENT");
@@ -1201,10 +1079,8 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
curl_ssl_verify = 1;
curl_session_count = 0;
-#ifdef USE_CURL_MULTI
if (max_requests < 1)
max_requests = DEFAULT_MAX_REQUESTS;
-#endif
set_from_env(&http_proxy_ssl_cert, "GIT_PROXY_SSL_CERT");
set_from_env(&http_proxy_ssl_key, "GIT_PROXY_SSL_KEY");
@@ -1224,9 +1100,7 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
ssl_cert_password_required = 1;
}
-#ifndef NO_CURL_EASY_DUPHANDLE
curl_default = get_curl_handle();
-#endif
}
void http_cleanup(void)
@@ -1244,13 +1118,9 @@ void http_cleanup(void)
}
active_queue_head = NULL;
-#ifndef NO_CURL_EASY_DUPHANDLE
curl_easy_cleanup(curl_default);
-#endif
-#ifdef USE_CURL_MULTI
curl_multi_cleanup(curlm);
-#endif
curl_global_cleanup();
string_list_clear(&extra_http_headers, 0);
@@ -1297,7 +1167,6 @@ struct active_request_slot *get_active_slot(void)
struct active_request_slot *slot = active_queue_head;
struct active_request_slot *newslot;
-#ifdef USE_CURL_MULTI
int num_transfers;
/* Wait for a slot to open up if the queue is full */
@@ -1306,7 +1175,6 @@ struct active_request_slot *get_active_slot(void)
if (num_transfers < active_requests)
process_curl_messages();
}
-#endif
while (slot != NULL && slot->in_use)
slot = slot->next;
@@ -1329,11 +1197,7 @@ struct active_request_slot *get_active_slot(void)
}
if (slot->curl == NULL) {
-#ifdef NO_CURL_EASY_DUPHANDLE
- slot->curl = get_curl_handle();
-#else
slot->curl = curl_easy_duphandle(curl_default);
-#endif
curl_session_count++;
}
@@ -1367,12 +1231,8 @@ struct active_request_slot *get_active_slot(void)
else
curl_easy_setopt(slot->curl, CURLOPT_FOLLOWLOCATION, 0);
-#if LIBCURL_VERSION_NUM >= 0x070a08
curl_easy_setopt(slot->curl, CURLOPT_IPRESOLVE, git_curl_ipresolve);
-#endif
-#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY
curl_easy_setopt(slot->curl, CURLOPT_HTTPAUTH, http_auth_methods);
-#endif
if (http_auth.password || curl_empty_auth_enabled())
init_curl_http_auth(slot->curl);
@@ -1381,7 +1241,6 @@ struct active_request_slot *get_active_slot(void)
int start_active_slot(struct active_request_slot *slot)
{
-#ifdef USE_CURL_MULTI
CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl);
int num_transfers;
@@ -1399,11 +1258,9 @@ int start_active_slot(struct active_request_slot *slot)
* something.
*/
curl_multi_perform(curlm, &num_transfers);
-#endif
return 1;
}
-#ifdef USE_CURL_MULTI
struct fill_chain {
void *data;
int (*fill)(void *);
@@ -1462,11 +1319,9 @@ void step_active_slots(void)
fill_active_slots();
}
}
-#endif
void run_active_slot(struct active_request_slot *slot)
{
-#ifdef USE_CURL_MULTI
fd_set readfds;
fd_set writefds;
fd_set excfds;
@@ -1479,7 +1334,6 @@ void run_active_slot(struct active_request_slot *slot)
step_active_slots();
if (slot->in_use) {
-#if LIBCURL_VERSION_NUM >= 0x070f04
long curl_timeout;
curl_multi_timeout(curlm, &curl_timeout);
if (curl_timeout == 0) {
@@ -1491,10 +1345,6 @@ void run_active_slot(struct active_request_slot *slot)
select_timeout.tv_sec = curl_timeout / 1000;
select_timeout.tv_usec = (curl_timeout % 1000) * 1000;
}
-#else
- select_timeout.tv_sec = 0;
- select_timeout.tv_usec = 50000;
-#endif
max_fd = -1;
FD_ZERO(&readfds);
@@ -1517,12 +1367,6 @@ void run_active_slot(struct active_request_slot *slot)
select(max_fd+1, &readfds, &writefds, &excfds, &select_timeout);
}
}
-#else
- while (slot->in_use) {
- slot->curl_result = curl_easy_perform(slot->curl);
- finish_active_slot(slot);
- }
-#endif
}
static void release_active_slot(struct active_request_slot *slot)
@@ -1536,9 +1380,7 @@ static void release_active_slot(struct active_request_slot *slot)
curl_session_count--;
}
}
-#ifdef USE_CURL_MULTI
fill_active_slots();
-#endif
}
void finish_all_active_slots(void)
@@ -1647,6 +1489,10 @@ static int handle_curl_result(struct slot_results *results)
*/
credential_reject(&cert_auth);
return HTTP_NOAUTH;
+#ifdef GIT_CURL_HAVE_CURLE_SSL_PINNEDPUBKEYNOTMATCH
+ } else if (results->curl_result == CURLE_SSL_PINNEDPUBKEYNOTMATCH) {
+ return HTTP_NOMATCHPUBLICKEY;
+#endif
} else if (missing_target(results))
return HTTP_MISSING_TARGET;
else if (results->http_code == 401) {
@@ -1654,24 +1500,20 @@ static int handle_curl_result(struct slot_results *results)
credential_reject(&http_auth);
return HTTP_NOAUTH;
} else {
-#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY
http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE;
if (results->auth_avail) {
http_auth_methods &= results->auth_avail;
http_auth_methods_restricted = 1;
}
-#endif
return HTTP_REAUTH;
}
} else {
if (results->http_connectcode == 407)
credential_reject(&proxy_auth);
-#if LIBCURL_VERSION_NUM >= 0x070c00
if (!curl_errorstr[0])
strlcpy(curl_errorstr,
curl_easy_strerror(results->curl_result),
sizeof(curl_errorstr));
-#endif
return HTTP_ERROR;
}
}
@@ -1930,7 +1772,7 @@ static int http_request(const char *url,
curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1);
} else {
curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
- curl_easy_setopt(slot->curl, CURLOPT_FILE, result);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEDATA, result);
if (target == HTTP_REQUEST_FILE) {
off_t posn = ftello(result);
@@ -2347,7 +2189,7 @@ struct http_pack_request *new_direct_http_pack_request(
}
preq->slot = get_active_slot();
- curl_easy_setopt(preq->slot->curl, CURLOPT_FILE, preq->packfile);
+ curl_easy_setopt(preq->slot->curl, CURLOPT_WRITEDATA, preq->packfile);
curl_easy_setopt(preq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
curl_easy_setopt(preq->slot->curl, CURLOPT_URL, preq->url);
curl_easy_setopt(preq->slot->curl, CURLOPT_HTTPHEADER,
@@ -2518,7 +2360,7 @@ struct http_object_request *new_http_object_request(const char *base_url,
freq->slot = get_active_slot();
- curl_easy_setopt(freq->slot->curl, CURLOPT_FILE, freq);
+ curl_easy_setopt(freq->slot->curl, CURLOPT_WRITEDATA, freq);
curl_easy_setopt(freq->slot->curl, CURLOPT_FAILONERROR, 0);
curl_easy_setopt(freq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
curl_easy_setopt(freq->slot->curl, CURLOPT_ERRORBUFFER, freq->errorstr);
diff --git a/http.h b/http.h
index bf3d1270ad..df1590e53a 100644
--- a/http.h
+++ b/http.h
@@ -10,49 +10,7 @@
#include "remote.h"
#include "url.h"
-/*
- * We detect based on the cURL version if multi-transfer is
- * usable in this implementation and define this symbol accordingly.
- * This shouldn't be set by the Makefile or by the user (e.g. via CFLAGS).
- */
-#undef USE_CURL_MULTI
-
-#if LIBCURL_VERSION_NUM >= 0x071000
-#define USE_CURL_MULTI
#define DEFAULT_MAX_REQUESTS 5
-#endif
-
-#if LIBCURL_VERSION_NUM < 0x070704
-#define curl_global_cleanup() do { /* nothing */ } while (0)
-#endif
-
-#if LIBCURL_VERSION_NUM < 0x070800
-#define curl_global_init(a) do { /* nothing */ } while (0)
-#elif LIBCURL_VERSION_NUM >= 0x070c00
-#define curl_global_init(a) curl_global_init_mem(a, xmalloc, free, \
- xrealloc, xstrdup, xcalloc)
-#endif
-
-#if (LIBCURL_VERSION_NUM < 0x070c04) || (LIBCURL_VERSION_NUM == 0x071000)
-#define NO_CURL_EASY_DUPHANDLE
-#endif
-
-#if LIBCURL_VERSION_NUM < 0x070a03
-#define CURLE_HTTP_RETURNED_ERROR CURLE_HTTP_NOT_FOUND
-#endif
-
-#if LIBCURL_VERSION_NUM < 0x070c03
-#define NO_CURL_IOCTL
-#endif
-
-/*
- * CURLOPT_USE_SSL was known as CURLOPT_FTP_SSL up to 7.16.4,
- * and the constants were known as CURLFTPSSL_*
-*/
-#if !defined(CURLOPT_USE_SSL) && defined(CURLOPT_FTP_SSL)
-#define CURLOPT_USE_SSL CURLOPT_FTP_SSL
-#define CURLUSESSL_TRY CURLFTPSSL_TRY
-#endif
struct slot_results {
CURLcode curl_result;
@@ -82,9 +40,7 @@ struct buffer {
size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *strbuf);
size_t fwrite_buffer(char *ptr, size_t eltsize, size_t nmemb, void *strbuf);
size_t fwrite_null(char *ptr, size_t eltsize, size_t nmemb, void *strbuf);
-#ifndef NO_CURL_IOCTL
curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp);
-#endif
/* Slot lifecycle functions */
struct active_request_slot *get_active_slot(void);
@@ -101,11 +57,9 @@ void finish_all_active_slots(void);
int run_one_slot(struct active_request_slot *slot,
struct slot_results *results);
-#ifdef USE_CURL_MULTI
void fill_active_slots(void);
void add_fill_function(void *data, int (*fill)(void *));
void step_active_slots(void);
-#endif
void http_init(struct remote *remote, const char *url,
int proactive_auth);
@@ -200,6 +154,7 @@ struct http_get_options {
#define HTTP_START_FAILED 3
#define HTTP_REAUTH 4
#define HTTP_NOAUTH 5
+#define HTTP_NOMATCHPUBLICKEY 6
/*
* Requests a URL and stores the result in a strbuf.
diff --git a/imap-send.c b/imap-send.c
index a0540ba5cf..e6090a0346 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -1441,7 +1441,7 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
curl_easy_setopt(curl, CURLOPT_PORT, server.port);
if (server.auth_method) {
-#if LIBCURL_VERSION_NUM < 0x072200
+#ifndef GIT_CURL_HAVE_CURLOPT_LOGIN_OPTIONS
warning("No LOGIN_OPTIONS support in this cURL version");
#else
struct strbuf auth = STRBUF_INIT;
@@ -1517,11 +1517,7 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,
if (cred.username) {
if (res == CURLE_OK)
credential_approve(&cred);
-#if LIBCURL_VERSION_NUM >= 0x070d01
else if (res == CURLE_LOGIN_DENIED)
-#else
- else
-#endif
credential_reject(&cred);
}
diff --git a/list-objects.c b/list-objects.c
index 473a332416..2f623f8211 100644
--- a/list-objects.c
+++ b/list-objects.c
@@ -337,8 +337,8 @@ static void add_pending_tree(struct rev_info *revs, struct tree *tree)
add_pending_object(revs, &tree->object, "");
}
-static void traverse_trees_and_blobs(struct traversal_context *ctx,
- struct strbuf *base)
+static void traverse_non_commits(struct traversal_context *ctx,
+ struct strbuf *base)
{
int i;
@@ -410,9 +410,9 @@ static void do_traverse(struct traversal_context *ctx)
* needs a reallocation for each commit. Can we pass the
* tree directory without allocation churn?
*/
- traverse_trees_and_blobs(ctx, &csp);
+ traverse_non_commits(ctx, &csp);
}
- traverse_trees_and_blobs(ctx, &csp);
+ traverse_non_commits(ctx, &csp);
strbuf_release(&csp);
}
diff --git a/list.h b/list.h
index eb601192f4..362a4cd7f5 100644
--- a/list.h
+++ b/list.h
@@ -46,7 +46,10 @@ struct list_head {
#define INIT_LIST_HEAD(ptr) \
(ptr)->next = (ptr)->prev = (ptr)
-#define LIST_HEAD_INIT(name) { &(name), &(name) }
+#define LIST_HEAD_INIT(name) { \
+ .next = &(name), \
+ .prev = &(name), \
+}
/* Add new element at the head of the list. */
static inline void list_add(struct list_head *newp, struct list_head *head)
diff --git a/lockfile.h b/lockfile.h
index db93e6ba73..90af4e66b2 100644
--- a/lockfile.h
+++ b/lockfile.h
@@ -121,7 +121,7 @@ struct lock_file {
struct tempfile *tempfile;
};
-#define LOCK_INIT { NULL }
+#define LOCK_INIT { 0 }
/* String appended to a filename to derive the lockfile name: */
#define LOCK_SUFFIX ".lock"
diff --git a/log-tree.h b/log-tree.h
index 1e8c91dbf2..e7e4641cf8 100644
--- a/log-tree.h
+++ b/log-tree.h
@@ -14,10 +14,8 @@ struct decoration_filter {
};
int parse_decorate_color_config(const char *var, const char *slot_name, const char *value);
-void init_log_tree_opt(struct rev_info *);
int log_tree_diff_flush(struct rev_info *);
int log_tree_commit(struct rev_info *, struct commit *);
-int log_tree_opt_parse(struct rev_info *, const char **, int);
void show_log(struct rev_info *opt);
void format_decorations_extended(struct strbuf *sb, const struct commit *commit,
int use_color,
diff --git a/ls-refs.c b/ls-refs.c
index 88f6c3f60d..54078323dc 100644
--- a/ls-refs.c
+++ b/ls-refs.c
@@ -41,6 +41,12 @@ static void ensure_config_read(void)
}
/*
+ * If we see this many or more "ref-prefix" lines from the client, we consider
+ * it "too many" and will avoid using the prefix feature entirely.
+ */
+#define TOO_MANY_PREFIXES 65536
+
+/*
* Check if one of the prefixes is a prefix of the ref.
* If no prefixes were provided, all refs match.
*/
@@ -65,6 +71,7 @@ struct ls_refs_data {
unsigned peel;
unsigned symrefs;
struct strvec prefixes;
+ struct strbuf buf;
unsigned unborn : 1;
};
@@ -73,7 +80,8 @@ static int send_ref(const char *refname, const struct object_id *oid,
{
struct ls_refs_data *data = cb_data;
const char *refname_nons = strip_namespace(refname);
- struct strbuf refline = STRBUF_INIT;
+
+ strbuf_reset(&data->buf);
if (ref_is_hidden(refname_nons, refname))
return 0;
@@ -82,9 +90,9 @@ static int send_ref(const char *refname, const struct object_id *oid,
return 0;
if (oid)
- strbuf_addf(&refline, "%s %s", oid_to_hex(oid), refname_nons);
+ strbuf_addf(&data->buf, "%s %s", oid_to_hex(oid), refname_nons);
else
- strbuf_addf(&refline, "unborn %s", refname_nons);
+ strbuf_addf(&data->buf, "unborn %s", refname_nons);
if (data->symrefs && flag & REF_ISSYMREF) {
struct object_id unused;
const char *symref_target = resolve_ref_unsafe(refname, 0,
@@ -94,20 +102,19 @@ static int send_ref(const char *refname, const struct object_id *oid,
if (!symref_target)
die("'%s' is a symref but it is not?", refname);
- strbuf_addf(&refline, " symref-target:%s",
+ strbuf_addf(&data->buf, " symref-target:%s",
strip_namespace(symref_target));
}
if (data->peel && oid) {
struct object_id peeled;
if (!peel_iterated_oid(oid, &peeled))
- strbuf_addf(&refline, " peeled:%s", oid_to_hex(&peeled));
+ strbuf_addf(&data->buf, " peeled:%s", oid_to_hex(&peeled));
}
- strbuf_addch(&refline, '\n');
- packet_write(1, refline.buf, refline.len);
+ strbuf_addch(&data->buf, '\n');
+ packet_fwrite(stdout, data->buf.buf, data->buf.len);
- strbuf_release(&refline);
return 0;
}
@@ -138,13 +145,13 @@ static int ls_refs_config(const char *var, const char *value, void *data)
return parse_hide_refs_config(var, value, "uploadpack");
}
-int ls_refs(struct repository *r, struct strvec *keys,
- struct packet_reader *request)
+int ls_refs(struct repository *r, struct packet_reader *request)
{
struct ls_refs_data data;
memset(&data, 0, sizeof(data));
strvec_init(&data.prefixes);
+ strbuf_init(&data.buf, 0);
ensure_config_read();
git_config(ls_refs_config, NULL);
@@ -157,22 +164,35 @@ int ls_refs(struct repository *r, struct strvec *keys,
data.peel = 1;
else if (!strcmp("symrefs", arg))
data.symrefs = 1;
- else if (skip_prefix(arg, "ref-prefix ", &out))
- strvec_push(&data.prefixes, out);
+ else if (skip_prefix(arg, "ref-prefix ", &out)) {
+ if (data.prefixes.nr < TOO_MANY_PREFIXES)
+ strvec_push(&data.prefixes, out);
+ }
else if (!strcmp("unborn", arg))
data.unborn = allow_unborn;
+ else
+ die(_("unexpected line: '%s'"), arg);
}
if (request->status != PACKET_READ_FLUSH)
die(_("expected flush after ls-refs arguments"));
+ /*
+ * If we saw too many prefixes, we must avoid using them at all; as
+ * soon as we have any prefix, they are meant to form a comprehensive
+ * list.
+ */
+ if (data.prefixes.nr >= TOO_MANY_PREFIXES)
+ strvec_clear(&data.prefixes);
+
send_possibly_unborn_head(&data);
if (!data.prefixes.nr)
strvec_push(&data.prefixes, "");
for_each_fullref_in_prefixes(get_git_namespace(), data.prefixes.v,
- send_ref, &data, 0);
- packet_flush(1);
+ send_ref, &data);
+ packet_fflush(stdout);
strvec_clear(&data.prefixes);
+ strbuf_release(&data.buf);
return 0;
}
diff --git a/ls-refs.h b/ls-refs.h
index a99e4be0bd..e2243a547b 100644
--- a/ls-refs.h
+++ b/ls-refs.h
@@ -2,10 +2,8 @@
#define LS_REFS_H
struct repository;
-struct strvec;
struct packet_reader;
-int ls_refs(struct repository *r, struct strvec *keys,
- struct packet_reader *request);
+int ls_refs(struct repository *r, struct packet_reader *request);
int ls_refs_advertise(struct repository *r, struct strbuf *value);
#endif /* LS_REFS_H */
diff --git a/mailmap.c b/mailmap.c
index 462b395634..40ce152024 100644
--- a/mailmap.c
+++ b/mailmap.c
@@ -37,6 +37,7 @@ static void free_mailmap_info(void *p, const char *s)
s, debug_str(mi->name), debug_str(mi->email));
free(mi->name);
free(mi->email);
+ free(mi);
}
static void free_mailmap_entry(void *p, const char *s)
@@ -52,6 +53,7 @@ static void free_mailmap_entry(void *p, const char *s)
me->namemap.strdup_strings = 1;
string_list_clear_func(&me->namemap, free_mailmap_info);
+ free(me);
}
/*
diff --git a/merge-ort.c b/merge-ort.c
index 6eb910d6f0..e5456f4722 100644
--- a/merge-ort.c
+++ b/merge-ort.c
@@ -32,6 +32,7 @@
#include "promisor-remote.h"
#include "revision.h"
#include "strmap.h"
+#include "submodule-config.h"
#include "submodule.h"
#include "tree.h"
#include "unpack-trees.h"
@@ -303,8 +304,6 @@ struct merge_options_internal {
* * these keys serve to intern all the path strings, which allows
* us to do pointer comparison on directory names instead of
* strcmp; we just have to be careful to use the interned strings.
- * (Technically paths_to_free may track some strings that were
- * removed from froms paths.)
*
* The values of paths:
* * either a pointer to a merged_info, or a conflict_info struct
@@ -340,14 +339,14 @@ struct merge_options_internal {
struct strmap conflicted;
/*
- * paths_to_free: additional list of strings to free
+ * pool: memory pool for fast allocation/deallocation
*
- * If keys are removed from "paths", they are added to paths_to_free
- * to ensure they are later freed. We avoid free'ing immediately since
- * other places (e.g. conflict_info.pathnames[]) may still be
- * referencing these paths.
+ * We allocate room for lots of filenames and auxiliary data
+ * structures in merge_options_internal, and it tends to all be
+ * freed together too. Using a memory pool for these provides a
+ * nice speedup.
*/
- struct string_list paths_to_free;
+ struct mem_pool pool;
/*
* output: special messages and conflict notices for various paths
@@ -519,64 +518,45 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,
{
struct rename_info *renames = &opti->renames;
int i;
- void (*strmap_func)(struct strmap *, int) =
+ void (*strmap_clear_func)(struct strmap *, int) =
reinitialize ? strmap_partial_clear : strmap_clear;
- void (*strintmap_func)(struct strintmap *) =
+ void (*strintmap_clear_func)(struct strintmap *) =
reinitialize ? strintmap_partial_clear : strintmap_clear;
- void (*strset_func)(struct strset *) =
+ void (*strset_clear_func)(struct strset *) =
reinitialize ? strset_partial_clear : strset_clear;
- /*
- * We marked opti->paths with strdup_strings = 0, so that we
- * wouldn't have to make another copy of the fullpath created by
- * make_traverse_path from setup_path_info(). But, now that we've
- * used it and have no other references to these strings, it is time
- * to deallocate them.
- */
- free_strmap_strings(&opti->paths);
- strmap_func(&opti->paths, 1);
+ strmap_clear_func(&opti->paths, 0);
/*
* All keys and values in opti->conflicted are a subset of those in
* opti->paths. We don't want to deallocate anything twice, so we
* don't free the keys and we pass 0 for free_values.
*/
- strmap_func(&opti->conflicted, 0);
-
- /*
- * opti->paths_to_free is similar to opti->paths; we created it with
- * strdup_strings = 0 to avoid making _another_ copy of the fullpath
- * but now that we've used it and have no other references to these
- * strings, it is time to deallocate them. We do so by temporarily
- * setting strdup_strings to 1.
- */
- opti->paths_to_free.strdup_strings = 1;
- string_list_clear(&opti->paths_to_free, 0);
- opti->paths_to_free.strdup_strings = 0;
+ strmap_clear_func(&opti->conflicted, 0);
if (opti->attr_index.cache_nr) /* true iff opt->renormalize */
discard_index(&opti->attr_index);
/* Free memory used by various renames maps */
for (i = MERGE_SIDE1; i <= MERGE_SIDE2; ++i) {
- strintmap_func(&renames->dirs_removed[i]);
- strmap_func(&renames->dir_renames[i], 0);
- strintmap_func(&renames->relevant_sources[i]);
+ strintmap_clear_func(&renames->dirs_removed[i]);
+ strmap_clear_func(&renames->dir_renames[i], 0);
+ strintmap_clear_func(&renames->relevant_sources[i]);
if (!reinitialize)
assert(renames->cached_pairs_valid_side == 0);
if (i != renames->cached_pairs_valid_side &&
-1 != renames->cached_pairs_valid_side) {
- strset_func(&renames->cached_target_names[i]);
- strmap_func(&renames->cached_pairs[i], 1);
- strset_func(&renames->cached_irrelevant[i]);
+ strset_clear_func(&renames->cached_target_names[i]);
+ strmap_clear_func(&renames->cached_pairs[i], 1);
+ strset_clear_func(&renames->cached_irrelevant[i]);
partial_clear_dir_rename_count(&renames->dir_rename_count[i]);
if (!reinitialize)
strmap_clear(&renames->dir_rename_count[i], 1);
}
}
for (i = MERGE_SIDE1; i <= MERGE_SIDE2; ++i) {
- strintmap_func(&renames->deferred[i].possible_trivial_merges);
- strset_func(&renames->deferred[i].target_dirs);
+ strintmap_clear_func(&renames->deferred[i].possible_trivial_merges);
+ strset_clear_func(&renames->deferred[i].target_dirs);
renames->deferred[i].trivial_merges_okay = 1; /* 1 == maybe */
}
renames->cached_pairs_valid_side = 0;
@@ -603,6 +583,8 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,
strmap_clear(&opti->output, 0);
}
+ mem_pool_discard(&opti->pool, 0);
+
/* Clean out callback_data as well. */
FREE_AND_NULL(renames->callback_data);
renames->callback_data_nr = renames->callback_data_alloc = 0;
@@ -665,6 +647,36 @@ static void path_msg(struct merge_options *opt,
strbuf_addch(sb, '\n');
}
+static struct diff_filespec *pool_alloc_filespec(struct mem_pool *pool,
+ const char *path)
+{
+ /* Similar to alloc_filespec(), but allocate from pool and reuse path */
+ struct diff_filespec *spec;
+
+ spec = mem_pool_calloc(pool, 1, sizeof(*spec));
+ spec->path = (char*)path; /* spec won't modify it */
+
+ spec->count = 1;
+ spec->is_binary = -1;
+ return spec;
+}
+
+static struct diff_filepair *pool_diff_queue(struct mem_pool *pool,
+ struct diff_queue_struct *queue,
+ struct diff_filespec *one,
+ struct diff_filespec *two)
+{
+ /* Same code as diff_queue(), except allocate from pool */
+ struct diff_filepair *dp;
+
+ dp = mem_pool_calloc(pool, 1, sizeof(*dp));
+ dp->one = one;
+ dp->two = two;
+ if (queue)
+ diff_q(queue, dp);
+ return dp;
+}
+
/* add a string to a strbuf, but converting "/" to "_" */
static void add_flattened_path(struct strbuf *out, const char *s)
{
@@ -793,8 +805,9 @@ static void setup_path_info(struct merge_options *opt,
assert(!df_conflict || !resolved); /* df_conflict implies !resolved */
assert(resolved == (merged_version != NULL));
- mi = xcalloc(1, resolved ? sizeof(struct merged_info) :
- sizeof(struct conflict_info));
+ mi = mem_pool_calloc(&opt->priv->pool, 1,
+ resolved ? sizeof(struct merged_info) :
+ sizeof(struct conflict_info));
mi->directory_name = current_dir_name;
mi->basename_offset = current_dir_name_len;
mi->clean = !!resolved;
@@ -891,11 +904,11 @@ static void add_pair(struct merge_options *opt,
return;
}
- one = alloc_filespec(pathname);
- two = alloc_filespec(pathname);
+ one = pool_alloc_filespec(&opt->priv->pool, pathname);
+ two = pool_alloc_filespec(&opt->priv->pool, pathname);
fill_filespec(is_add ? two : one,
&names[names_idx].oid, 1, names[names_idx].mode);
- diff_queue(&renames->pairs[side], one, two);
+ pool_diff_queue(&opt->priv->pool, &renames->pairs[side], one, two);
}
static void collect_rename_info(struct merge_options *opt,
@@ -1086,7 +1099,7 @@ static int collect_merge_info_callback(int n,
len = traverse_path_len(info, p->pathlen);
/* +1 in both of the following lines to include the NUL byte */
- fullpath = xmalloc(len + 1);
+ fullpath = mem_pool_alloc(&opt->priv->pool, len + 1);
make_traverse_path(fullpath, len + 1, info, p->path, p->pathlen);
/*
@@ -1341,7 +1354,7 @@ static int handle_deferred_entries(struct merge_options *opt,
copy = renames->deferred[side].possible_trivial_merges;
strintmap_init_with_options(&renames->deferred[side].possible_trivial_merges,
0,
- NULL,
+ &opt->priv->pool,
0);
strintmap_for_each_entry(&copy, &iter, entry) {
const char *path = entry->key;
@@ -1499,7 +1512,6 @@ static int find_first_merges(struct repository *repo,
xsnprintf(merged_revision, sizeof(merged_revision), "^%s",
oid_to_hex(&a->object.oid));
repo_init_revisions(repo, &revs, NULL);
- rev_opts.submodule = path;
/* FIXME: can't handle linked worktrees in submodules yet */
revs.single_worktree = path != NULL;
setup_revisions(ARRAY_SIZE(rev_args)-1, rev_args, &revs, &rev_opts);
@@ -1509,7 +1521,7 @@ static int find_first_merges(struct repository *repo,
die("revision walk setup failed");
while ((commit = get_revision(&revs)) != NULL) {
struct object *o = &(commit->object);
- if (in_merge_bases(b, commit))
+ if (repo_in_merge_bases(repo, b, commit))
add_object_array(o, NULL, &merges);
}
reset_revision_walk();
@@ -1524,7 +1536,7 @@ static int find_first_merges(struct repository *repo,
contains_another = 0;
for (j = 0; j < merges.nr; j++) {
struct commit *m2 = (struct commit *) merges.objects[j].item;
- if (i != j && in_merge_bases(m2, m1)) {
+ if (i != j && repo_in_merge_bases(repo, m2, m1)) {
contains_another = 1;
break;
}
@@ -1545,10 +1557,12 @@ static int merge_submodule(struct merge_options *opt,
const struct object_id *b,
struct object_id *result)
{
+ struct repository subrepo;
+ struct strbuf sb = STRBUF_INIT;
+ int ret = 0;
struct commit *commit_o, *commit_a, *commit_b;
int parent_count;
struct object_array merges;
- struct strbuf sb = STRBUF_INIT;
int i;
int search = !opt->priv->call_depth;
@@ -1564,6 +1578,10 @@ static int merge_submodule(struct merge_options *opt,
if (is_null_oid(b))
return 0;
+ /*
+ * NEEDSWORK: Remove this when all submodule object accesses are
+ * through explicitly specified repositores.
+ */
if (add_submodule_odb(path)) {
path_msg(opt, path, 0,
_("Failed to merge submodule %s (not checked out)"),
@@ -1571,39 +1589,48 @@ static int merge_submodule(struct merge_options *opt,
return 0;
}
- if (!(commit_o = lookup_commit_reference(opt->repo, o)) ||
- !(commit_a = lookup_commit_reference(opt->repo, a)) ||
- !(commit_b = lookup_commit_reference(opt->repo, b))) {
+ if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) {
+ path_msg(opt, path, 0,
+ _("Failed to merge submodule %s (not checked out)"),
+ path);
+ return 0;
+ }
+
+ if (!(commit_o = lookup_commit_reference(&subrepo, o)) ||
+ !(commit_a = lookup_commit_reference(&subrepo, a)) ||
+ !(commit_b = lookup_commit_reference(&subrepo, b))) {
path_msg(opt, path, 0,
_("Failed to merge submodule %s (commits not present)"),
path);
- return 0;
+ goto cleanup;
}
/* check whether both changes are forward */
- if (!in_merge_bases(commit_o, commit_a) ||
- !in_merge_bases(commit_o, commit_b)) {
+ if (!repo_in_merge_bases(&subrepo, commit_o, commit_a) ||
+ !repo_in_merge_bases(&subrepo, commit_o, commit_b)) {
path_msg(opt, path, 0,
_("Failed to merge submodule %s "
"(commits don't follow merge-base)"),
path);
- return 0;
+ goto cleanup;
}
/* Case #1: a is contained in b or vice versa */
- if (in_merge_bases(commit_a, commit_b)) {
+ if (repo_in_merge_bases(&subrepo, commit_a, commit_b)) {
oidcpy(result, b);
path_msg(opt, path, 1,
_("Note: Fast-forwarding submodule %s to %s"),
path, oid_to_hex(b));
- return 1;
+ ret = 1;
+ goto cleanup;
}
- if (in_merge_bases(commit_b, commit_a)) {
+ if (repo_in_merge_bases(&subrepo, commit_b, commit_a)) {
oidcpy(result, a);
path_msg(opt, path, 1,
_("Note: Fast-forwarding submodule %s to %s"),
path, oid_to_hex(a));
- return 1;
+ ret = 1;
+ goto cleanup;
}
/*
@@ -1615,10 +1642,10 @@ static int merge_submodule(struct merge_options *opt,
/* Skip the search if makes no sense to the calling context. */
if (!search)
- return 0;
+ goto cleanup;
/* find commit which merges them */
- parent_count = find_first_merges(opt->repo, path, commit_a, commit_b,
+ parent_count = find_first_merges(&subrepo, path, commit_a, commit_b,
&merges);
switch (parent_count) {
case 0:
@@ -1652,7 +1679,9 @@ static int merge_submodule(struct merge_options *opt,
}
object_array_clear(&merges);
- return 0;
+cleanup:
+ repo_clear(&subrepo);
+ return ret;
}
static void initialize_attr_index(struct merge_options *opt)
@@ -2293,12 +2322,17 @@ static void apply_directory_rename_modifications(struct merge_options *opt,
VERIFY_CI(ci);
/* Find parent directories missing from opt->priv->paths */
- cur_path = new_path;
+ cur_path = mem_pool_strdup(&opt->priv->pool, new_path);
+ free((char*)new_path);
+ new_path = (char *)cur_path;
+
while (1) {
/* Find the parent directory of cur_path */
char *last_slash = strrchr(cur_path, '/');
if (last_slash) {
- parent_name = xstrndup(cur_path, last_slash - cur_path);
+ parent_name = mem_pool_strndup(&opt->priv->pool,
+ cur_path,
+ last_slash - cur_path);
} else {
parent_name = opt->priv->toplevel_dir;
break;
@@ -2307,7 +2341,6 @@ static void apply_directory_rename_modifications(struct merge_options *opt,
/* Look it up in opt->priv->paths */
entry = strmap_get_entry(&opt->priv->paths, parent_name);
if (entry) {
- free((char*)parent_name);
parent_name = entry->key; /* reuse known pointer */
break;
}
@@ -2334,13 +2367,6 @@ static void apply_directory_rename_modifications(struct merge_options *opt,
parent_name = cur_dir;
}
- /*
- * We are removing old_path from opt->priv->paths. old_path also will
- * eventually need to be freed, but it may still be used by e.g.
- * ci->pathnames. So, store it in another string-list for now.
- */
- string_list_append(&opt->priv->paths_to_free, old_path);
-
assert(ci->filemask == 2 || ci->filemask == 4);
assert(ci->dirmask == 0);
strmap_remove(&opt->priv->paths, old_path, 0);
@@ -2374,7 +2400,6 @@ static void apply_directory_rename_modifications(struct merge_options *opt,
new_ci->stages[index].mode = ci->stages[index].mode;
oidcpy(&new_ci->stages[index].oid, &ci->stages[index].oid);
- free(ci);
ci = new_ci;
}
@@ -2802,10 +2827,23 @@ static void use_cached_pairs(struct merge_options *opt,
if (!new_name)
new_name = old_name;
+ /*
+ * cached_pairs has *copies* of old_name and new_name,
+ * because it has to persist across merges. Since
+ * pool_alloc_filespec() will just re-use the existing
+ * filenames, which will also get re-used by
+ * opt->priv->paths if they become renames, and then
+ * get freed at the end of the merge, that would leave
+ * the copy in cached_pairs dangling. Avoid this by
+ * making a copy here.
+ */
+ old_name = mem_pool_strdup(&opt->priv->pool, old_name);
+ new_name = mem_pool_strdup(&opt->priv->pool, new_name);
+
/* We don't care about oid/mode, only filenames and status */
- one = alloc_filespec(old_name);
- two = alloc_filespec(new_name);
- diff_queue(pairs, one, two);
+ one = pool_alloc_filespec(&opt->priv->pool, old_name);
+ two = pool_alloc_filespec(&opt->priv->pool, new_name);
+ pool_diff_queue(&opt->priv->pool, pairs, one, two);
pairs->queue[pairs->nr-1]->status = entry->value ? 'R' : 'D';
}
}
@@ -2913,6 +2951,7 @@ static int detect_regular_renames(struct merge_options *opt,
diff_queued_diff = renames->pairs[side_index];
trace2_region_enter("diff", "diffcore_rename", opt->repo);
diffcore_rename_extended(&diff_opts,
+ &opt->priv->pool,
&renames->relevant_sources[side_index],
&renames->dirs_removed[side_index],
&renames->dir_rename_count[side_index],
@@ -2963,7 +3002,7 @@ static int collect_renames(struct merge_options *opt,
if (p->status != 'A' && p->status != 'R') {
possibly_cache_new_pair(renames, p, side_index, NULL);
- diff_free_filepair(p);
+ pool_diff_free_filepair(&opt->priv->pool, p);
continue;
}
@@ -2976,7 +3015,7 @@ static int collect_renames(struct merge_options *opt,
possibly_cache_new_pair(renames, p, side_index, new_path);
if (p->status != 'R' && !new_path) {
- diff_free_filepair(p);
+ pool_diff_free_filepair(&opt->priv->pool, p);
continue;
}
@@ -3094,7 +3133,7 @@ cleanup:
side_pairs = &renames->pairs[s];
for (i = 0; i < side_pairs->nr; ++i) {
struct diff_filepair *p = side_pairs->queue[i];
- diff_free_filepair(p);
+ pool_diff_free_filepair(&opt->priv->pool, p);
}
}
@@ -3107,7 +3146,8 @@ simple_cleanup:
if (combined.nr) {
int i;
for (i = 0; i < combined.nr; i++)
- diff_free_filepair(combined.queue[i]);
+ pool_diff_free_filepair(&opt->priv->pool,
+ combined.queue[i]);
free(combined.queue);
}
@@ -3581,7 +3621,8 @@ static void process_entry(struct merge_options *opt,
* the directory to remain here, so we need to move this
* path to some new location.
*/
- CALLOC_ARRAY(new_ci, 1);
+ new_ci = mem_pool_calloc(&opt->priv->pool, 1, sizeof(*new_ci));
+
/* We don't really want new_ci->merged.result copied, but it'll
* be overwritten below so it doesn't matter. We also don't
* want any directory mode/oid values copied, but we'll zero
@@ -3673,7 +3714,8 @@ static void process_entry(struct merge_options *opt,
const char *a_path = NULL, *b_path = NULL;
int rename_a = 0, rename_b = 0;
- new_ci = xmalloc(sizeof(*new_ci));
+ new_ci = mem_pool_alloc(&opt->priv->pool,
+ sizeof(*new_ci));
if (S_ISREG(a_mode))
rename_a = 1;
@@ -3742,17 +3784,8 @@ static void process_entry(struct merge_options *opt,
b_path = path;
strmap_put(&opt->priv->paths, b_path, new_ci);
- if (rename_a && rename_b) {
+ if (rename_a && rename_b)
strmap_remove(&opt->priv->paths, path, 0);
- /*
- * We removed path from opt->priv->paths. path
- * will also eventually need to be freed, but
- * it may still be used by e.g. ci->pathnames.
- * So, store it in another string-list for now.
- */
- string_list_append(&opt->priv->paths_to_free,
- path);
- }
/*
* Do special handling for b_path since process_entry()
@@ -4029,11 +4062,7 @@ static int checkout(struct merge_options *opt,
unpack_opts.quiet = 0; /* FIXME: sequencer might want quiet? */
unpack_opts.verbose_update = (opt->verbosity > 2);
unpack_opts.fn = twoway_merge;
- if (1/* FIXME: opts->overwrite_ignore*/) {
- CALLOC_ARRAY(unpack_opts.dir, 1);
- unpack_opts.dir->flags |= DIR_SHOW_IGNORED;
- setup_standard_excludes(unpack_opts.dir);
- }
+ unpack_opts.preserve_ignored = 0; /* FIXME: !opts->overwrite_ignore */
parse_tree(prev);
init_tree_desc(&trees[0], prev->buffer, prev->size);
parse_tree(next);
@@ -4041,8 +4070,6 @@ static int checkout(struct merge_options *opt,
ret = unpack_trees(2, trees, &unpack_opts);
clear_unpack_trees_porcelain(&unpack_opts);
- dir_clear(unpack_opts.dir);
- FREE_AND_NULL(unpack_opts.dir);
return ret;
}
@@ -4058,6 +4085,21 @@ static int record_conflicted_index_entries(struct merge_options *opt)
if (strmap_empty(&opt->priv->conflicted))
return 0;
+ /*
+ * We are in a conflicted state. These conflicts might be inside
+ * sparse-directory entries, so check if any entries are outside
+ * of the sparse-checkout cone preemptively.
+ *
+ * We set original_cache_nr below, but that might change if
+ * index_name_pos() calls ask for paths within sparse directories.
+ */
+ strmap_for_each_entry(&opt->priv->conflicted, &iter, e) {
+ if (!path_in_sparse_checkout(e->key, index)) {
+ ensure_full_index(index);
+ break;
+ }
+ }
+
/* If any entries have skip_worktree set, we'll have to check 'em out */
state.force = 1;
state.quiet = 1;
@@ -4065,7 +4107,7 @@ static int record_conflicted_index_entries(struct merge_options *opt)
state.istate = index;
original_cache_nr = index->cache_nr;
- /* Put every entry from paths into plist, then sort */
+ /* Append every entry from conflicted into index, then sort */
strmap_for_each_entry(&opt->priv->conflicted, &iter, e) {
const char *path = e->key;
struct conflict_info *ci = e->value;
@@ -4293,6 +4335,7 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)
{
struct rename_info *renames;
int i;
+ struct mem_pool *pool = NULL;
/* Sanity checks on opt */
trace2_region_enter("merge", "sanity checks", opt->repo);
@@ -4358,9 +4401,11 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)
/* Initialization of various renames fields */
renames = &opt->priv->renames;
+ mem_pool_init(&opt->priv->pool, 0);
+ pool = &opt->priv->pool;
for (i = MERGE_SIDE1; i <= MERGE_SIDE2; i++) {
strintmap_init_with_options(&renames->dirs_removed[i],
- NOT_RELEVANT, NULL, 0);
+ NOT_RELEVANT, pool, 0);
strmap_init_with_options(&renames->dir_rename_count[i],
NULL, 1);
strmap_init_with_options(&renames->dir_renames[i],
@@ -4374,7 +4419,7 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)
*/
strintmap_init_with_options(&renames->relevant_sources[i],
-1 /* explicitly invalid */,
- NULL, 0);
+ pool, 0);
strmap_init_with_options(&renames->cached_pairs[i],
NULL, 1);
strset_init_with_options(&renames->cached_irrelevant[i],
@@ -4384,9 +4429,9 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)
}
for (i = MERGE_SIDE1; i <= MERGE_SIDE2; i++) {
strintmap_init_with_options(&renames->deferred[i].possible_trivial_merges,
- 0, NULL, 0);
+ 0, pool, 0);
strset_init_with_options(&renames->deferred[i].target_dirs,
- NULL, 1);
+ pool, 1);
renames->deferred[i].trivial_merges_okay = 1; /* 1 == maybe */
}
@@ -4394,14 +4439,13 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)
* Although we initialize opt->priv->paths with strdup_strings=0,
* that's just to avoid making yet another copy of an allocated
* string. Putting the entry into paths means we are taking
- * ownership, so we will later free it. paths_to_free is similar.
+ * ownership, so we will later free it.
*
* In contrast, conflicted just has a subset of keys from paths, so
* we don't want to free those (it'd be a duplicate free).
*/
- strmap_init_with_options(&opt->priv->paths, NULL, 0);
- strmap_init_with_options(&opt->priv->conflicted, NULL, 0);
- string_list_init_nodup(&opt->priv->paths_to_free);
+ strmap_init_with_options(&opt->priv->paths, pool, 0);
+ strmap_init_with_options(&opt->priv->conflicted, pool, 0);
/*
* keys & strbufs in output will sometimes need to outlive "paths",
diff --git a/merge-recursive.c b/merge-recursive.c
index 3355d50e8a..c553751889 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -24,6 +24,7 @@
#include "repository.h"
#include "revision.h"
#include "string-list.h"
+#include "submodule-config.h"
#include "submodule.h"
#include "tag.h"
#include "tree-walk.h"
@@ -55,10 +56,7 @@ static int path_hashmap_cmp(const void *cmp_data,
a = container_of(eptr, const struct path_hashmap_entry, e);
b = container_of(entry_or_key, const struct path_hashmap_entry, e);
- if (ignore_case)
- return strcasecmp(a->path, key ? key : b->path);
- else
- return strcmp(a->path, key ? key : b->path);
+ return fspathcmp(a->path, key ? key : b->path);
}
/*
@@ -411,8 +409,11 @@ static int unpack_trees_start(struct merge_options *opt,
memset(&opt->priv->unpack_opts, 0, sizeof(opt->priv->unpack_opts));
if (opt->priv->call_depth)
opt->priv->unpack_opts.index_only = 1;
- else
+ else {
opt->priv->unpack_opts.update = 1;
+ /* FIXME: should only do this if !overwrite_ignore */
+ opt->priv->unpack_opts.preserve_ignored = 0;
+ }
opt->priv->unpack_opts.merge = 1;
opt->priv->unpack_opts.head_idx = 2;
opt->priv->unpack_opts.fn = threeway_merge;
@@ -1113,7 +1114,6 @@ static int find_first_merges(struct repository *repo,
xsnprintf(merged_revision, sizeof(merged_revision), "^%s",
oid_to_hex(&a->object.oid));
repo_init_revisions(repo, &revs, NULL);
- rev_opts.submodule = path;
/* FIXME: can't handle linked worktrees in submodules yet */
revs.single_worktree = path != NULL;
setup_revisions(ARRAY_SIZE(rev_args)-1, rev_args, &revs, &rev_opts);
@@ -1123,7 +1123,7 @@ static int find_first_merges(struct repository *repo,
die("revision walk setup failed");
while ((commit = get_revision(&revs)) != NULL) {
struct object *o = &(commit->object);
- if (in_merge_bases(b, commit))
+ if (repo_in_merge_bases(repo, b, commit))
add_object_array(o, NULL, &merges);
}
reset_revision_walk();
@@ -1138,7 +1138,7 @@ static int find_first_merges(struct repository *repo,
contains_another = 0;
for (j = 0; j < merges.nr; j++) {
struct commit *m2 = (struct commit *) merges.objects[j].item;
- if (i != j && in_merge_bases(m2, m1)) {
+ if (i != j && repo_in_merge_bases(repo, m2, m1)) {
contains_another = 1;
break;
}
@@ -1174,6 +1174,8 @@ static int merge_submodule(struct merge_options *opt,
const struct object_id *base, const struct object_id *a,
const struct object_id *b)
{
+ struct repository subrepo;
+ int ret = 0;
struct commit *commit_base, *commit_a, *commit_b;
int parent_count;
struct object_array merges;
@@ -1197,27 +1199,36 @@ static int merge_submodule(struct merge_options *opt,
if (is_null_oid(b))
return 0;
+ /*
+ * NEEDSWORK: Remove this when all submodule object accesses are
+ * through explicitly specified repositores.
+ */
if (add_submodule_odb(path)) {
output(opt, 1, _("Failed to merge submodule %s (not checked out)"), path);
return 0;
}
- if (!(commit_base = lookup_commit_reference(opt->repo, base)) ||
- !(commit_a = lookup_commit_reference(opt->repo, a)) ||
- !(commit_b = lookup_commit_reference(opt->repo, b))) {
- output(opt, 1, _("Failed to merge submodule %s (commits not present)"), path);
+ if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) {
+ output(opt, 1, _("Failed to merge submodule %s (not checked out)"), path);
return 0;
}
+ if (!(commit_base = lookup_commit_reference(&subrepo, base)) ||
+ !(commit_a = lookup_commit_reference(&subrepo, a)) ||
+ !(commit_b = lookup_commit_reference(&subrepo, b))) {
+ output(opt, 1, _("Failed to merge submodule %s (commits not present)"), path);
+ goto cleanup;
+ }
+
/* check whether both changes are forward */
- if (!in_merge_bases(commit_base, commit_a) ||
- !in_merge_bases(commit_base, commit_b)) {
+ if (!repo_in_merge_bases(&subrepo, commit_base, commit_a) ||
+ !repo_in_merge_bases(&subrepo, commit_base, commit_b)) {
output(opt, 1, _("Failed to merge submodule %s (commits don't follow merge-base)"), path);
- return 0;
+ goto cleanup;
}
/* Case #1: a is contained in b or vice versa */
- if (in_merge_bases(commit_a, commit_b)) {
+ if (repo_in_merge_bases(&subrepo, commit_a, commit_b)) {
oidcpy(result, b);
if (show(opt, 3)) {
output(opt, 3, _("Fast-forwarding submodule %s to the following commit:"), path);
@@ -1227,9 +1238,10 @@ static int merge_submodule(struct merge_options *opt,
else
; /* no output */
- return 1;
+ ret = 1;
+ goto cleanup;
}
- if (in_merge_bases(commit_b, commit_a)) {
+ if (repo_in_merge_bases(&subrepo, commit_b, commit_a)) {
oidcpy(result, a);
if (show(opt, 3)) {
output(opt, 3, _("Fast-forwarding submodule %s to the following commit:"), path);
@@ -1239,7 +1251,8 @@ static int merge_submodule(struct merge_options *opt,
else
; /* no output */
- return 1;
+ ret = 1;
+ goto cleanup;
}
/*
@@ -1251,10 +1264,10 @@ static int merge_submodule(struct merge_options *opt,
/* Skip the search if makes no sense to the calling context. */
if (!search)
- return 0;
+ goto cleanup;
/* find commit which merges them */
- parent_count = find_first_merges(opt->repo, &merges, path,
+ parent_count = find_first_merges(&subrepo, &merges, path,
commit_a, commit_b);
switch (parent_count) {
case 0:
@@ -1281,7 +1294,9 @@ static int merge_submodule(struct merge_options *opt,
}
object_array_clear(&merges);
- return 0;
+cleanup:
+ repo_clear(&subrepo);
+ return ret;
}
static int merge_mode_and_contents(struct merge_options *opt,
@@ -3750,6 +3765,9 @@ int merge_recursive(struct merge_options *opt,
assert(opt->ancestor == NULL ||
!strcmp(opt->ancestor, "constructed merge base"));
+ prepare_repo_settings(opt->repo);
+ opt->repo->settings.command_requires_full_index = 1;
+
if (merge_start(opt, repo_get_commit_tree(opt->repo, h1)))
return -1;
clean = merge_recursive_internal(opt, h1, h2, merge_bases, result);
diff --git a/merge.c b/merge.c
index 6e736881d9..2382ff66d3 100644
--- a/merge.c
+++ b/merge.c
@@ -53,7 +53,6 @@ int checkout_fast_forward(struct repository *r,
struct unpack_trees_options opts;
struct tree_desc t[MAX_UNPACK_TREES];
int i, nr_trees = 0;
- struct dir_struct dir = DIR_INIT;
struct lock_file lock_file = LOCK_INIT;
refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL);
@@ -80,11 +79,7 @@ int checkout_fast_forward(struct repository *r,
}
memset(&opts, 0, sizeof(opts));
- if (overwrite_ignore) {
- dir.flags |= DIR_SHOW_IGNORED;
- setup_standard_excludes(&dir);
- opts.dir = &dir;
- }
+ opts.preserve_ignored = !overwrite_ignore;
opts.head_idx = 1;
opts.src_index = r->index;
@@ -101,7 +96,6 @@ int checkout_fast_forward(struct repository *r,
clear_unpack_trees_porcelain(&opts);
return -1;
}
- dir_clear(&dir);
clear_unpack_trees_porcelain(&opts);
if (write_locked_index(r->index, &lock_file, COMMIT_LOCK))
diff --git a/mergesort.c b/mergesort.c
index e5fdf2ee4a..6216835566 100644
--- a/mergesort.c
+++ b/mergesort.c
@@ -1,73 +1,84 @@
#include "cache.h"
#include "mergesort.h"
-struct mergesort_sublist {
- void *ptr;
- unsigned long len;
-};
-
-static void *get_nth_next(void *list, unsigned long n,
- void *(*get_next_fn)(const void *))
+/* Combine two sorted lists. Take from `list` on equality. */
+static void *llist_merge(void *list, void *other,
+ void *(*get_next_fn)(const void *),
+ void (*set_next_fn)(void *, void *),
+ int (*compare_fn)(const void *, const void *))
{
- while (n-- && list)
- list = get_next_fn(list);
- return list;
-}
+ void *result = list, *tail;
-static void *pop_item(struct mergesort_sublist *l,
- void *(*get_next_fn)(const void *))
-{
- void *p = l->ptr;
- l->ptr = get_next_fn(l->ptr);
- l->len = l->ptr ? (l->len - 1) : 0;
- return p;
+ if (compare_fn(list, other) > 0) {
+ result = other;
+ goto other;
+ }
+ for (;;) {
+ do {
+ tail = list;
+ list = get_next_fn(list);
+ if (!list) {
+ set_next_fn(tail, other);
+ return result;
+ }
+ } while (compare_fn(list, other) <= 0);
+ set_next_fn(tail, other);
+ other:
+ do {
+ tail = other;
+ other = get_next_fn(other);
+ if (!other) {
+ set_next_fn(tail, list);
+ return result;
+ }
+ } while (compare_fn(list, other) > 0);
+ set_next_fn(tail, list);
+ }
}
+/*
+ * Perform an iterative mergesort using an array of sublists.
+ *
+ * n is the number of items.
+ * ranks[i] is undefined if n & 2^i == 0, and assumed empty.
+ * ranks[i] contains a sublist of length 2^i otherwise.
+ *
+ * The number of bits in a void pointer limits the number of objects
+ * that can be created, and thus the number of array elements necessary
+ * to be able to sort any valid list.
+ *
+ * Adding an item to this array is like incrementing a binary number;
+ * positional values for set bits correspond to sublist lengths.
+ */
void *llist_mergesort(void *list,
void *(*get_next_fn)(const void *),
void (*set_next_fn)(void *, void *),
int (*compare_fn)(const void *, const void *))
{
- unsigned long l;
-
- if (!list)
- return NULL;
- for (l = 1; ; l *= 2) {
- void *curr;
- struct mergesort_sublist p, q;
+ void *ranks[bitsizeof(void *)];
+ size_t n = 0;
+ int i;
- p.ptr = list;
- q.ptr = get_nth_next(p.ptr, l, get_next_fn);
- if (!q.ptr)
- break;
- p.len = q.len = l;
+ while (list) {
+ void *next = get_next_fn(list);
+ if (next)
+ set_next_fn(list, NULL);
+ for (i = 0; n & (1 << i); i++)
+ list = llist_merge(ranks[i], list, get_next_fn,
+ set_next_fn, compare_fn);
+ n++;
+ ranks[i] = list;
+ list = next;
+ }
- if (compare_fn(p.ptr, q.ptr) > 0)
- list = curr = pop_item(&q, get_next_fn);
+ for (i = 0; n; i++, n >>= 1) {
+ if (!(n & 1))
+ continue;
+ if (list)
+ list = llist_merge(ranks[i], list, get_next_fn,
+ set_next_fn, compare_fn);
else
- list = curr = pop_item(&p, get_next_fn);
-
- while (p.ptr) {
- while (p.len || q.len) {
- void *prev = curr;
-
- if (!p.len)
- curr = pop_item(&q, get_next_fn);
- else if (!q.len)
- curr = pop_item(&p, get_next_fn);
- else if (compare_fn(p.ptr, q.ptr) > 0)
- curr = pop_item(&q, get_next_fn);
- else
- curr = pop_item(&p, get_next_fn);
- set_next_fn(prev, curr);
- }
- p.ptr = q.ptr;
- p.len = l;
- q.ptr = get_nth_next(p.ptr, l, get_next_fn);
- q.len = q.ptr ? l : 0;
-
- }
- set_next_fn(curr, NULL);
+ list = ranks[i];
}
return list;
}
diff --git a/midx.c b/midx.c
index 18a4f25aa3..7e06e85975 100644
--- a/midx.c
+++ b/midx.c
@@ -283,14 +283,18 @@ uint32_t nth_midxed_pack_int_id(struct multi_pack_index *m, uint32_t pos)
(off_t)pos * MIDX_CHUNK_OFFSET_WIDTH);
}
-static int nth_midxed_pack_entry(struct repository *r,
- struct multi_pack_index *m,
- struct pack_entry *e,
- uint32_t pos)
+int fill_midx_entry(struct repository * r,
+ const struct object_id *oid,
+ struct pack_entry *e,
+ struct multi_pack_index *m)
{
+ uint32_t pos;
uint32_t pack_int_id;
struct packed_git *p;
+ if (!bsearch_midx(oid, m, &pos))
+ return 0;
+
if (pos >= m->num_objects)
return 0;
@@ -310,15 +314,9 @@ static int nth_midxed_pack_entry(struct repository *r,
if (!is_pack_valid(p))
return 0;
- if (p->num_bad_objects) {
- uint32_t i;
- struct object_id oid;
- nth_midxed_object_oid(&oid, m, pos);
- for (i = 0; i < p->num_bad_objects; i++)
- if (hasheq(oid.hash,
- p->bad_object_sha1 + the_hash_algo->rawsz * i))
- return 0;
- }
+ if (oidset_size(&p->bad_objects) &&
+ oidset_contains(&p->bad_objects, oid))
+ return 0;
e->offset = nth_midxed_offset(m, pos);
e->p = p;
@@ -326,19 +324,6 @@ static int nth_midxed_pack_entry(struct repository *r,
return 1;
}
-int fill_midx_entry(struct repository * r,
- const struct object_id *oid,
- struct pack_entry *e,
- struct multi_pack_index *m)
-{
- uint32_t pos;
-
- if (!bsearch_midx(oid, m, &pos))
- return 0;
-
- return nth_midxed_pack_entry(r, m, e, pos);
-}
-
/* Match "foo.idx" against either "foo.pack" _or_ "foo.idx". */
static int cmp_idx_or_pack_name(const char *idx_or_pack_name,
const char *idx_name)
@@ -1069,9 +1054,13 @@ static int write_midx_bitmap(char *midx_name, unsigned char *midx_hash,
struct pack_idx_entry **index;
struct commit **commits = NULL;
uint32_t i, commits_nr;
+ uint16_t options = 0;
char *bitmap_name = xstrfmt("%s-%s.bitmap", midx_name, hash_to_hex(midx_hash));
int ret;
+ if (flags & MIDX_WRITE_BITMAP_HASH_CACHE)
+ options |= BITMAP_OPT_HASH_CACHE;
+
prepare_midx_packing_data(&pdata, ctx);
commits = find_commits_for_midx_bitmap(&commits_nr, refs_snapshot, ctx);
@@ -1110,7 +1099,7 @@ static int write_midx_bitmap(char *midx_name, unsigned char *midx_hash,
goto cleanup;
bitmap_writer_set_checksum(midx_hash);
- bitmap_writer_finish(index, pdata.nr_objects, bitmap_name, 0);
+ bitmap_writer_finish(index, pdata.nr_objects, bitmap_name, options);
cleanup:
free(index);
diff --git a/midx.h b/midx.h
index 11ff094a8c..6e32297fa3 100644
--- a/midx.h
+++ b/midx.h
@@ -45,6 +45,7 @@ struct multi_pack_index {
#define MIDX_PROGRESS (1 << 0)
#define MIDX_WRITE_REV_INDEX (1 << 1)
#define MIDX_WRITE_BITMAP (1 << 2)
+#define MIDX_WRITE_BITMAP_HASH_CACHE (1 << 3)
const unsigned char *get_midx_checksum(struct multi_pack_index *m);
char *get_midx_filename(const char *object_dir);
diff --git a/notes-merge.c b/notes-merge.c
index 46c1f7c7f1..b4a3a903e8 100644
--- a/notes-merge.c
+++ b/notes-merge.c
@@ -273,7 +273,7 @@ static void check_notes_merge_worktree(struct notes_merge_options *o)
*/
if (file_exists(git_path(NOTES_MERGE_WORKTREE)) &&
!is_empty_dir(git_path(NOTES_MERGE_WORKTREE))) {
- if (advice_resolve_conflict)
+ if (advice_enabled(ADVICE_RESOLVE_CONFLICT))
die(_("You have not concluded your previous "
"notes merge (%s exists).\nPlease, use "
"'git notes merge --commit' or 'git notes "
diff --git a/object-file.c b/object-file.c
index a4d720b4f5..112d9b4bad 100644
--- a/object-file.c
+++ b/object-file.c
@@ -32,6 +32,7 @@
#include "packfile.h"
#include "object-store.h"
#include "promisor-remote.h"
+#include "submodule.h"
/* The maximum size for an object header. */
#define MAX_HEADER_LEN 32
@@ -414,74 +415,6 @@ enum scld_error safe_create_leading_directories_const(const char *path)
return result;
}
-int raceproof_create_file(const char *path, create_file_fn fn, void *cb)
-{
- /*
- * The number of times we will try to remove empty directories
- * in the way of path. This is only 1 because if another
- * process is racily creating directories that conflict with
- * us, we don't want to fight against them.
- */
- int remove_directories_remaining = 1;
-
- /*
- * The number of times that we will try to create the
- * directories containing path. We are willing to attempt this
- * more than once, because another process could be trying to
- * clean up empty directories at the same time as we are
- * trying to create them.
- */
- int create_directories_remaining = 3;
-
- /* A scratch copy of path, filled lazily if we need it: */
- struct strbuf path_copy = STRBUF_INIT;
-
- int ret, save_errno;
-
- /* Sanity check: */
- assert(*path);
-
-retry_fn:
- ret = fn(path, cb);
- save_errno = errno;
- if (!ret)
- goto out;
-
- if (errno == EISDIR && remove_directories_remaining-- > 0) {
- /*
- * A directory is in the way. Maybe it is empty; try
- * to remove it:
- */
- if (!path_copy.len)
- strbuf_addstr(&path_copy, path);
-
- if (!remove_dir_recursively(&path_copy, REMOVE_DIR_EMPTY_ONLY))
- goto retry_fn;
- } else if (errno == ENOENT && create_directories_remaining-- > 0) {
- /*
- * Maybe the containing directory didn't exist, or
- * maybe it was just deleted by a process that is
- * racing with us to clean up empty directories. Try
- * to create it:
- */
- enum scld_error scld_result;
-
- if (!path_copy.len)
- strbuf_addstr(&path_copy, path);
-
- do {
- scld_result = safe_create_leading_directories(path_copy.buf);
- if (scld_result == SCLD_OK)
- goto retry_fn;
- } while (scld_result == SCLD_VANISHED && create_directories_remaining-- > 0);
- }
-
-out:
- strbuf_release(&path_copy);
- errno = save_errno;
- return ret;
-}
-
static void fill_loose_path(struct strbuf *buf, const struct object_id *oid)
{
int i;
@@ -1613,6 +1546,10 @@ static int do_oid_object_info_extended(struct repository *r,
break;
}
+ if (register_all_submodule_odb_as_alternates())
+ /* We added some alternates; retry */
+ continue;
+
/* Check if it is a missing object */
if (fetch_if_missing && repo_has_promisor_remote(r) &&
!already_retried &&
@@ -1637,7 +1574,7 @@ static int do_oid_object_info_extended(struct repository *r,
return 0;
rtype = packed_object_info(r, e.p, e.offset, oi);
if (rtype < 0) {
- mark_bad_packed_object(e.p, real->hash);
+ mark_bad_packed_object(e.p, real);
return do_oid_object_info_extended(r, real, oi, 0);
} else if (oi->whence == OI_PACKED) {
oi->u.packed.offset = e.offset;
@@ -1746,7 +1683,7 @@ void *read_object_file_extended(struct repository *r,
die(_("loose object %s (stored in %s) is corrupt"),
oid_to_hex(repl), path);
- if ((p = has_packed_and_bad(r, repl->hash)) != NULL)
+ if ((p = has_packed_and_bad(r, repl)) != NULL)
die(_("packed object %s (stored in %s) is corrupt"),
oid_to_hex(repl), p->pack_name);
obj_read_unlock();
diff --git a/object-name.c b/object-name.c
index 3263c19457..fdff4601b2 100644
--- a/object-name.c
+++ b/object-name.c
@@ -806,7 +806,7 @@ static int get_oid_basic(struct repository *r, const char *str, int len,
refs_found = repo_dwim_ref(r, str, len, &tmp_oid, &real_ref, 0);
if (refs_found > 0) {
warning(warn_msg, len, str);
- if (advice_object_name_warning)
+ if (advice_enabled(ADVICE_OBJECT_NAME_WARNING))
fprintf(stderr, "%s\n", _(object_name_msg));
}
free(real_ref);
diff --git a/object-store.h b/object-store.h
index 250aa5f33c..1e647a5be3 100644
--- a/object-store.h
+++ b/object-store.h
@@ -10,6 +10,7 @@
#include "khash.h"
#include "dir.h"
#include "oidtree.h"
+#include "oidset.h"
struct object_directory {
struct object_directory *next;
@@ -76,9 +77,8 @@ struct packed_git {
const void *index_data;
size_t index_size;
uint32_t num_objects;
- uint32_t num_bad_objects;
uint32_t crc_offset;
- unsigned char *bad_object_sha1;
+ struct oidset bad_objects;
int index_version;
time_t mtime;
int pack_fd;
@@ -371,7 +371,7 @@ struct object_info {
* Initializer for a "struct object_info" that wants no items. You may
* also memset() the memory to all-zeroes.
*/
-#define OBJECT_INFO_INIT {NULL}
+#define OBJECT_INFO_INIT { 0 }
/* Invoke lookup_replace_object() on the given hash */
#define OBJECT_INFO_LOOKUP_REPLACE 1
@@ -456,6 +456,12 @@ enum for_each_object_flags {
* Visit objects within a pack in packfile order rather than .idx order
*/
FOR_EACH_OBJECT_PACK_ORDER = (1<<2),
+
+ /* Only iterate over packs that are not marked as kept in-core. */
+ FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS = (1<<3),
+
+ /* Only iterate over packs that do not have .keep files. */
+ FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4),
};
/*
diff --git a/object.h b/object.h
index 3b38c9cc98..cb556ab775 100644
--- a/object.h
+++ b/object.h
@@ -55,7 +55,7 @@ struct object_array {
} *objects;
};
-#define OBJECT_ARRAY_INIT { 0, 0, NULL }
+#define OBJECT_ARRAY_INIT { 0 }
/*
* object flag allocation:
@@ -75,7 +75,6 @@ struct object_array {
* builtin/fsck.c: 0--3
* builtin/gc.c: 0
* builtin/index-pack.c: 2021
- * builtin/pack-objects.c: 20
* builtin/reflog.c: 10--12
* builtin/show-branch.c: 0-------------------------------------------26
* builtin/unpack-objects.c: 2021
diff --git a/oid-array.h b/oid-array.h
index 72bca78b7d..f60f9af674 100644
--- a/oid-array.h
+++ b/oid-array.h
@@ -56,7 +56,7 @@ struct oid_array {
int sorted;
};
-#define OID_ARRAY_INIT { NULL, 0, 0, 0 }
+#define OID_ARRAY_INIT { 0 }
/**
* Add an item to the set. The object ID will be placed at the end of the array
diff --git a/oidset.c b/oidset.c
index 5aac633c1f..b36a2bae86 100644
--- a/oidset.c
+++ b/oidset.c
@@ -36,11 +36,6 @@ void oidset_clear(struct oidset *set)
oidset_init(set, 0);
}
-int oidset_size(struct oidset *set)
-{
- return kh_size(&set->set);
-}
-
void oidset_parse_file(struct oidset *set, const char *path)
{
oidset_parse_file_carefully(set, path, NULL, NULL);
diff --git a/oidset.h b/oidset.h
index 01f6560283..ba4a5a2cd3 100644
--- a/oidset.h
+++ b/oidset.h
@@ -57,7 +57,10 @@ int oidset_remove(struct oidset *set, const struct object_id *oid);
/**
* Returns the number of oids in the set.
*/
-int oidset_size(struct oidset *set);
+static inline int oidset_size(const struct oidset *set)
+{
+ return kh_size(&set->set);
+}
/**
* Remove all entries from the oidset, freeing any resources associated with
diff --git a/pack-bitmap.c b/pack-bitmap.c
index 67be9be9a6..f47a0a7db4 100644
--- a/pack-bitmap.c
+++ b/pack-bitmap.c
@@ -1742,6 +1742,33 @@ int test_bitmap_commits(struct repository *r)
return 0;
}
+int test_bitmap_hashes(struct repository *r)
+{
+ struct bitmap_index *bitmap_git = prepare_bitmap_git(r);
+ struct object_id oid;
+ uint32_t i, index_pos;
+
+ if (!bitmap_git->hashes)
+ goto cleanup;
+
+ for (i = 0; i < bitmap_num_objects(bitmap_git); i++) {
+ if (bitmap_is_midx(bitmap_git))
+ index_pos = pack_pos_to_midx(bitmap_git->midx, i);
+ else
+ index_pos = pack_pos_to_index(bitmap_git->pack, i);
+
+ nth_bitmap_object_oid(bitmap_git, &oid, index_pos);
+
+ printf("%s %"PRIu32"\n",
+ oid_to_hex(&oid), get_be32(bitmap_git->hashes + index_pos));
+ }
+
+cleanup:
+ free_bitmap_index(bitmap_git);
+
+ return 0;
+}
+
int rebuild_bitmap(const uint32_t *reposition,
struct ewah_bitmap *source,
struct bitmap *dest)
@@ -1791,18 +1818,20 @@ uint32_t *create_bitmap_mapping(struct bitmap_index *bitmap_git,
for (i = 0; i < num_objects; ++i) {
struct object_id oid;
struct object_entry *oe;
+ uint32_t index_pos;
if (bitmap_is_midx(bitmap_git))
- nth_midxed_object_oid(&oid,
- bitmap_git->midx,
- pack_pos_to_midx(bitmap_git->midx, i));
+ index_pos = pack_pos_to_midx(bitmap_git->midx, i);
else
- nth_packed_object_id(&oid, bitmap_git->pack,
- pack_pos_to_index(bitmap_git->pack, i));
+ index_pos = pack_pos_to_index(bitmap_git->pack, i);
+ nth_bitmap_object_oid(bitmap_git, &oid, index_pos);
oe = packlist_find(mapping, &oid);
- if (oe)
+ if (oe) {
reposition[i] = oe_in_pack_pos(mapping, oe) + 1;
+ if (bitmap_git->hashes && !oe->hash)
+ oe->hash = get_be32(bitmap_git->hashes + index_pos);
+ }
}
return reposition;
diff --git a/pack-bitmap.h b/pack-bitmap.h
index 7d407c5a4c..19a63fa1ab 100644
--- a/pack-bitmap.h
+++ b/pack-bitmap.h
@@ -52,6 +52,7 @@ void traverse_bitmap_commit_list(struct bitmap_index *,
show_reachable_fn show_reachable);
void test_bitmap_walk(struct rev_info *revs);
int test_bitmap_commits(struct repository *r);
+int test_bitmap_hashes(struct repository *r);
struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs,
struct list_objects_filter_options *filter,
int filter_provided_objects);
diff --git a/pack-revindex.h b/pack-revindex.h
index 479b8f2f9c..74f4eae668 100644
--- a/pack-revindex.h
+++ b/pack-revindex.h
@@ -109,7 +109,7 @@ off_t pack_pos_to_offset(struct packed_git *p, uint32_t pos);
* If the reverse index has not yet been loaded, or the position is out of
* bounds, this function aborts.
*
- * This function runs in time O(log N) with the number of objects in the MIDX.
+ * This function runs in constant time.
*/
uint32_t pack_pos_to_midx(struct multi_pack_index *m, uint32_t pos);
@@ -120,7 +120,7 @@ uint32_t pack_pos_to_midx(struct multi_pack_index *m, uint32_t pos);
* If the reverse index has not yet been loaded, or the position is out of
* bounds, this function aborts.
*
- * This function runs in constant time.
+ * This function runs in time O(log N) with the number of objects in the MIDX.
*/
int midx_to_pack_pos(struct multi_pack_index *midx, uint32_t at, uint32_t *pos);
diff --git a/pack-write.c b/pack-write.c
index f1fc3ecafa..a5846f3a34 100644
--- a/pack-write.c
+++ b/pack-write.c
@@ -75,9 +75,7 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec
index_name = strbuf_detach(&tmp_file, NULL);
} else {
unlink(index_name);
- fd = open(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
- if (fd < 0)
- die_errno("unable to create '%s'", index_name);
+ fd = xopen(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
}
f = hashfd(fd, index_name);
}
@@ -224,6 +222,9 @@ const char *write_rev_file(const char *rev_name,
uint32_t i;
const char *ret;
+ if (!(flags & WRITE_REV) && !(flags & WRITE_REV_VERIFY))
+ return NULL;
+
ALLOC_ARRAY(pack_order, nr_objects);
for (i = 0; i < nr_objects; i++)
pack_order[i] = i;
@@ -256,9 +257,7 @@ const char *write_rev_file_order(const char *rev_name,
rev_name = strbuf_detach(&tmp_file, NULL);
} else {
unlink(rev_name);
- fd = open(rev_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
- if (fd < 0)
- die_errno("unable to create '%s'", rev_name);
+ fd = xopen(rev_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
}
f = hashfd(fd, rev_name);
} else if (flags & WRITE_REV_VERIFY) {
@@ -462,49 +461,48 @@ struct hashfile *create_tmp_packfile(char **pack_tmp_name)
return hashfd(fd, *pack_tmp_name);
}
-void finish_tmp_packfile(struct strbuf *name_buffer,
+static void rename_tmp_packfile(struct strbuf *name_prefix, const char *source,
+ const char *ext)
+{
+ size_t name_prefix_len = name_prefix->len;
+
+ strbuf_addstr(name_prefix, ext);
+ if (rename(source, name_prefix->buf))
+ die_errno("unable to rename temporary file to '%s'",
+ name_prefix->buf);
+ strbuf_setlen(name_prefix, name_prefix_len);
+}
+
+void rename_tmp_packfile_idx(struct strbuf *name_buffer,
+ char **idx_tmp_name)
+{
+ rename_tmp_packfile(name_buffer, *idx_tmp_name, "idx");
+}
+
+void stage_tmp_packfiles(struct strbuf *name_buffer,
const char *pack_tmp_name,
struct pack_idx_entry **written_list,
uint32_t nr_written,
struct pack_idx_option *pack_idx_opts,
- unsigned char hash[])
+ unsigned char hash[],
+ char **idx_tmp_name)
{
- const char *idx_tmp_name, *rev_tmp_name = NULL;
- int basename_len = name_buffer->len;
+ const char *rev_tmp_name = NULL;
if (adjust_shared_perm(pack_tmp_name))
die_errno("unable to make temporary pack file readable");
- idx_tmp_name = write_idx_file(NULL, written_list, nr_written,
- pack_idx_opts, hash);
- if (adjust_shared_perm(idx_tmp_name))
+ *idx_tmp_name = (char *)write_idx_file(NULL, written_list, nr_written,
+ pack_idx_opts, hash);
+ if (adjust_shared_perm(*idx_tmp_name))
die_errno("unable to make temporary index file readable");
rev_tmp_name = write_rev_file(NULL, written_list, nr_written, hash,
pack_idx_opts->flags);
- strbuf_addf(name_buffer, "%s.pack", hash_to_hex(hash));
-
- if (rename(pack_tmp_name, name_buffer->buf))
- die_errno("unable to rename temporary pack file");
-
- strbuf_setlen(name_buffer, basename_len);
-
- strbuf_addf(name_buffer, "%s.idx", hash_to_hex(hash));
- if (rename(idx_tmp_name, name_buffer->buf))
- die_errno("unable to rename temporary index file");
-
- strbuf_setlen(name_buffer, basename_len);
-
- if (rev_tmp_name) {
- strbuf_addf(name_buffer, "%s.rev", hash_to_hex(hash));
- if (rename(rev_tmp_name, name_buffer->buf))
- die_errno("unable to rename temporary reverse-index file");
- }
-
- strbuf_setlen(name_buffer, basename_len);
-
- free((void *)idx_tmp_name);
+ rename_tmp_packfile(name_buffer, pack_tmp_name, "pack");
+ if (rev_tmp_name)
+ rename_tmp_packfile(name_buffer, rev_tmp_name, "rev");
}
void write_promisor_file(const char *promisor_name, struct ref **sought, int nr_sought)
diff --git a/pack.h b/pack.h
index fa13954526..b22bfc4a18 100644
--- a/pack.h
+++ b/pack.h
@@ -110,6 +110,14 @@ int encode_in_pack_object_header(unsigned char *hdr, int hdr_len,
int read_pack_header(int fd, struct pack_header *);
struct hashfile *create_tmp_packfile(char **pack_tmp_name);
-void finish_tmp_packfile(struct strbuf *name_buffer, const char *pack_tmp_name, struct pack_idx_entry **written_list, uint32_t nr_written, struct pack_idx_option *pack_idx_opts, unsigned char sha1[]);
+void stage_tmp_packfiles(struct strbuf *name_buffer,
+ const char *pack_tmp_name,
+ struct pack_idx_entry **written_list,
+ uint32_t nr_written,
+ struct pack_idx_option *pack_idx_opts,
+ unsigned char hash[],
+ char **idx_tmp_name);
+void rename_tmp_packfile_idx(struct strbuf *basename,
+ char **idx_tmp_name);
#endif
diff --git a/packfile.c b/packfile.c
index 371f5488cf..89402cfc69 100644
--- a/packfile.c
+++ b/packfile.c
@@ -339,6 +339,7 @@ void close_pack(struct packed_git *p)
close_pack_fd(p);
close_pack_index(p);
close_pack_revindex(p);
+ oidset_clear(&p->bad_objects);
}
void close_object_store(struct raw_object_store *o)
@@ -1161,31 +1162,19 @@ int unpack_object_header(struct packed_git *p,
return type;
}
-void mark_bad_packed_object(struct packed_git *p, const unsigned char *sha1)
+void mark_bad_packed_object(struct packed_git *p, const struct object_id *oid)
{
- unsigned i;
- const unsigned hashsz = the_hash_algo->rawsz;
- for (i = 0; i < p->num_bad_objects; i++)
- if (hasheq(sha1, p->bad_object_sha1 + hashsz * i))
- return;
- p->bad_object_sha1 = xrealloc(p->bad_object_sha1,
- st_mult(GIT_MAX_RAWSZ,
- st_add(p->num_bad_objects, 1)));
- hashcpy(p->bad_object_sha1 + hashsz * p->num_bad_objects, sha1);
- p->num_bad_objects++;
+ oidset_insert(&p->bad_objects, oid);
}
const struct packed_git *has_packed_and_bad(struct repository *r,
- const unsigned char *sha1)
+ const struct object_id *oid)
{
struct packed_git *p;
- unsigned i;
for (p = r->objects->packed_git; p; p = p->next)
- for (i = 0; i < p->num_bad_objects; i++)
- if (hasheq(sha1,
- p->bad_object_sha1 + the_hash_algo->rawsz * i))
- return p;
+ if (oidset_contains(&p->bad_objects, oid))
+ return p;
return NULL;
}
@@ -1272,7 +1261,7 @@ static int retry_bad_packed_offset(struct repository *r,
if (offset_to_pack_pos(p, obj_offset, &pos) < 0)
return OBJ_BAD;
nth_packed_object_id(&oid, p, pack_pos_to_index(p, pos));
- mark_bad_packed_object(p, oid.hash);
+ mark_bad_packed_object(p, &oid);
type = oid_object_info(r, &oid, NULL);
if (type <= OBJ_NONE)
return OBJ_BAD;
@@ -1722,7 +1711,7 @@ void *unpack_entry(struct repository *r, struct packed_git *p, off_t obj_offset,
nth_packed_object_id(&oid, p, index_pos);
error("bad packed object CRC for %s",
oid_to_hex(&oid));
- mark_bad_packed_object(p, oid.hash);
+ mark_bad_packed_object(p, &oid);
data = NULL;
goto out;
}
@@ -1811,7 +1800,7 @@ void *unpack_entry(struct repository *r, struct packed_git *p, off_t obj_offset,
" at offset %"PRIuMAX" from %s",
oid_to_hex(&base_oid), (uintmax_t)obj_offset,
p->pack_name);
- mark_bad_packed_object(p, base_oid.hash);
+ mark_bad_packed_object(p, &base_oid);
base = read_object(r, &base_oid, &type, &base_size);
external_base = base;
}
@@ -2016,13 +2005,9 @@ static int fill_pack_entry(const struct object_id *oid,
{
off_t offset;
- if (p->num_bad_objects) {
- unsigned i;
- for (i = 0; i < p->num_bad_objects; i++)
- if (hasheq(oid->hash,
- p->bad_object_sha1 + the_hash_algo->rawsz * i))
- return 0;
- }
+ if (oidset_size(&p->bad_objects) &&
+ oidset_contains(&p->bad_objects, oid))
+ return 0;
offset = find_pack_entry_one(oid->hash, p);
if (!offset)
@@ -2205,6 +2190,12 @@ int for_each_packed_object(each_packed_object_fn cb, void *data,
if ((flags & FOR_EACH_OBJECT_PROMISOR_ONLY) &&
!p->pack_promisor)
continue;
+ if ((flags & FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&
+ p->pack_keep_in_core)
+ continue;
+ if ((flags & FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&
+ p->pack_keep)
+ continue;
if (open_pack_index(p)) {
pack_errors = 1;
continue;
diff --git a/packfile.h b/packfile.h
index 3ae117a8ae..186146779d 100644
--- a/packfile.h
+++ b/packfile.h
@@ -159,8 +159,8 @@ int packed_object_info(struct repository *r,
struct packed_git *pack,
off_t offset, struct object_info *);
-void mark_bad_packed_object(struct packed_git *p, const unsigned char *sha1);
-const struct packed_git *has_packed_and_bad(struct repository *r, const unsigned char *sha1);
+void mark_bad_packed_object(struct packed_git *, const struct object_id *);
+const struct packed_git *has_packed_and_bad(struct repository *, const struct object_id *);
#define ON_DISK_KEEP_PACKS 1
#define IN_CORE_KEEP_PACKS 2
diff --git a/parse-options.c b/parse-options.c
index 2abff136a1..6e0535bdaa 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -310,19 +310,6 @@ static enum parse_opt_result parse_long_opt(
again:
if (!skip_prefix(arg, long_name, &rest))
rest = NULL;
- if (options->type == OPTION_ARGUMENT) {
- if (!rest)
- continue;
- if (*rest == '=')
- return error(_("%s takes no value"),
- optname(options, flags));
- if (*rest)
- continue;
- if (options->value)
- *(int *)options->value = options->defval;
- p->out[p->cpidx++] = arg - 2;
- return PARSE_OPT_DONE;
- }
if (!rest) {
/* abbreviated? */
if (!(p->flags & PARSE_OPT_KEEP_UNKNOWN) &&
@@ -917,25 +904,77 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,
FILE *outfile = err ? stderr : stdout;
int need_newline;
+ const char *usage_prefix = _("usage: %s");
+ /*
+ * The translation could be anything, but we can count on
+ * msgfmt(1)'s --check option to have asserted that "%s" is in
+ * the translation. So compute the length of the "usage: "
+ * part. We are assuming that the translator wasn't overly
+ * clever and used e.g. "%1$s" instead of "%s", there's only
+ * one "%s" in "usage_prefix" above, so there's no reason to
+ * do so even with a RTL language.
+ */
+ size_t usage_len = strlen(usage_prefix) - strlen("%s");
+ /*
+ * TRANSLATORS: the colon here should align with the
+ * one in "usage: %s" translation.
+ */
+ const char *or_prefix = _(" or: %s");
+ /*
+ * TRANSLATORS: You should only need to translate this format
+ * string if your language is a RTL language (e.g. Arabic,
+ * Hebrew etc.), not if it's a LTR language (e.g. German,
+ * Russian, Chinese etc.).
+ *
+ * When a translated usage string has an embedded "\n" it's
+ * because options have wrapped to the next line. The line
+ * after the "\n" will then be padded to align with the
+ * command name, such as N_("git cmd [opt]\n<8
+ * spaces>[opt2]"), where the 8 spaces are the same length as
+ * "git cmd ".
+ *
+ * This format string prints out that already-translated
+ * line. The "%*s" is whitespace padding to account for the
+ * padding at the start of the line that we add in this
+ * function. The "%s" is a line in the (hopefully already
+ * translated) N_() usage string, which contained embedded
+ * newlines before we split it up.
+ */
+ const char *usage_continued = _("%*s%s");
+ const char *prefix = usage_prefix;
+ int saw_empty_line = 0;
+
if (!usagestr)
return PARSE_OPT_HELP;
if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL)
fprintf(outfile, "cat <<\\EOF\n");
- fprintf_ln(outfile, _("usage: %s"), _(*usagestr++));
- while (*usagestr && **usagestr)
- /*
- * TRANSLATORS: the colon here should align with the
- * one in "usage: %s" translation.
- */
- fprintf_ln(outfile, _(" or: %s"), _(*usagestr++));
while (*usagestr) {
- if (**usagestr)
- fprintf_ln(outfile, _(" %s"), _(*usagestr));
- else
- fputc('\n', outfile);
- usagestr++;
+ const char *str = _(*usagestr++);
+ struct string_list list = STRING_LIST_INIT_DUP;
+ unsigned int j;
+
+ if (!saw_empty_line && !*str)
+ saw_empty_line = 1;
+
+ string_list_split(&list, str, '\n', -1);
+ for (j = 0; j < list.nr; j++) {
+ const char *line = list.items[j].string;
+
+ if (saw_empty_line && *line)
+ fprintf_ln(outfile, _(" %s"), line);
+ else if (saw_empty_line)
+ fputc('\n', outfile);
+ else if (!j)
+ fprintf_ln(outfile, prefix, line);
+ else
+ fprintf_ln(outfile, usage_continued,
+ (int)usage_len, "", line);
+ }
+ string_list_clear(&list, 0);
+
+ prefix = or_prefix;
}
need_newline = 1;
diff --git a/parse-options.h b/parse-options.h
index a845a9d952..13405472ee 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -8,7 +8,6 @@
enum parse_opt_type {
/* special types */
OPTION_END,
- OPTION_ARGUMENT,
OPTION_GROUP,
OPTION_NUMBER,
OPTION_ALIAS,
@@ -155,8 +154,6 @@ struct option {
#define OPT_INTEGER_F(s, l, v, h, f) { OPTION_INTEGER, (s), (l), (v), N_("n"), (h), (f) }
#define OPT_END() { OPTION_END }
-#define OPT_ARGUMENT(l, v, h) { OPTION_ARGUMENT, 0, (l), (v), NULL, \
- (h), PARSE_OPT_NOARG, NULL, 1 }
#define OPT_GROUP(h) { OPTION_GROUP, 0, NULL, NULL, NULL, (h) }
#define OPT_BIT(s, l, v, h, b) OPT_BIT_F(s, l, v, h, b, 0)
#define OPT_BITOP(s, l, v, h, set, clear) { OPTION_BITOP, (s), (l), (v), NULL, (h), \
@@ -169,8 +166,10 @@ struct option {
#define OPT_BOOL(s, l, v, h) OPT_BOOL_F(s, l, v, h, 0)
#define OPT_HIDDEN_BOOL(s, l, v, h) { OPTION_SET_INT, (s), (l), (v), NULL, \
(h), PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1}
-#define OPT_CMDMODE(s, l, v, h, i) { OPTION_SET_INT, (s), (l), (v), NULL, \
- (h), PARSE_OPT_CMDMODE|PARSE_OPT_NOARG|PARSE_OPT_NONEG, NULL, (i) }
+#define OPT_CMDMODE_F(s, l, v, h, i, f) { OPTION_SET_INT, (s), (l), (v), NULL, \
+ (h), PARSE_OPT_CMDMODE|PARSE_OPT_NOARG|PARSE_OPT_NONEG | (f), NULL, (i) }
+#define OPT_CMDMODE(s, l, v, h, i) OPT_CMDMODE_F(s, l, v, h, i, 0)
+
#define OPT_INTEGER(s, l, v, h) OPT_INTEGER_F(s, l, v, h, 0)
#define OPT_MAGNITUDE(s, l, v, h) { OPTION_MAGNITUDE, (s), (l), (v), \
N_("n"), (h), PARSE_OPT_NONEG }
diff --git a/path.c b/path.c
index 7bccd830e9..2c895471d9 100644
--- a/path.c
+++ b/path.c
@@ -12,6 +12,7 @@
#include "packfile.h"
#include "object-store.h"
#include "lockfile.h"
+#include "exec-cmd.h"
static int get_st_mode_bits(const char *path, int *mode)
{
@@ -719,19 +720,25 @@ static struct passwd *getpw_str(const char *username, size_t len)
}
/*
- * Return a string with ~ and ~user expanded via getpw*. If buf != NULL,
- * then it is a newly allocated string. Returns NULL on getpw failure or
- * if path is NULL.
+ * Return a string with ~ and ~user expanded via getpw*. Returns NULL on getpw
+ * failure or if path is NULL.
*
- * If real_home is true, strbuf_realpath($HOME) is used in the expansion.
+ * If real_home is true, strbuf_realpath($HOME) is used in the `~/` expansion.
+ *
+ * If the path starts with `%(prefix)/`, the remainder is interpreted as
+ * relative to where Git is installed, and expanded to the absolute path.
*/
-char *expand_user_path(const char *path, int real_home)
+char *interpolate_path(const char *path, int real_home)
{
struct strbuf user_path = STRBUF_INIT;
const char *to_copy = path;
if (path == NULL)
goto return_null;
+
+ if (skip_prefix(path, "%(prefix)/", &path))
+ return system_path(path);
+
if (path[0] == '~') {
const char *first_slash = strchrnul(path, '/');
const char *username = path + 1;
@@ -812,7 +819,7 @@ const char *enter_repo(const char *path, int strict)
strbuf_add(&validated_path, path, len);
if (used_path.buf[0] == '~') {
- char *newpath = expand_user_path(used_path.buf, 0);
+ char *newpath = interpolate_path(used_path.buf, 0);
if (!newpath)
return NULL;
strbuf_attach(&used_path, newpath, strlen(newpath),
@@ -1503,21 +1510,28 @@ int looks_like_command_line_option(const char *str)
return str && str[0] == '-';
}
-char *xdg_config_home(const char *filename)
+char *xdg_config_home_for(const char *subdir, const char *filename)
{
const char *home, *config_home;
+ assert(subdir);
assert(filename);
config_home = getenv("XDG_CONFIG_HOME");
if (config_home && *config_home)
- return mkpathdup("%s/git/%s", config_home, filename);
+ return mkpathdup("%s/%s/%s", config_home, subdir, filename);
home = getenv("HOME");
if (home)
- return mkpathdup("%s/.config/git/%s", home, filename);
+ return mkpathdup("%s/.config/%s/%s", home, subdir, filename);
+
return NULL;
}
+char *xdg_config_home(const char *filename)
+{
+ return xdg_config_home_for("git", filename);
+}
+
char *xdg_cache_home(const char *filename)
{
const char *home, *cache_home;
diff --git a/path.h b/path.h
index 251c78d980..b68691a86b 100644
--- a/path.h
+++ b/path.h
@@ -181,10 +181,7 @@ struct path_cache {
const char *shallow;
};
-#define PATH_CACHE_INIT \
- { \
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL \
- }
+#define PATH_CACHE_INIT { 0 }
const char *git_path_squash_msg(struct repository *r);
const char *git_path_merge_msg(struct repository *r);
diff --git a/pathspec.c b/pathspec.c
index 08f8d3eedc..ddeeba7911 100644
--- a/pathspec.c
+++ b/pathspec.c
@@ -37,11 +37,10 @@ void add_pathspec_matches_against_index(const struct pathspec *pathspec,
num_unmatched++;
if (!num_unmatched)
return;
- /* TODO: audit for interaction with sparse-index. */
- ensure_full_index(istate);
for (i = 0; i < istate->cache_nr; i++) {
const struct cache_entry *ce = istate->cache[i];
- if (sw_action == PS_IGNORE_SKIP_WORKTREE && ce_skip_worktree(ce))
+ if (sw_action == PS_IGNORE_SKIP_WORKTREE &&
+ (ce_skip_worktree(ce) || !path_in_sparse_checkout(ce->name, istate)))
continue;
ce_path_match(istate, ce, pathspec, seen);
}
@@ -72,7 +71,7 @@ char *find_pathspecs_matching_skip_worktree(const struct pathspec *pathspec)
for (i = 0; i < istate->cache_nr; i++) {
struct cache_entry *ce = istate->cache[i];
- if (ce_skip_worktree(ce))
+ if (ce_skip_worktree(ce) || !path_in_sparse_checkout(ce->name, istate))
ce_path_match(istate, ce, pathspec, seen);
}
diff --git a/pkt-line.c b/pkt-line.c
index 9f63eae2e6..de4a94b437 100644
--- a/pkt-line.c
+++ b/pkt-line.c
@@ -243,6 +243,43 @@ void packet_write(int fd_out, const char *buf, size_t size)
die("%s", err.buf);
}
+void packet_fwrite(FILE *f, const char *buf, size_t size)
+{
+ size_t packet_size;
+ char header[4];
+
+ if (size > LARGE_PACKET_DATA_MAX)
+ die(_("packet write failed - data exceeds max packet size"));
+
+ packet_trace(buf, size, 1);
+ packet_size = size + 4;
+
+ set_packet_header(header, packet_size);
+ fwrite_or_die(f, header, 4);
+ fwrite_or_die(f, buf, size);
+}
+
+void packet_fwrite_fmt(FILE *fh, const char *fmt, ...)
+{
+ static struct strbuf buf = STRBUF_INIT;
+ va_list args;
+
+ strbuf_reset(&buf);
+
+ va_start(args, fmt);
+ format_packet(&buf, "", fmt, args);
+ va_end(args);
+
+ fwrite_or_die(fh, buf.buf, buf.len);
+}
+
+void packet_fflush(FILE *f)
+{
+ packet_trace("0000", 4, 1);
+ fwrite_or_die(f, "0000", 4);
+ fflush_or_die(f);
+}
+
void packet_buf_write(struct strbuf *buf, const char *fmt, ...)
{
va_list args;
diff --git a/pkt-line.h b/pkt-line.h
index 5af5f45687..82b95e4bdd 100644
--- a/pkt-line.h
+++ b/pkt-line.h
@@ -36,6 +36,17 @@ int write_packetized_from_fd_no_flush(int fd_in, int fd_out);
int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out);
/*
+ * Stdio versions of packet_write functions. When mixing these with fd
+ * based functions, take care to call fflush(3) before doing fd writes or
+ * closing the fd.
+ */
+void packet_fwrite(FILE *f, const char *buf, size_t size);
+void packet_fwrite_fmt(FILE *f, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
+
+/* packet_fflush writes a flush packet and flushes the stdio buffer of f */
+void packet_fflush(FILE *f);
+
+/*
* Read a packetized line into the buffer, which must be at least size bytes
* long. The return value specifies the number of bytes read into the buffer.
*
diff --git a/pretty.c b/pretty.c
index 9631529c10..73b5ead509 100644
--- a/pretty.c
+++ b/pretty.c
@@ -671,7 +671,11 @@ const char *repo_logmsg_reencode(struct repository *r,
* If the re-encoding failed, out might be NULL here; in that
* case we just return the commit message verbatim.
*/
- return out ? out : msg;
+ if (!out) {
+ warning("unable to reencode commit to '%s'", output_encoding);
+ return msg;
+ }
+ return out;
}
static int mailmap_name(const char **email, size_t *email_len,
diff --git a/protocol-caps.c b/protocol-caps.c
index 13a9e63a04..bbde91810a 100644
--- a/protocol-caps.c
+++ b/protocol-caps.c
@@ -69,13 +69,13 @@ static void send_info(struct repository *r, struct packet_writer *writer,
}
}
- packet_writer_write(writer, "%s",
- strbuf_detach(&send_buffer, NULL));
+ packet_writer_write(writer, "%s", send_buffer.buf);
+ strbuf_reset(&send_buffer);
}
+ strbuf_release(&send_buffer);
}
-int cap_object_info(struct repository *r, struct strvec *keys,
- struct packet_reader *request)
+int cap_object_info(struct repository *r, struct packet_reader *request)
{
struct requested_info info;
struct packet_writer writer;
diff --git a/protocol-caps.h b/protocol-caps.h
index 0a9f49df11..15c4550360 100644
--- a/protocol-caps.h
+++ b/protocol-caps.h
@@ -2,9 +2,7 @@
#define PROTOCOL_CAPS_H
struct repository;
-struct strvec;
struct packet_reader;
-int cap_object_info(struct repository *r, struct strvec *keys,
- struct packet_reader *request);
+int cap_object_info(struct repository *r, struct packet_reader *request);
#endif /* PROTOCOL_CAPS_H */
diff --git a/protocol.c b/protocol.c
index 052d7edbb9..c53f7df5be 100644
--- a/protocol.c
+++ b/protocol.c
@@ -73,6 +73,8 @@ enum protocol_version determine_protocol_version_server(void)
string_list_clear(&list, 0);
}
+ trace2_data_intmax("transfer", NULL, "negotiated-version", version);
+
return version;
}
diff --git a/quote.c b/quote.c
index 8a3a5e39eb..26719d21d1 100644
--- a/quote.c
+++ b/quote.c
@@ -471,6 +471,23 @@ void perl_quote_buf(struct strbuf *sb, const char *src)
strbuf_addch(sb, sq);
}
+void perl_quote_buf_with_len(struct strbuf *sb, const char *src, size_t len)
+{
+ const char sq = '\'';
+ const char bq = '\\';
+ const char *c = src;
+ const char *end = src + len;
+
+ strbuf_addch(sb, sq);
+ while (c != end) {
+ if (*c == sq || *c == bq)
+ strbuf_addch(sb, bq);
+ strbuf_addch(sb, *c);
+ c++;
+ }
+ strbuf_addch(sb, sq);
+}
+
void python_quote_buf(struct strbuf *sb, const char *src)
{
const char sq = '\'';
diff --git a/quote.h b/quote.h
index 049d8dd0b3..87ff458b06 100644
--- a/quote.h
+++ b/quote.h
@@ -95,6 +95,7 @@ char *quote_path(const char *in, const char *prefix, struct strbuf *out, unsigne
/* quoting as a string literal for other languages */
void perl_quote_buf(struct strbuf *sb, const char *src);
+void perl_quote_buf_with_len(struct strbuf *sb, const char *src, size_t len);
void python_quote_buf(struct strbuf *sb, const char *src);
void tcl_quote_buf(struct strbuf *sb, const char *src);
void basic_regex_quote_buf(struct strbuf *sb, const char *src);
diff --git a/range-diff.c b/range-diff.c
index e9479794b4..cac89a2f4f 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -26,17 +26,6 @@ struct patch_util {
struct object_id oid;
};
-static size_t find_end_of_line(char *buffer, unsigned long size)
-{
- char *eol = memchr(buffer, '\n', size);
-
- if (!eol)
- return size;
-
- *eol = '\0';
- return eol + 1 - buffer;
-}
-
/*
* Reads the patches into a string list, with the `util` field being populated
* as struct object_id (will need to be free()d).
@@ -49,7 +38,7 @@ static int read_patches(const char *range, struct string_list *list,
struct patch_util *util = NULL;
int in_header = 1;
char *line, *current_filename = NULL;
- int offset, len;
+ ssize_t len;
size_t size;
strvec_pushl(&cp.args, "log", "--no-color", "-p", "--no-merges",
@@ -86,11 +75,18 @@ static int read_patches(const char *range, struct string_list *list,
line = contents.buf;
size = contents.len;
- for (offset = 0; size > 0; offset += len, size -= len, line += len) {
+ for (; size > 0; size -= len, line += len) {
const char *p;
+ char *eol;
+
+ eol = memchr(line, '\n', size);
+ if (eol) {
+ *eol = '\0';
+ len = eol + 1 - line;
+ } else {
+ len = size;
+ }
- len = find_end_of_line(line, size);
- line[len - 1] = '\0';
if (skip_prefix(line, "commit ", &p)) {
if (util) {
string_list_append(list, buf.buf)->util = util;
@@ -132,7 +128,8 @@ static int read_patches(const char *range, struct string_list *list,
strbuf_addch(&buf, '\n');
if (!util->diff_offset)
util->diff_offset = buf.len;
- line[len - 1] = '\n';
+ if (eol)
+ *eol = '\n';
orig_len = len;
len = parse_git_diff_header(&root, &linenr, 0, line,
len, size, &patch);
@@ -485,6 +482,7 @@ static void output(struct string_list *a, struct string_list *b,
else
diff_setup(&opts);
+ opts.no_free = 1;
if (!opts.output_format)
opts.output_format = DIFF_FORMAT_PATCH;
opts.flags.suppress_diff_headers = 1;
@@ -545,6 +543,8 @@ static void output(struct string_list *a, struct string_list *b,
strbuf_release(&buf);
strbuf_release(&dashes);
strbuf_release(&indent);
+ opts.no_free = 0;
+ diff_free(&opts);
}
int show_range_diff(const char *range1, const char *range2,
diff --git a/read-cache.c b/read-cache.c
index 9048ef9e90..a78b88a41b 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -1944,13 +1944,22 @@ static void tweak_untracked_cache(struct index_state *istate)
prepare_repo_settings(r);
- if (r->settings.core_untracked_cache == UNTRACKED_CACHE_REMOVE) {
+ switch (r->settings.core_untracked_cache) {
+ case UNTRACKED_CACHE_REMOVE:
remove_untracked_cache(istate);
- return;
- }
-
- if (r->settings.core_untracked_cache == UNTRACKED_CACHE_WRITE)
+ break;
+ case UNTRACKED_CACHE_WRITE:
add_untracked_cache(istate);
+ break;
+ case UNTRACKED_CACHE_KEEP:
+ /*
+ * Either an explicit "core.untrackedCache=keep", the
+ * default if "core.untrackedCache" isn't configured,
+ * or a fallback on an unknown "core.untrackedCache"
+ * value.
+ */
+ break;
+ }
}
static void tweak_split_index(struct index_state *istate)
@@ -2391,9 +2400,21 @@ int read_index_from(struct index_state *istate, const char *path,
base_path = xstrfmt("%s/sharedindex.%s", gitdir, base_oid_hex);
trace2_region_enter_printf("index", "shared/do_read_index",
the_repository, "%s", base_path);
- ret = do_read_index(split_index->base, base_path, 1);
+ ret = do_read_index(split_index->base, base_path, 0);
trace2_region_leave_printf("index", "shared/do_read_index",
the_repository, "%s", base_path);
+ if (!ret) {
+ char *path_copy = xstrdup(path);
+ const char *base_path2 = xstrfmt("%s/sharedindex.%s",
+ dirname(path_copy),
+ base_oid_hex);
+ free(path_copy);
+ trace2_region_enter_printf("index", "shared/do_read_index",
+ the_repository, "%s", base_path2);
+ ret = do_read_index(split_index->base, base_path2, 1);
+ trace2_region_leave_printf("index", "shared/do_read_index",
+ the_repository, "%s", base_path2);
+ }
if (!oideq(&split_index->base_oid, &split_index->base->oid))
die(_("broken index, expect %s in %s, got %s"),
base_oid_hex, base_path,
@@ -2812,11 +2833,8 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
}
}
- if (!istate->version) {
+ if (!istate->version)
istate->version = get_index_format_default(the_repository);
- if (git_env_bool("GIT_TEST_SPLIT_INDEX", 0))
- init_split_index(istate);
- }
/* demote version 3 to version 2 when the latter suffices */
if (istate->version == 3 || istate->version == 2)
@@ -3069,7 +3087,7 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l
int ret;
int was_full = !istate->sparse_index;
- ret = convert_to_sparse(istate);
+ ret = convert_to_sparse(istate, 0);
if (ret) {
warning(_("failed to convert to a sparse-index"));
@@ -3182,7 +3200,7 @@ static int write_shared_index(struct index_state *istate,
int ret, was_full = !istate->sparse_index;
move_cache_to_base_index(istate);
- convert_to_sparse(istate);
+ convert_to_sparse(istate, 0);
trace2_region_enter_printf("index", "shared/do_write_index",
the_repository, "%s", get_tempfile_path(*temp));
@@ -3243,7 +3261,7 @@ static int too_many_not_shared_entries(struct index_state *istate)
int write_locked_index(struct index_state *istate, struct lock_file *lock,
unsigned flags)
{
- int new_shared_index, ret;
+ int new_shared_index, ret, test_split_index_env;
struct split_index *si = istate->split_index;
if (git_env_bool("GIT_TEST_CHECK_CACHE_TREE", 0))
@@ -3258,7 +3276,10 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock,
if (istate->fsmonitor_last_update)
fill_fsmonitor_bitmap(istate);
- if (!si || alternate_index_output ||
+ test_split_index_env = git_env_bool("GIT_TEST_SPLIT_INDEX", 0);
+
+ if ((!si && !test_split_index_env) ||
+ alternate_index_output ||
(istate->cache_changed & ~EXTMASK)) {
if (si)
oidclr(&si->base_oid);
@@ -3266,10 +3287,15 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock,
goto out;
}
- if (git_env_bool("GIT_TEST_SPLIT_INDEX", 0)) {
- int v = si->base_oid.hash[0];
- if ((v & 15) < 6)
+ if (test_split_index_env) {
+ if (!si) {
+ si = init_split_index(istate);
istate->cache_changed |= SPLIT_INDEX_ORDERED;
+ } else {
+ int v = si->base_oid.hash[0];
+ if ((v & 15) < 6)
+ istate->cache_changed |= SPLIT_INDEX_ORDERED;
+ }
}
if (too_many_not_shared_entries(istate))
istate->cache_changed |= SPLIT_INDEX_ORDERED;
diff --git a/rebase-interactive.c b/rebase-interactive.c
index b6cbd16a17..87649d0c01 100644
--- a/rebase-interactive.c
+++ b/rebase-interactive.c
@@ -226,32 +226,3 @@ int todo_list_check_against_backup(struct repository *r, struct todo_list *todo_
todo_list_release(&backup);
return res;
}
-
-int check_todo_list_from_file(struct repository *r)
-{
- struct todo_list old_todo = TODO_LIST_INIT, new_todo = TODO_LIST_INIT;
- int res = 0;
-
- if (strbuf_read_file(&new_todo.buf, rebase_path_todo(), 0) < 0) {
- res = error(_("could not read '%s'."), rebase_path_todo());
- goto out;
- }
-
- if (strbuf_read_file(&old_todo.buf, rebase_path_todo_backup(), 0) < 0) {
- res = error(_("could not read '%s'."), rebase_path_todo_backup());
- goto out;
- }
-
- res = todo_list_parse_insn_buffer(r, old_todo.buf.buf, &old_todo);
- if (!res)
- res = todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo);
- if (res)
- fprintf(stderr, _(edit_todo_list_advice));
- if (!res)
- res = todo_list_check(&old_todo, &new_todo);
-out:
- todo_list_release(&old_todo);
- todo_list_release(&new_todo);
-
- return res;
-}
diff --git a/rebase-interactive.h b/rebase-interactive.h
index dc2cf0ee12..7239c60f79 100644
--- a/rebase-interactive.h
+++ b/rebase-interactive.h
@@ -16,6 +16,4 @@ int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo);
int todo_list_check_against_backup(struct repository *r,
struct todo_list *todo_list);
-int check_todo_list_from_file(struct repository *r);
-
#endif
diff --git a/rebase.c b/rebase.c
index f8137d859b..6775cddb28 100644
--- a/rebase.c
+++ b/rebase.c
@@ -1,5 +1,6 @@
#include "rebase.h"
#include "config.h"
+#include "gettext.h"
/*
* Parses textual value for pull.rebase, branch.<name>.rebase, etc.
@@ -20,12 +21,12 @@ enum rebase_type rebase_parse_value(const char *value)
return REBASE_FALSE;
else if (v > 0)
return REBASE_TRUE;
- else if (!strcmp(value, "preserve") || !strcmp(value, "p"))
- return REBASE_PRESERVE;
else if (!strcmp(value, "merges") || !strcmp(value, "m"))
return REBASE_MERGES;
else if (!strcmp(value, "interactive") || !strcmp(value, "i"))
return REBASE_INTERACTIVE;
+ else if (!strcmp(value, "preserve") || !strcmp(value, "p"))
+ error(_("%s: 'preserve' superseded by 'merges'"), value);
/*
* Please update _git_config() in git-completion.bash when you
* add new rebase modes.
diff --git a/rebase.h b/rebase.h
index cc723d4748..203b437282 100644
--- a/rebase.h
+++ b/rebase.h
@@ -5,7 +5,6 @@ enum rebase_type {
REBASE_INVALID = -1,
REBASE_FALSE = 0,
REBASE_TRUE,
- REBASE_PRESERVE,
REBASE_MERGES,
REBASE_INTERACTIVE
};
diff --git a/ref-filter.c b/ref-filter.c
index 0cfef7b719..add429be79 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -144,6 +144,7 @@ enum atom_type {
ATOM_BODY,
ATOM_TRAILERS,
ATOM_CONTENTS,
+ ATOM_RAW,
ATOM_UPSTREAM,
ATOM_PUSH,
ATOM_SYMREF,
@@ -156,6 +157,7 @@ enum atom_type {
ATOM_IF,
ATOM_THEN,
ATOM_ELSE,
+ ATOM_REST,
};
/*
@@ -190,6 +192,9 @@ static struct used_atom {
unsigned int nlines;
} contents;
struct {
+ enum { RAW_BARE, RAW_LENGTH } option;
+ } raw_data;
+ struct {
cmp_status cmp_status;
const char *str;
} if_then_else;
@@ -223,7 +228,7 @@ static int strbuf_addf_ret(struct strbuf *sb, int ret, const char *fmt, ...)
return ret;
}
-static int color_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int color_atom_parser(struct ref_format *format, struct used_atom *atom,
const char *color_value, struct strbuf *err)
{
if (!color_value)
@@ -261,7 +266,7 @@ static int refname_atom_parser_internal(struct refname_atom *atom, const char *a
return 0;
}
-static int remote_ref_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int remote_ref_atom_parser(struct ref_format *format, struct used_atom *atom,
const char *arg, struct strbuf *err)
{
struct string_list params = STRING_LIST_INIT_DUP;
@@ -308,7 +313,7 @@ static int remote_ref_atom_parser(const struct ref_format *format, struct used_a
return 0;
}
-static int objecttype_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int objecttype_atom_parser(struct ref_format *format, struct used_atom *atom,
const char *arg, struct strbuf *err)
{
if (arg)
@@ -320,7 +325,7 @@ static int objecttype_atom_parser(const struct ref_format *format, struct used_a
return 0;
}
-static int objectsize_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int objectsize_atom_parser(struct ref_format *format, struct used_atom *atom,
const char *arg, struct strbuf *err)
{
if (!arg) {
@@ -340,7 +345,7 @@ static int objectsize_atom_parser(const struct ref_format *format, struct used_a
return 0;
}
-static int deltabase_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int deltabase_atom_parser(struct ref_format *format, struct used_atom *atom,
const char *arg, struct strbuf *err)
{
if (arg)
@@ -352,7 +357,7 @@ static int deltabase_atom_parser(const struct ref_format *format, struct used_at
return 0;
}
-static int body_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int body_atom_parser(struct ref_format *format, struct used_atom *atom,
const char *arg, struct strbuf *err)
{
if (arg)
@@ -361,7 +366,7 @@ static int body_atom_parser(const struct ref_format *format, struct used_atom *a
return 0;
}
-static int subject_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int subject_atom_parser(struct ref_format *format, struct used_atom *atom,
const char *arg, struct strbuf *err)
{
if (!arg)
@@ -373,7 +378,7 @@ static int subject_atom_parser(const struct ref_format *format, struct used_atom
return 0;
}
-static int trailers_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int trailers_atom_parser(struct ref_format *format, struct used_atom *atom,
const char *arg, struct strbuf *err)
{
atom->u.contents.trailer_opts.no_divider = 1;
@@ -399,7 +404,7 @@ static int trailers_atom_parser(const struct ref_format *format, struct used_ato
return 0;
}
-static int contents_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int contents_atom_parser(struct ref_format *format, struct used_atom *atom,
const char *arg, struct strbuf *err)
{
if (!arg)
@@ -427,7 +432,19 @@ static int contents_atom_parser(const struct ref_format *format, struct used_ato
return 0;
}
-static int oid_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int raw_atom_parser(struct ref_format *format, struct used_atom *atom,
+ const char *arg, struct strbuf *err)
+{
+ if (!arg)
+ atom->u.raw_data.option = RAW_BARE;
+ else if (!strcmp(arg, "size"))
+ atom->u.raw_data.option = RAW_LENGTH;
+ else
+ return strbuf_addf_ret(err, -1, _("unrecognized %%(raw) argument: %s"), arg);
+ return 0;
+}
+
+static int oid_atom_parser(struct ref_format *format, struct used_atom *atom,
const char *arg, struct strbuf *err)
{
if (!arg)
@@ -446,7 +463,7 @@ static int oid_atom_parser(const struct ref_format *format, struct used_atom *at
return 0;
}
-static int person_email_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int person_email_atom_parser(struct ref_format *format, struct used_atom *atom,
const char *arg, struct strbuf *err)
{
if (!arg)
@@ -460,7 +477,7 @@ static int person_email_atom_parser(const struct ref_format *format, struct used
return 0;
}
-static int refname_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int refname_atom_parser(struct ref_format *format, struct used_atom *atom,
const char *arg, struct strbuf *err)
{
return refname_atom_parser_internal(&atom->u.refname, arg, atom->name, err);
@@ -477,7 +494,7 @@ static align_type parse_align_position(const char *s)
return -1;
}
-static int align_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int align_atom_parser(struct ref_format *format, struct used_atom *atom,
const char *arg, struct strbuf *err)
{
struct align *align = &atom->u.align;
@@ -529,7 +546,7 @@ static int align_atom_parser(const struct ref_format *format, struct used_atom *
return 0;
}
-static int if_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int if_atom_parser(struct ref_format *format, struct used_atom *atom,
const char *arg, struct strbuf *err)
{
if (!arg) {
@@ -544,7 +561,16 @@ static int if_atom_parser(const struct ref_format *format, struct used_atom *ato
return 0;
}
-static int head_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int rest_atom_parser(struct ref_format *format, struct used_atom *atom,
+ const char *arg, struct strbuf *err)
+{
+ if (arg)
+ return strbuf_addf_ret(err, -1, _("%%(rest) does not take arguments"));
+ format->use_rest = 1;
+ return 0;
+}
+
+static int head_atom_parser(struct ref_format *format, struct used_atom *atom,
const char *arg, struct strbuf *unused_err)
{
atom->u.head = resolve_refdup("HEAD", RESOLVE_REF_READING, NULL, NULL);
@@ -555,7 +581,7 @@ static struct {
const char *name;
info_source source;
cmp_type cmp_type;
- int (*parser)(const struct ref_format *format, struct used_atom *atom,
+ int (*parser)(struct ref_format *format, struct used_atom *atom,
const char *arg, struct strbuf *err);
} valid_atom[] = {
[ATOM_REFNAME] = { "refname", SOURCE_NONE, FIELD_STR, refname_atom_parser },
@@ -587,6 +613,7 @@ static struct {
[ATOM_BODY] = { "body", SOURCE_OBJ, FIELD_STR, body_atom_parser },
[ATOM_TRAILERS] = { "trailers", SOURCE_OBJ, FIELD_STR, trailers_atom_parser },
[ATOM_CONTENTS] = { "contents", SOURCE_OBJ, FIELD_STR, contents_atom_parser },
+ [ATOM_RAW] = { "raw", SOURCE_OBJ, FIELD_STR, raw_atom_parser },
[ATOM_UPSTREAM] = { "upstream", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },
[ATOM_PUSH] = { "push", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },
[ATOM_SYMREF] = { "symref", SOURCE_NONE, FIELD_STR, refname_atom_parser },
@@ -599,13 +626,14 @@ static struct {
[ATOM_IF] = { "if", SOURCE_NONE, FIELD_STR, if_atom_parser },
[ATOM_THEN] = { "then", SOURCE_NONE },
[ATOM_ELSE] = { "else", SOURCE_NONE },
+ [ATOM_REST] = { "rest", SOURCE_NONE, FIELD_STR, rest_atom_parser },
/*
* Please update $__git_ref_fieldlist in git-completion.bash
* when you add new atoms
*/
};
-#define REF_FORMATTING_STATE_INIT { 0, NULL }
+#define REF_FORMATTING_STATE_INIT { 0 }
struct ref_formatting_stack {
struct ref_formatting_stack *prev;
@@ -621,16 +649,23 @@ struct ref_formatting_state {
struct atom_value {
const char *s;
+ ssize_t s_size;
int (*handler)(struct atom_value *atomv, struct ref_formatting_state *state,
struct strbuf *err);
uintmax_t value; /* used for sorting when not FIELD_STR */
struct used_atom *atom;
};
+#define ATOM_SIZE_UNSPECIFIED (-1)
+
+#define ATOM_VALUE_INIT { \
+ .s_size = ATOM_SIZE_UNSPECIFIED \
+}
+
/*
* Used to parse format string and sort specifiers
*/
-static int parse_ref_filter_atom(const struct ref_format *format,
+static int parse_ref_filter_atom(struct ref_format *format,
const char *atom, const char *ep,
struct strbuf *err)
{
@@ -645,13 +680,6 @@ static int parse_ref_filter_atom(const struct ref_format *format,
return strbuf_addf_ret(err, -1, _("malformed field name: %.*s"),
(int)(ep-atom), atom);
- /* Do we have the atom already used elsewhere? */
- for (i = 0; i < used_atom_cnt; i++) {
- int len = strlen(used_atom[i].name);
- if (len == ep - atom && !memcmp(used_atom[i].name, atom, len))
- return i;
- }
-
/*
* If the atom name has a colon, strip it and everything after
* it off - it specifies the format for this entry, and
@@ -661,6 +689,13 @@ static int parse_ref_filter_atom(const struct ref_format *format,
arg = memchr(sp, ':', ep - sp);
atom_len = (arg ? arg : ep) - sp;
+ /* Do we have the atom already used elsewhere? */
+ for (i = 0; i < used_atom_cnt; i++) {
+ int len = strlen(used_atom[i].name);
+ if (len == ep - atom && !memcmp(used_atom[i].name, atom, len))
+ return i;
+ }
+
/* Is the atom a valid one? */
for (i = 0; i < ARRAY_SIZE(valid_atom); i++) {
int len = strlen(valid_atom[i].name);
@@ -710,17 +745,23 @@ static int parse_ref_filter_atom(const struct ref_format *format,
return at;
}
-static void quote_formatting(struct strbuf *s, const char *str, int quote_style)
+static void quote_formatting(struct strbuf *s, const char *str, ssize_t len, int quote_style)
{
switch (quote_style) {
case QUOTE_NONE:
- strbuf_addstr(s, str);
+ if (len < 0)
+ strbuf_addstr(s, str);
+ else
+ strbuf_add(s, str, len);
break;
case QUOTE_SHELL:
sq_quote_buf(s, str);
break;
case QUOTE_PERL:
- perl_quote_buf(s, str);
+ if (len < 0)
+ perl_quote_buf(s, str);
+ else
+ perl_quote_buf_with_len(s, str, len);
break;
case QUOTE_PYTHON:
python_quote_buf(s, str);
@@ -741,9 +782,11 @@ static int append_atom(struct atom_value *v, struct ref_formatting_state *state,
* encountered.
*/
if (!state->stack->prev)
- quote_formatting(&state->stack->output, v->s, state->quote_style);
- else
+ quote_formatting(&state->stack->output, v->s, v->s_size, state->quote_style);
+ else if (v->s_size < 0)
strbuf_addstr(&state->stack->output, v->s);
+ else
+ strbuf_add(&state->stack->output, v->s, v->s_size);
return 0;
}
@@ -843,21 +886,23 @@ static int if_atom_handler(struct atom_value *atomv, struct ref_formatting_state
return 0;
}
-static int is_empty(const char *s)
+static int is_empty(struct strbuf *buf)
{
- while (*s != '\0') {
- if (!isspace(*s))
- return 0;
- s++;
- }
- return 1;
-}
+ const char *cur = buf->buf;
+ const char *end = buf->buf + buf->len;
+
+ while (cur != end && (isspace(*cur)))
+ cur++;
+
+ return cur == end;
+ }
static int then_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state,
struct strbuf *err)
{
struct ref_formatting_stack *cur = state->stack;
struct if_then_else *if_then_else = NULL;
+ size_t str_len = 0;
if (cur->at_end == if_then_else_handler)
if_then_else = (struct if_then_else *)cur->at_end_data;
@@ -868,18 +913,22 @@ static int then_atom_handler(struct atom_value *atomv, struct ref_formatting_sta
if (if_then_else->else_atom_seen)
return strbuf_addf_ret(err, -1, _("format: %%(then) atom used after %%(else)"));
if_then_else->then_atom_seen = 1;
+ if (if_then_else->str)
+ str_len = strlen(if_then_else->str);
/*
* If the 'equals' or 'notequals' attribute is used then
* perform the required comparison. If not, only non-empty
* strings satisfy the 'if' condition.
*/
if (if_then_else->cmp_status == COMPARE_EQUAL) {
- if (!strcmp(if_then_else->str, cur->output.buf))
+ if (str_len == cur->output.len &&
+ !memcmp(if_then_else->str, cur->output.buf, cur->output.len))
if_then_else->condition_satisfied = 1;
} else if (if_then_else->cmp_status == COMPARE_UNEQUAL) {
- if (strcmp(if_then_else->str, cur->output.buf))
+ if (str_len != cur->output.len ||
+ memcmp(if_then_else->str, cur->output.buf, cur->output.len))
if_then_else->condition_satisfied = 1;
- } else if (cur->output.len && !is_empty(cur->output.buf))
+ } else if (cur->output.len && !is_empty(&cur->output))
if_then_else->condition_satisfied = 1;
strbuf_reset(&cur->output);
return 0;
@@ -925,7 +974,7 @@ static int end_atom_handler(struct atom_value *atomv, struct ref_formatting_stat
* only on the topmost supporting atom.
*/
if (!current->prev->prev) {
- quote_formatting(&s, current->output.buf, state->quote_style);
+ quote_formatting(&s, current->output.buf, current->output.len, state->quote_style);
strbuf_swap(&current->output, &s);
}
strbuf_release(&s);
@@ -955,6 +1004,11 @@ static const char *find_next(const char *cp)
return NULL;
}
+static int reject_atom(enum atom_type atom_type)
+{
+ return atom_type == ATOM_REST;
+}
+
/*
* Make sure the format string is well formed, and parse out
* the used atoms.
@@ -975,6 +1029,16 @@ int verify_ref_format(struct ref_format *format)
at = parse_ref_filter_atom(format, sp + 2, ep, &err);
if (at < 0)
die("%s", err.buf);
+ if (reject_atom(used_atom[at].atom_type))
+ die(_("this command reject atom %%(%.*s)"), (int)(ep - sp - 2), sp + 2);
+
+ if ((format->quote_style == QUOTE_PYTHON ||
+ format->quote_style == QUOTE_SHELL ||
+ format->quote_style == QUOTE_TCL) &&
+ used_atom[at].atom_type == ATOM_RAW &&
+ used_atom[at].u.raw_data.option == RAW_BARE)
+ die(_("--format=%.*s cannot be used with"
+ "--python, --shell, --tcl"), (int)(ep - sp - 2), sp + 2);
cp = ep + 1;
if (skip_prefix(used_atom[at].name, "color:", &color))
@@ -1357,25 +1421,42 @@ static void append_lines(struct strbuf *out, const char *buf, unsigned long size
}
/* See grab_values */
-static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf)
+static void grab_sub_body_contents(struct atom_value *val, int deref, struct expand_data *data)
{
int i;
const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;
size_t sublen = 0, bodylen = 0, nonsiglen = 0, siglen = 0;
+ void *buf = data->content;
for (i = 0; i < used_atom_cnt; i++) {
struct used_atom *atom = &used_atom[i];
const char *name = atom->name;
struct atom_value *v = &val[i];
+ enum atom_type atom_type = atom->atom_type;
if (!!deref != (*name == '*'))
continue;
if (deref)
name++;
- if (strcmp(name, "body") &&
- !starts_with(name, "subject") &&
- !starts_with(name, "trailers") &&
- !starts_with(name, "contents"))
+
+ if (atom_type == ATOM_RAW) {
+ unsigned long buf_size = data->size;
+
+ if (atom->u.raw_data.option == RAW_BARE) {
+ v->s = xmemdupz(buf, buf_size);
+ v->s_size = buf_size;
+ } else if (atom->u.raw_data.option == RAW_LENGTH) {
+ v->s = xstrfmt("%"PRIuMAX, (uintmax_t)buf_size);
+ }
+ continue;
+ }
+
+ if ((data->type != OBJ_TAG &&
+ data->type != OBJ_COMMIT) ||
+ (strcmp(name, "body") &&
+ !starts_with(name, "subject") &&
+ !starts_with(name, "trailers") &&
+ !starts_with(name, "contents")))
continue;
if (!subpos)
find_subpos(buf,
@@ -1439,25 +1520,29 @@ static void fill_missing_values(struct atom_value *val)
* pointed at by the ref itself; otherwise it is the object the
* ref (which is a tag) refers to.
*/
-static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf)
+static void grab_values(struct atom_value *val, int deref, struct object *obj, struct expand_data *data)
{
+ void *buf = data->content;
+
switch (obj->type) {
case OBJ_TAG:
grab_tag_values(val, deref, obj);
- grab_sub_body_contents(val, deref, buf);
+ grab_sub_body_contents(val, deref, data);
grab_person("tagger", val, deref, buf);
break;
case OBJ_COMMIT:
grab_commit_values(val, deref, obj);
- grab_sub_body_contents(val, deref, buf);
+ grab_sub_body_contents(val, deref, data);
grab_person("author", val, deref, buf);
grab_person("committer", val, deref, buf);
break;
case OBJ_TREE:
/* grab_tree_values(val, deref, obj, buf, sz); */
+ grab_sub_body_contents(val, deref, data);
break;
case OBJ_BLOB:
/* grab_blob_values(val, deref, obj, buf, sz); */
+ grab_sub_body_contents(val, deref, data);
break;
default:
die("Eh? Object of type %d?", obj->type);
@@ -1679,7 +1764,7 @@ static int get_object(struct ref_array_item *ref, int deref, struct object **obj
return strbuf_addf_ret(err, -1, _("parse_object_buffer failed on %s for %s"),
oid_to_hex(&oi->oid), ref->refname);
}
- grab_values(ref->value, deref, *obj, oi->content);
+ grab_values(ref->value, deref, *obj, oi);
}
grab_common_values(ref->value, deref, oi);
@@ -1761,6 +1846,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
const char *refname;
struct branch *branch = NULL;
+ v->s_size = ATOM_SIZE_UNSPECIFIED;
v->handler = append_atom;
v->atom = atom;
@@ -1864,6 +1950,12 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
v->handler = else_atom_handler;
v->s = xstrdup("");
continue;
+ } else if (atom_type == ATOM_REST) {
+ if (ref->rest)
+ v->s = xstrdup(ref->rest);
+ else
+ v->s = xstrdup("");
+ continue;
} else
continue;
@@ -2008,8 +2100,7 @@ static int filter_pattern_match(struct ref_filter *filter, const char *refname)
*/
static int for_each_fullref_in_pattern(struct ref_filter *filter,
each_ref_fn cb,
- void *cb_data,
- int broken)
+ void *cb_data)
{
if (!filter->match_as_path) {
/*
@@ -2017,7 +2108,7 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,
* prefixes like "refs/heads/" etc. are stripped off,
* so we have to look at everything:
*/
- return for_each_fullref_in("", cb, cb_data, broken);
+ return for_each_fullref_in("", cb, cb_data);
}
if (filter->ignore_case) {
@@ -2026,16 +2117,16 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,
* so just return everything and let the caller
* sort it out.
*/
- return for_each_fullref_in("", cb, cb_data, broken);
+ return for_each_fullref_in("", cb, cb_data);
}
if (!filter->name_patterns[0]) {
/* no patterns; we have to look at everything */
- return for_each_fullref_in("", cb, cb_data, broken);
+ return for_each_fullref_in("", cb, cb_data);
}
return for_each_fullref_in_prefixes(NULL, filter->name_patterns,
- cb, cb_data, broken);
+ cb, cb_data);
}
/*
@@ -2081,6 +2172,7 @@ static struct ref_array_item *new_ref_array_item(const char *refname,
FLEX_ALLOC_STR(ref, refname, refname);
oidcpy(&ref->objectname, oid);
+ ref->rest = NULL;
return ref;
}
@@ -2312,13 +2404,10 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
{
struct ref_filter_cbdata ref_cbdata;
int ret = 0;
- unsigned int broken = 0;
ref_cbdata.array = array;
ref_cbdata.filter = filter;
- if (type & FILTER_REFS_INCLUDE_BROKEN)
- broken = 1;
filter->kind = type & FILTER_REFS_KIND_MASK;
init_contains_cache(&ref_cbdata.contains_cache);
@@ -2335,13 +2424,13 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
* of filter_ref_kind().
*/
if (filter->kind == FILTER_REFS_BRANCHES)
- ret = for_each_fullref_in("refs/heads/", ref_filter_handler, &ref_cbdata, broken);
+ ret = for_each_fullref_in("refs/heads/", ref_filter_handler, &ref_cbdata);
else if (filter->kind == FILTER_REFS_REMOTES)
- ret = for_each_fullref_in("refs/remotes/", ref_filter_handler, &ref_cbdata, broken);
+ ret = for_each_fullref_in("refs/remotes/", ref_filter_handler, &ref_cbdata);
else if (filter->kind == FILTER_REFS_TAGS)
- ret = for_each_fullref_in("refs/tags/", ref_filter_handler, &ref_cbdata, broken);
+ ret = for_each_fullref_in("refs/tags/", ref_filter_handler, &ref_cbdata);
else if (filter->kind & FILTER_REFS_ALL)
- ret = for_each_fullref_in_pattern(filter, ref_filter_handler, &ref_cbdata, broken);
+ ret = for_each_fullref_in_pattern(filter, ref_filter_handler, &ref_cbdata);
if (!ret && (filter->kind & FILTER_REFS_DETACHED_HEAD))
head_ref(ref_filter_handler, &ref_cbdata);
}
@@ -2368,6 +2457,19 @@ static int compare_detached_head(struct ref_array_item *a, struct ref_array_item
return 0;
}
+static int memcasecmp(const void *vs1, const void *vs2, size_t n)
+{
+ const char *s1 = vs1, *s2 = vs2;
+ const char *end = s1 + n;
+
+ for (; s1 < end; s1++, s2++) {
+ int diff = tolower(*s1) - tolower(*s2);
+ if (diff)
+ return diff;
+ }
+ return 0;
+}
+
static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, struct ref_array_item *b)
{
struct atom_value *va, *vb;
@@ -2388,10 +2490,29 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru
} else if (s->sort_flags & REF_SORTING_VERSION) {
cmp = versioncmp(va->s, vb->s);
} else if (cmp_type == FIELD_STR) {
- int (*cmp_fn)(const char *, const char *);
- cmp_fn = s->sort_flags & REF_SORTING_ICASE
- ? strcasecmp : strcmp;
- cmp = cmp_fn(va->s, vb->s);
+ if (va->s_size < 0 && vb->s_size < 0) {
+ int (*cmp_fn)(const char *, const char *);
+ cmp_fn = s->sort_flags & REF_SORTING_ICASE
+ ? strcasecmp : strcmp;
+ cmp = cmp_fn(va->s, vb->s);
+ } else {
+ size_t a_size = va->s_size < 0 ?
+ strlen(va->s) : va->s_size;
+ size_t b_size = vb->s_size < 0 ?
+ strlen(vb->s) : vb->s_size;
+ int (*cmp_fn)(const void *, const void *, size_t);
+ cmp_fn = s->sort_flags & REF_SORTING_ICASE
+ ? memcasecmp : memcmp;
+
+ cmp = cmp_fn(va->s, vb->s, b_size > a_size ?
+ a_size : b_size);
+ if (!cmp) {
+ if (a_size > b_size)
+ cmp = 1;
+ else if (a_size < b_size)
+ cmp = -1;
+ }
+ }
} else {
if (va->value < vb->value)
cmp = -1;
@@ -2461,9 +2582,9 @@ static void append_literal(const char *cp, const char *ep, struct ref_formatting
}
int format_ref_array_item(struct ref_array_item *info,
- const struct ref_format *format,
- struct strbuf *final_buf,
- struct strbuf *error_buf)
+ struct ref_format *format,
+ struct strbuf *final_buf,
+ struct strbuf *error_buf)
{
const char *cp, *sp, *ep;
struct ref_formatting_state state = REF_FORMATTING_STATE_INIT;
@@ -2490,7 +2611,7 @@ int format_ref_array_item(struct ref_array_item *info,
append_literal(cp, sp, &state);
}
if (format->need_color_reset_at_eol) {
- struct atom_value resetv;
+ struct atom_value resetv = ATOM_VALUE_INIT;
resetv.s = GIT_COLOR_RESET;
if (append_atom(&resetv, &state, error_buf)) {
pop_stack_element(&state.stack);
@@ -2507,7 +2628,7 @@ int format_ref_array_item(struct ref_array_item *info,
}
void pretty_print_ref(const char *name, const struct object_id *oid,
- const struct ref_format *format)
+ struct ref_format *format)
{
struct ref_array_item *ref_item;
struct strbuf output = STRBUF_INIT;
diff --git a/ref-filter.h b/ref-filter.h
index baf72a7189..b636f4389d 100644
--- a/ref-filter.h
+++ b/ref-filter.h
@@ -13,7 +13,6 @@
#define QUOTE_PYTHON 4
#define QUOTE_TCL 8
-#define FILTER_REFS_INCLUDE_BROKEN 0x0001
#define FILTER_REFS_TAGS 0x0002
#define FILTER_REFS_BRANCHES 0x0004
#define FILTER_REFS_REMOTES 0x0008
@@ -38,6 +37,7 @@ struct ref_sorting {
struct ref_array_item {
struct object_id objectname;
+ const char *rest;
int flag;
unsigned int kind;
const char *symref;
@@ -76,14 +76,16 @@ struct ref_format {
* verify_ref_format() afterwards to finalize.
*/
const char *format;
+ const char *rest;
int quote_style;
+ int use_rest;
int use_color;
/* Internal state to ref-filter */
int need_color_reset_at_eol;
};
-#define REF_FORMAT_INIT { NULL, 0, -1 }
+#define REF_FORMAT_INIT { .use_color = -1 }
/* Macros for checking --merged and --no-merged options */
#define _OPT_MERGED_NO_MERGED(option, filter, h) \
@@ -116,7 +118,7 @@ void ref_array_sort(struct ref_sorting *sort, struct ref_array *array);
void ref_sorting_set_sort_flags_all(struct ref_sorting *sorting, unsigned int mask, int on);
/* Based on the given format and quote_style, fill the strbuf */
int format_ref_array_item(struct ref_array_item *info,
- const struct ref_format *format,
+ struct ref_format *format,
struct strbuf *final_buf,
struct strbuf *error_buf);
/* Parse a single sort specifier and add it to the list */
@@ -137,7 +139,7 @@ void setup_ref_filter_porcelain_msg(void);
* name must be a fully qualified refname.
*/
void pretty_print_ref(const char *name, const struct object_id *oid,
- const struct ref_format *format);
+ struct ref_format *format);
/*
* Push a single ref onto the array; this can be used to construct your own
diff --git a/reflog-walk.c b/reflog-walk.c
index e9cd328369..8ac4b284b6 100644
--- a/reflog-walk.c
+++ b/reflog-walk.c
@@ -158,10 +158,9 @@ int add_reflog_for_walk(struct reflog_walk_info *info,
}
reflogs = read_complete_reflog(branch);
if (!reflogs || reflogs->nr == 0) {
- struct object_id oid;
char *b;
int ret = dwim_log(branch, strlen(branch),
- &oid, &b);
+ NULL, &b);
if (ret > 1)
free(b);
else if (ret == 1) {
diff --git a/refs.c b/refs.c
index 8b9f7c3a80..7f019c2377 100644
--- a/refs.c
+++ b/refs.c
@@ -10,6 +10,7 @@
#include "refs.h"
#include "refs/refs-internal.h"
#include "run-command.h"
+#include "hook.h"
#include "object-store.h"
#include "object.h"
#include "tag.h"
@@ -33,11 +34,6 @@ static struct ref_storage_be *find_ref_storage_backend(const char *name)
return NULL;
}
-int ref_storage_backend_exists(const char *name)
-{
- return find_ref_storage_backend(name) != NULL;
-}
-
/*
* How to handle various characters in refnames:
* 0: An acceptable character for refs
@@ -698,7 +694,7 @@ int repo_dwim_log(struct repository *r, const char *str, int len,
strbuf_addf(&path, *p, len, str);
ref = refs_resolve_ref_unsafe(refs, path.buf,
RESOLVE_REF_READING,
- &hash, NULL);
+ oid ? &hash : NULL, NULL);
if (!ref)
continue;
if (refs_reflog_exists(refs, path.buf))
@@ -710,7 +706,8 @@ int repo_dwim_log(struct repository *r, const char *str, int len,
continue;
if (!logs_found++) {
*log = xstrdup(it);
- oidcpy(oid, &hash);
+ if (oid)
+ oidcpy(oid, &hash);
}
if (!warn_ambiguous_refs)
break;
@@ -1413,14 +1410,21 @@ int head_ref(each_ref_fn fn, void *cb_data)
struct ref_iterator *refs_ref_iterator_begin(
struct ref_store *refs,
- const char *prefix, int trim, int flags)
+ const char *prefix, int trim,
+ enum do_for_each_ref_flags flags)
{
struct ref_iterator *iter;
- if (ref_paranoia < 0)
- ref_paranoia = git_env_bool("GIT_REF_PARANOIA", 0);
- if (ref_paranoia)
- flags |= DO_FOR_EACH_INCLUDE_BROKEN;
+ if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
+ static int ref_paranoia = -1;
+
+ if (ref_paranoia < 0)
+ ref_paranoia = git_env_bool("GIT_REF_PARANOIA", 1);
+ if (ref_paranoia) {
+ flags |= DO_FOR_EACH_INCLUDE_BROKEN;
+ flags |= DO_FOR_EACH_OMIT_DANGLING_SYMREFS;
+ }
+ }
iter = refs->be->iterator_begin(refs, prefix, flags);
@@ -1479,7 +1483,8 @@ static int do_for_each_ref_helper(struct repository *r,
}
static int do_for_each_ref(struct ref_store *refs, const char *prefix,
- each_ref_fn fn, int trim, int flags, void *cb_data)
+ each_ref_fn fn, int trim,
+ enum do_for_each_ref_flags flags, void *cb_data)
{
struct ref_iterator *iter;
struct do_for_each_ref_help hp = { fn, cb_data };
@@ -1514,25 +1519,16 @@ int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
return refs_for_each_ref_in(get_main_ref_store(the_repository), prefix, fn, cb_data);
}
-int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken)
+int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data)
{
- unsigned int flag = 0;
-
- if (broken)
- flag = DO_FOR_EACH_INCLUDE_BROKEN;
return do_for_each_ref(get_main_ref_store(the_repository),
- prefix, fn, 0, flag, cb_data);
+ prefix, fn, 0, 0, cb_data);
}
int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
- each_ref_fn fn, void *cb_data,
- unsigned int broken)
+ each_ref_fn fn, void *cb_data)
{
- unsigned int flag = 0;
-
- if (broken)
- flag = DO_FOR_EACH_INCLUDE_BROKEN;
- return do_for_each_ref(refs, prefix, fn, 0, flag, cb_data);
+ return do_for_each_ref(refs, prefix, fn, 0, 0, cb_data);
}
int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_data)
@@ -1624,8 +1620,7 @@ static void find_longest_prefixes(struct string_list *out,
int for_each_fullref_in_prefixes(const char *namespace,
const char **patterns,
- each_ref_fn fn, void *cb_data,
- unsigned int broken)
+ each_ref_fn fn, void *cb_data)
{
struct string_list prefixes = STRING_LIST_INIT_DUP;
struct string_list_item *prefix;
@@ -1640,7 +1635,7 @@ int for_each_fullref_in_prefixes(const char *namespace,
for_each_string_list_item(prefix, &prefixes) {
strbuf_addstr(&buf, prefix->string);
- ret = for_each_fullref_in(buf.buf, fn, cb_data, broken);
+ ret = for_each_fullref_in(buf.buf, fn, cb_data);
if (ret)
break;
strbuf_setlen(&buf, namespace_len);
@@ -1681,7 +1676,7 @@ int refs_read_raw_ref(struct ref_store *ref_store,
}
return ref_store->be->read_raw_ref(ref_store, refname, oid, referent,
- type);
+ type, &errno);
}
/* This function needs to return a meaningful errno on failure */
@@ -2370,19 +2365,19 @@ int delete_reflog(const char *refname)
}
int refs_reflog_expire(struct ref_store *refs,
- const char *refname, const struct object_id *oid,
+ const char *refname,
unsigned int flags,
reflog_expiry_prepare_fn prepare_fn,
reflog_expiry_should_prune_fn should_prune_fn,
reflog_expiry_cleanup_fn cleanup_fn,
void *policy_cb_data)
{
- return refs->be->reflog_expire(refs, refname, oid, flags,
+ return refs->be->reflog_expire(refs, refname, flags,
prepare_fn, should_prune_fn,
cleanup_fn, policy_cb_data);
}
-int reflog_expire(const char *refname, const struct object_id *oid,
+int reflog_expire(const char *refname,
unsigned int flags,
reflog_expiry_prepare_fn prepare_fn,
reflog_expiry_should_prune_fn should_prune_fn,
@@ -2390,7 +2385,7 @@ int reflog_expire(const char *refname, const struct object_id *oid,
void *policy_cb_data)
{
return refs_reflog_expire(get_main_ref_store(the_repository),
- refname, oid, flags,
+ refname, flags,
prepare_fn, should_prune_fn,
cleanup_fn, policy_cb_data);
}
diff --git a/refs.h b/refs.h
index 48970dfc7e..d5099d4984 100644
--- a/refs.h
+++ b/refs.h
@@ -342,10 +342,8 @@ int for_each_ref(each_ref_fn fn, void *cb_data);
int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data);
int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
- each_ref_fn fn, void *cb_data,
- unsigned int broken);
-int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data,
- unsigned int broken);
+ each_ref_fn fn, void *cb_data);
+int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data);
/**
* iterate all refs in "patterns" by partitioning patterns into disjoint sets
@@ -354,8 +352,7 @@ int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data,
* callers should be prepared to ignore references that they did not ask for.
*/
int for_each_fullref_in_prefixes(const char *namespace, const char **patterns,
- each_ref_fn fn, void *cb_data,
- unsigned int broken);
+ each_ref_fn fn, void *cb_data);
/**
* iterate refs from the respective area.
*/
@@ -796,7 +793,7 @@ enum expire_reflog_flags {
* expiration policy that is desired.
*
* reflog_expiry_prepare_fn -- Called once after the reference is
- * locked.
+ * locked. Called with the OID of the locked reference.
*
* reflog_expiry_should_prune_fn -- Called once for each entry in the
* existing reflog. It should return true iff that entry should be
@@ -816,28 +813,25 @@ typedef int reflog_expiry_should_prune_fn(struct object_id *ooid,
typedef void reflog_expiry_cleanup_fn(void *cb_data);
/*
- * Expire reflog entries for the specified reference. oid is the old
- * value of the reference. flags is a combination of the constants in
+ * Expire reflog entries for the specified reference.
+ * flags is a combination of the constants in
* enum expire_reflog_flags. The three function pointers are described
* above. On success, return zero.
*/
int refs_reflog_expire(struct ref_store *refs,
const char *refname,
- const struct object_id *oid,
unsigned int flags,
reflog_expiry_prepare_fn prepare_fn,
reflog_expiry_should_prune_fn should_prune_fn,
reflog_expiry_cleanup_fn cleanup_fn,
void *policy_cb_data);
-int reflog_expire(const char *refname, const struct object_id *oid,
+int reflog_expire(const char *refname,
unsigned int flags,
reflog_expiry_prepare_fn prepare_fn,
reflog_expiry_should_prune_fn should_prune_fn,
reflog_expiry_cleanup_fn cleanup_fn,
void *policy_cb_data);
-int ref_storage_backend_exists(const char *name);
-
struct ref_store *get_main_ref_store(struct repository *r);
/**
diff --git a/refs/debug.c b/refs/debug.c
index 1a7a9e11cf..8667c64023 100644
--- a/refs/debug.c
+++ b/refs/debug.c
@@ -239,15 +239,14 @@ debug_ref_iterator_begin(struct ref_store *ref_store, const char *prefix,
static int debug_read_raw_ref(struct ref_store *ref_store, const char *refname,
struct object_id *oid, struct strbuf *referent,
- unsigned int *type)
+ unsigned int *type, int *failure_errno)
{
struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
int res = 0;
oidcpy(oid, null_oid());
- errno = 0;
res = drefs->refs->be->read_raw_ref(drefs->refs, refname, oid, referent,
- type);
+ type, failure_errno);
if (res == 0) {
trace_printf_key(&trace_refs, "read_raw_ref: %s: %s (=> %s) type %x: %d\n",
@@ -255,7 +254,7 @@ static int debug_read_raw_ref(struct ref_store *ref_store, const char *refname,
} else {
trace_printf_key(&trace_refs,
"read_raw_ref: %s: %d (errno %d)\n", refname,
- res, errno);
+ res, *failure_errno);
}
return res;
}
@@ -365,8 +364,8 @@ struct debug_reflog_expiry_should_prune {
};
static void debug_reflog_expiry_prepare(const char *refname,
- const struct object_id *oid,
- void *cb_data)
+ const struct object_id *oid,
+ void *cb_data)
{
struct debug_reflog_expiry_should_prune *prune = cb_data;
trace_printf_key(&trace_refs, "reflog_expire_prepare: %s\n", refname);
@@ -392,7 +391,7 @@ static void debug_reflog_expiry_cleanup(void *cb_data)
}
static int debug_reflog_expire(struct ref_store *ref_store, const char *refname,
- const struct object_id *oid, unsigned int flags,
+ unsigned int flags,
reflog_expiry_prepare_fn prepare_fn,
reflog_expiry_should_prune_fn should_prune_fn,
reflog_expiry_cleanup_fn cleanup_fn,
@@ -405,7 +404,7 @@ static int debug_reflog_expire(struct ref_store *ref_store, const char *refname,
.should_prune = should_prune_fn,
.cb_data = policy_cb_data,
};
- int res = drefs->refs->be->reflog_expire(drefs->refs, refname, oid,
+ int res = drefs->refs->be->reflog_expire(drefs->refs, refname,
flags, &debug_reflog_expiry_prepare,
&debug_reflog_expiry_should_prune_fn,
&debug_reflog_expiry_cleanup,
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 677b7e4cdd..6a6ead0b99 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -227,7 +227,7 @@ static void add_per_worktree_entries_to_dir(struct ref_dir *dir, const char *dir
pos = search_ref_dir(dir, prefix, prefix_len);
if (pos >= 0)
continue;
- child_entry = create_dir_entry(dir->cache, prefix, prefix_len, 1);
+ child_entry = create_dir_entry(dir->cache, prefix, prefix_len);
add_entry_to_dir(dir, child_entry);
}
}
@@ -278,7 +278,7 @@ static void loose_fill_ref_dir(struct ref_store *ref_store,
strbuf_addch(&refname, '/');
add_entry_to_dir(dir,
create_dir_entry(dir->cache, refname.buf,
- refname.len, 1));
+ refname.len));
} else {
if (!refs_resolve_ref_unsafe(&refs->base,
refname.buf,
@@ -336,14 +336,14 @@ static struct ref_cache *get_loose_ref_cache(struct files_ref_store *refs)
* lazily):
*/
add_entry_to_dir(get_ref_dir(refs->loose->root),
- create_dir_entry(refs->loose, "refs/", 5, 1));
+ create_dir_entry(refs->loose, "refs/", 5));
}
return refs->loose;
}
-static int files_read_raw_ref(struct ref_store *ref_store,
- const char *refname, struct object_id *oid,
- struct strbuf *referent, unsigned int *type)
+static int files_read_raw_ref(struct ref_store *ref_store, const char *refname,
+ struct object_id *oid, struct strbuf *referent,
+ unsigned int *type, int *failure_errno)
{
struct files_ref_store *refs =
files_downcast(ref_store, REF_STORE_READ, "read_raw_ref");
@@ -354,7 +354,6 @@ static int files_read_raw_ref(struct ref_store *ref_store,
struct stat st;
int fd;
int ret = -1;
- int save_errno;
int remaining_retries = 3;
*type = 0;
@@ -459,10 +458,9 @@ stat_ref:
ret = parse_loose_ref_contents(buf, oid, referent, type);
out:
- save_errno = errno;
+ *failure_errno = errno;
strbuf_release(&sb_path);
strbuf_release(&sb_contents);
- errno = save_errno;
return ret;
}
@@ -531,7 +529,6 @@ static void unlock_ref(struct ref_lock *lock)
static int lock_raw_ref(struct files_ref_store *refs,
const char *refname, int mustexist,
const struct string_list *extras,
- const struct string_list *skip,
struct ref_lock **lock_p,
struct strbuf *referent,
unsigned int *type,
@@ -541,6 +538,7 @@ static int lock_raw_ref(struct files_ref_store *refs,
struct strbuf ref_file = STRBUF_INIT;
int attempts_remaining = 3;
int ret = TRANSACTION_GENERIC_ERROR;
+ int failure_errno;
assert(err);
files_assert_main_repository(refs, "lock_raw_ref");
@@ -568,7 +566,7 @@ retry:
* reason to expect this error to be transitory.
*/
if (refs_verify_refname_available(&refs->base, refname,
- extras, skip, err)) {
+ extras, NULL, err)) {
if (mustexist) {
/*
* To the user the relevant error is
@@ -611,7 +609,9 @@ retry:
if (hold_lock_file_for_update_timeout(
&lock->lk, ref_file.buf, LOCK_NO_DEREF,
get_files_ref_lock_timeout_ms()) < 0) {
- if (errno == ENOENT && --attempts_remaining > 0) {
+ int myerr = errno;
+ errno = 0;
+ if (myerr == ENOENT && --attempts_remaining > 0) {
/*
* Maybe somebody just deleted one of the
* directories leading to ref_file. Try
@@ -619,7 +619,7 @@ retry:
*/
goto retry;
} else {
- unable_to_lock_message(ref_file.buf, errno, err);
+ unable_to_lock_message(ref_file.buf, myerr, err);
goto error_return;
}
}
@@ -629,9 +629,9 @@ retry:
* fear that its value will change.
*/
- if (files_read_raw_ref(&refs->base, refname,
- &lock->old_oid, referent, type)) {
- if (errno == ENOENT) {
+ if (files_read_raw_ref(&refs->base, refname, &lock->old_oid, referent,
+ type, &failure_errno)) {
+ if (failure_errno == ENOENT) {
if (mustexist) {
/* Garden variety missing reference. */
strbuf_addf(err, "unable to resolve reference '%s'",
@@ -655,7 +655,7 @@ retry:
* reference named "refs/foo/bar/baz".
*/
}
- } else if (errno == EISDIR) {
+ } else if (failure_errno == EISDIR) {
/*
* There is a directory in the way. It might have
* contained references that have been deleted. If
@@ -673,7 +673,7 @@ retry:
REMOVE_DIR_EMPTY_ONLY)) {
if (refs_verify_refname_available(
&refs->base, refname,
- extras, skip, err)) {
+ extras, NULL, err)) {
/*
* The error message set by
* verify_refname_available() is OK.
@@ -693,13 +693,13 @@ retry:
goto error_return;
}
}
- } else if (errno == EINVAL && (*type & REF_ISBROKEN)) {
+ } else if (failure_errno == EINVAL && (*type & REF_ISBROKEN)) {
strbuf_addf(err, "unable to resolve reference '%s': "
"reference broken", refname);
goto error_return;
} else {
strbuf_addf(err, "unable to resolve reference '%s': %s",
- refname, strerror(errno));
+ refname, strerror(failure_errno));
goto error_return;
}
@@ -710,7 +710,7 @@ retry:
*/
if (refs_verify_refname_available(
refs->packed_ref_store, refname,
- extras, skip, err))
+ extras, NULL, err))
goto error_return;
}
@@ -744,6 +744,11 @@ static int files_ref_iterator_advance(struct ref_iterator *ref_iterator)
ref_type(iter->iter0->refname) != REF_TYPE_PER_WORKTREE)
continue;
+ if ((iter->flags & DO_FOR_EACH_OMIT_DANGLING_SYMREFS) &&
+ (iter->iter0->flags & REF_ISSYMREF) &&
+ (iter->iter0->flags & REF_ISBROKEN))
+ continue;
+
if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
!ref_resolves_to_object(iter->iter0->refname,
iter->iter0->oid,
@@ -854,39 +859,112 @@ static struct ref_iterator *files_ref_iterator_begin(
}
/*
- * Verify that the reference locked by lock has the value old_oid
- * (unless it is NULL). Fail if the reference doesn't exist and
- * mustexist is set. Return 0 on success. On error, write an error
- * message to err, set errno, and return a negative value.
+ * Callback function for raceproof_create_file(). This function is
+ * expected to do something that makes dirname(path) permanent despite
+ * the fact that other processes might be cleaning up empty
+ * directories at the same time. Usually it will create a file named
+ * path, but alternatively it could create another file in that
+ * directory, or even chdir() into that directory. The function should
+ * return 0 if the action was completed successfully. On error, it
+ * should return a nonzero result and set errno.
+ * raceproof_create_file() treats two errno values specially:
+ *
+ * - ENOENT -- dirname(path) does not exist. In this case,
+ * raceproof_create_file() tries creating dirname(path)
+ * (and any parent directories, if necessary) and calls
+ * the function again.
+ *
+ * - EISDIR -- the file already exists and is a directory. In this
+ * case, raceproof_create_file() removes the directory if
+ * it is empty (and recursively any empty directories that
+ * it contains) and calls the function again.
+ *
+ * Any other errno causes raceproof_create_file() to fail with the
+ * callback's return value and errno.
+ *
+ * Obviously, this function should be OK with being called again if it
+ * fails with ENOENT or EISDIR. In other scenarios it will not be
+ * called again.
+ */
+typedef int create_file_fn(const char *path, void *cb);
+
+/*
+ * Create a file in dirname(path) by calling fn, creating leading
+ * directories if necessary. Retry a few times in case we are racing
+ * with another process that is trying to clean up the directory that
+ * contains path. See the documentation for create_file_fn for more
+ * details.
+ *
+ * Return the value and set the errno that resulted from the most
+ * recent call of fn. fn is always called at least once, and will be
+ * called more than once if it returns ENOENT or EISDIR.
*/
-static int verify_lock(struct ref_store *ref_store, struct ref_lock *lock,
- const struct object_id *old_oid, int mustexist,
- struct strbuf *err)
+static int raceproof_create_file(const char *path, create_file_fn fn, void *cb)
{
- assert(err);
+ /*
+ * The number of times we will try to remove empty directories
+ * in the way of path. This is only 1 because if another
+ * process is racily creating directories that conflict with
+ * us, we don't want to fight against them.
+ */
+ int remove_directories_remaining = 1;
- if (refs_read_ref_full(ref_store, lock->ref_name,
- mustexist ? RESOLVE_REF_READING : 0,
- &lock->old_oid, NULL)) {
- if (old_oid) {
- int save_errno = errno;
- strbuf_addf(err, "can't verify ref '%s'", lock->ref_name);
- errno = save_errno;
- return -1;
- } else {
- oidclr(&lock->old_oid);
- return 0;
- }
- }
- if (old_oid && !oideq(&lock->old_oid, old_oid)) {
- strbuf_addf(err, "ref '%s' is at %s but expected %s",
- lock->ref_name,
- oid_to_hex(&lock->old_oid),
- oid_to_hex(old_oid));
- errno = EBUSY;
- return -1;
+ /*
+ * The number of times that we will try to create the
+ * directories containing path. We are willing to attempt this
+ * more than once, because another process could be trying to
+ * clean up empty directories at the same time as we are
+ * trying to create them.
+ */
+ int create_directories_remaining = 3;
+
+ /* A scratch copy of path, filled lazily if we need it: */
+ struct strbuf path_copy = STRBUF_INIT;
+
+ int ret, save_errno;
+
+ /* Sanity check: */
+ assert(*path);
+
+retry_fn:
+ ret = fn(path, cb);
+ save_errno = errno;
+ if (!ret)
+ goto out;
+
+ if (errno == EISDIR && remove_directories_remaining-- > 0) {
+ /*
+ * A directory is in the way. Maybe it is empty; try
+ * to remove it:
+ */
+ if (!path_copy.len)
+ strbuf_addstr(&path_copy, path);
+
+ if (!remove_dir_recursively(&path_copy, REMOVE_DIR_EMPTY_ONLY))
+ goto retry_fn;
+ } else if (errno == ENOENT && create_directories_remaining-- > 0) {
+ /*
+ * Maybe the containing directory didn't exist, or
+ * maybe it was just deleted by a process that is
+ * racing with us to clean up empty directories. Try
+ * to create it:
+ */
+ enum scld_error scld_result;
+
+ if (!path_copy.len)
+ strbuf_addstr(&path_copy, path);
+
+ do {
+ scld_result = safe_create_leading_directories(path_copy.buf);
+ if (scld_result == SCLD_OK)
+ goto retry_fn;
+ } while (scld_result == SCLD_VANISHED && create_directories_remaining-- > 0);
}
- return 0;
+
+out:
+ strbuf_release(&path_copy);
+ errno = save_errno;
+ return ret;
}
static int remove_empty_directories(struct strbuf *path)
@@ -910,64 +988,27 @@ static int create_reflock(const char *path, void *cb)
/*
* Locks a ref returning the lock on success and NULL on failure.
- * On failure errno is set to something meaningful.
*/
static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs,
- const char *refname,
- const struct object_id *old_oid,
- const struct string_list *extras,
- const struct string_list *skip,
- unsigned int flags, int *type,
+ const char *refname, int *type,
struct strbuf *err)
{
struct strbuf ref_file = STRBUF_INIT;
struct ref_lock *lock;
- int last_errno = 0;
- int mustexist = (old_oid && !is_null_oid(old_oid));
- int resolve_flags = RESOLVE_REF_NO_RECURSE;
- int resolved;
files_assert_main_repository(refs, "lock_ref_oid_basic");
assert(err);
CALLOC_ARRAY(lock, 1);
- if (mustexist)
- resolve_flags |= RESOLVE_REF_READING;
- if (flags & REF_DELETING)
- resolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME;
-
files_ref_path(refs, &ref_file, refname);
- resolved = !!refs_resolve_ref_unsafe(&refs->base,
- refname, resolve_flags,
- &lock->old_oid, type);
- if (!resolved && errno == EISDIR) {
- /*
- * we are trying to lock foo but we used to
- * have foo/bar which now does not exist;
- * it is normal for the empty directory 'foo'
- * to remain.
- */
- if (remove_empty_directories(&ref_file)) {
- last_errno = errno;
- if (!refs_verify_refname_available(
- &refs->base,
- refname, extras, skip, err))
- strbuf_addf(err, "there are still refs under '%s'",
- refname);
- goto error_return;
- }
- resolved = !!refs_resolve_ref_unsafe(&refs->base,
- refname, resolve_flags,
- &lock->old_oid, type);
- }
- if (!resolved) {
- last_errno = errno;
- if (last_errno != ENOTDIR ||
- !refs_verify_refname_available(&refs->base, refname,
- extras, skip, err))
+ if (!refs_resolve_ref_unsafe(&refs->base, refname,
+ RESOLVE_REF_NO_RECURSE,
+ &lock->old_oid, type)) {
+ if (!refs_verify_refname_available(&refs->base, refname,
+ NULL, NULL, err))
strbuf_addf(err, "unable to resolve reference '%s': %s",
- refname, strerror(last_errno));
+ refname, strerror(errno));
goto error_return;
}
@@ -980,23 +1021,20 @@ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs,
*/
if (is_null_oid(&lock->old_oid) &&
refs_verify_refname_available(refs->packed_ref_store, refname,
- extras, skip, err)) {
- last_errno = ENOTDIR;
+ NULL, NULL, err))
goto error_return;
- }
lock->ref_name = xstrdup(refname);
if (raceproof_create_file(ref_file.buf, create_reflock, &lock->lk)) {
- last_errno = errno;
unable_to_lock_message(ref_file.buf, errno, err);
goto error_return;
}
- if (verify_lock(&refs->base, lock, old_oid, mustexist, err)) {
- last_errno = errno;
- goto error_return;
- }
+ if (refs_read_ref_full(&refs->base, lock->ref_name,
+ 0,
+ &lock->old_oid, NULL))
+ oidclr(&lock->old_oid);
goto out;
error_return:
@@ -1005,7 +1043,6 @@ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs,
out:
strbuf_release(&ref_file);
- errno = last_errno;
return lock;
}
@@ -1415,8 +1452,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
logmoved = log;
- lock = lock_ref_oid_basic(refs, newrefname, NULL, NULL, NULL,
- REF_NO_DEREF, NULL, &err);
+ lock = lock_ref_oid_basic(refs, newrefname, NULL, &err);
if (!lock) {
if (copy)
error("unable to copy '%s' to '%s': %s", oldrefname, newrefname, err.buf);
@@ -1438,8 +1474,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
goto out;
rollback:
- lock = lock_ref_oid_basic(refs, oldrefname, NULL, NULL, NULL,
- REF_NO_DEREF, NULL, &err);
+ lock = lock_ref_oid_basic(refs, oldrefname, NULL, &err);
if (!lock) {
error("unable to lock %s for rollback: %s", oldrefname, err.buf);
strbuf_release(&err);
@@ -1569,7 +1604,7 @@ static int log_ref_setup(struct files_ref_store *refs,
goto error;
}
} else {
- *logfd = open(logfile, O_APPEND | O_WRONLY, 0666);
+ *logfd = open(logfile, O_APPEND | O_WRONLY);
if (*logfd < 0) {
if (errno == ENOENT || errno == EISDIR) {
/*
@@ -1846,9 +1881,7 @@ static int files_create_symref(struct ref_store *ref_store,
struct ref_lock *lock;
int ret;
- lock = lock_ref_oid_basic(refs, refname, NULL,
- NULL, NULL, REF_NO_DEREF, NULL,
- &err);
+ lock = lock_ref_oid_basic(refs, refname, NULL, &err);
if (!lock) {
error("%s", err.buf);
strbuf_release(&err);
@@ -2416,7 +2449,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
}
ret = lock_raw_ref(refs, update->refname, mustexist,
- affected_refnames, NULL,
+ affected_refnames,
&lock, &referent,
&update->type, err);
if (ret) {
@@ -3037,7 +3070,7 @@ static int expire_reflog_ent(struct object_id *ooid, struct object_id *noid,
}
static int files_reflog_expire(struct ref_store *ref_store,
- const char *refname, const struct object_id *oid,
+ const char *refname,
unsigned int flags,
reflog_expiry_prepare_fn prepare_fn,
reflog_expiry_should_prune_fn should_prune_fn,
@@ -3054,6 +3087,7 @@ static int files_reflog_expire(struct ref_store *ref_store,
int status = 0;
int type;
struct strbuf err = STRBUF_INIT;
+ const struct object_id *oid;
memset(&cb, 0, sizeof(cb));
cb.flags = flags;
@@ -3065,14 +3099,26 @@ static int files_reflog_expire(struct ref_store *ref_store,
* reference itself, plus we might need to update the
* reference if --updateref was specified:
*/
- lock = lock_ref_oid_basic(refs, refname, oid,
- NULL, NULL, REF_NO_DEREF,
- &type, &err);
+ lock = lock_ref_oid_basic(refs, refname, &type, &err);
if (!lock) {
error("cannot lock ref '%s': %s", refname, err.buf);
strbuf_release(&err);
return -1;
}
+ oid = &lock->old_oid;
+
+ /*
+ * When refs are deleted, their reflog is deleted before the
+ * ref itself is deleted. This is because there is no separate
+ * lock for reflog; instead we take a lock on the ref with
+ * lock_ref_oid_basic().
+ *
+ * If a race happens and the reflog doesn't exist after we've
+ * acquired the lock that's OK. We've got nothing more to do;
+ * We were asked to delete the reflog, but someone else
+ * deleted it! The caller doesn't care that we deleted it,
+ * just that it is deleted. So we can return successfully.
+ */
if (!refs_reflog_exists(ref_store, refname)) {
unlock_ref(lock);
return 0;
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index f8aa97d799..47247a1491 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -724,9 +724,9 @@ static struct snapshot *get_snapshot(struct packed_ref_store *refs)
return refs->snapshot;
}
-static int packed_read_raw_ref(struct ref_store *ref_store,
- const char *refname, struct object_id *oid,
- struct strbuf *referent, unsigned int *type)
+static int packed_read_raw_ref(struct ref_store *ref_store, const char *refname,
+ struct object_id *oid, struct strbuf *referent,
+ unsigned int *type, int *failure_errno)
{
struct packed_ref_store *refs =
packed_downcast(ref_store, REF_STORE_READ, "read_raw_ref");
@@ -739,7 +739,7 @@ static int packed_read_raw_ref(struct ref_store *ref_store,
if (!rec) {
/* refname is not a packed reference. */
- errno = ENOENT;
+ *failure_errno = ENOENT;
return -1;
}
@@ -1600,6 +1600,7 @@ static int packed_for_each_reflog_ent(struct ref_store *ref_store,
const char *refname,
each_reflog_ent_fn fn, void *cb_data)
{
+ BUG("packed reference store does not support reflogs");
return 0;
}
@@ -1608,12 +1609,14 @@ static int packed_for_each_reflog_ent_reverse(struct ref_store *ref_store,
each_reflog_ent_fn fn,
void *cb_data)
{
+ BUG("packed reference store does not support reflogs");
return 0;
}
static int packed_reflog_exists(struct ref_store *ref_store,
const char *refname)
{
+ BUG("packed reference store does not support reflogs");
return 0;
}
@@ -1627,17 +1630,19 @@ static int packed_create_reflog(struct ref_store *ref_store,
static int packed_delete_reflog(struct ref_store *ref_store,
const char *refname)
{
+ BUG("packed reference store does not support reflogs");
return 0;
}
static int packed_reflog_expire(struct ref_store *ref_store,
- const char *refname, const struct object_id *oid,
+ const char *refname,
unsigned int flags,
reflog_expiry_prepare_fn prepare_fn,
reflog_expiry_should_prune_fn should_prune_fn,
reflog_expiry_cleanup_fn cleanup_fn,
void *policy_cb_data)
{
+ BUG("packed reference store does not support reflogs");
return 0;
}
diff --git a/refs/ref-cache.c b/refs/ref-cache.c
index 49d732f6db..a5ad8a39fb 100644
--- a/refs/ref-cache.c
+++ b/refs/ref-cache.c
@@ -49,7 +49,7 @@ struct ref_cache *create_ref_cache(struct ref_store *refs,
ret->ref_store = refs;
ret->fill_ref_dir = fill_ref_dir;
- ret->root = create_dir_entry(ret, "", 0, 1);
+ ret->root = create_dir_entry(ret, "", 0);
return ret;
}
@@ -86,14 +86,13 @@ static void clear_ref_dir(struct ref_dir *dir)
}
struct ref_entry *create_dir_entry(struct ref_cache *cache,
- const char *dirname, size_t len,
- int incomplete)
+ const char *dirname, size_t len)
{
struct ref_entry *direntry;
FLEX_ALLOC_MEM(direntry, name, dirname, len);
direntry->u.subdir.cache = cache;
- direntry->flag = REF_DIR | (incomplete ? REF_INCOMPLETE : 0);
+ direntry->flag = REF_DIR | REF_INCOMPLETE;
return direntry;
}
@@ -144,30 +143,19 @@ int search_ref_dir(struct ref_dir *dir, const char *refname, size_t len)
/*
* Search for a directory entry directly within dir (without
* recursing). Sort dir if necessary. subdirname must be a directory
- * name (i.e., end in '/'). If mkdir is set, then create the
- * directory if it is missing; otherwise, return NULL if the desired
+ * name (i.e., end in '/'). Returns NULL if the desired
* directory cannot be found. dir must already be complete.
*/
static struct ref_dir *search_for_subdir(struct ref_dir *dir,
- const char *subdirname, size_t len,
- int mkdir)
+ const char *subdirname, size_t len)
{
int entry_index = search_ref_dir(dir, subdirname, len);
struct ref_entry *entry;
- if (entry_index == -1) {
- if (!mkdir)
- return NULL;
- /*
- * Since dir is complete, the absence of a subdir
- * means that the subdir really doesn't exist;
- * therefore, create an empty record for it but mark
- * the record complete.
- */
- entry = create_dir_entry(dir->cache, subdirname, len, 0);
- add_entry_to_dir(dir, entry);
- } else {
- entry = dir->entries[entry_index];
- }
+
+ if (entry_index == -1)
+ return NULL;
+
+ entry = dir->entries[entry_index];
return get_ref_dir(entry);
}
@@ -176,18 +164,17 @@ static struct ref_dir *search_for_subdir(struct ref_dir *dir,
* tree that should hold refname. If refname is a directory name
* (i.e., it ends in '/'), then return that ref_dir itself. dir must
* represent the top-level directory and must already be complete.
- * Sort ref_dirs and recurse into subdirectories as necessary. If
- * mkdir is set, then create any missing directories; otherwise,
+ * Sort ref_dirs and recurse into subdirectories as necessary. Will
* return NULL if the desired directory cannot be found.
*/
static struct ref_dir *find_containing_dir(struct ref_dir *dir,
- const char *refname, int mkdir)
+ const char *refname)
{
const char *slash;
for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) {
size_t dirnamelen = slash - refname + 1;
struct ref_dir *subdir;
- subdir = search_for_subdir(dir, refname, dirnamelen, mkdir);
+ subdir = search_for_subdir(dir, refname, dirnamelen);
if (!subdir) {
dir = NULL;
break;
@@ -202,7 +189,7 @@ struct ref_entry *find_ref_entry(struct ref_dir *dir, const char *refname)
{
int entry_index;
struct ref_entry *entry;
- dir = find_containing_dir(dir, refname, 0);
+ dir = find_containing_dir(dir, refname);
if (!dir)
return NULL;
entry_index = search_ref_dir(dir, refname, strlen(refname));
@@ -212,50 +199,6 @@ struct ref_entry *find_ref_entry(struct ref_dir *dir, const char *refname)
return (entry->flag & REF_DIR) ? NULL : entry;
}
-int remove_entry_from_dir(struct ref_dir *dir, const char *refname)
-{
- int refname_len = strlen(refname);
- int entry_index;
- struct ref_entry *entry;
- int is_dir = refname[refname_len - 1] == '/';
- if (is_dir) {
- /*
- * refname represents a reference directory. Remove
- * the trailing slash; otherwise we will get the
- * directory *representing* refname rather than the
- * one *containing* it.
- */
- char *dirname = xmemdupz(refname, refname_len - 1);
- dir = find_containing_dir(dir, dirname, 0);
- free(dirname);
- } else {
- dir = find_containing_dir(dir, refname, 0);
- }
- if (!dir)
- return -1;
- entry_index = search_ref_dir(dir, refname, refname_len);
- if (entry_index == -1)
- return -1;
- entry = dir->entries[entry_index];
-
- MOVE_ARRAY(&dir->entries[entry_index],
- &dir->entries[entry_index + 1], dir->nr - entry_index - 1);
- dir->nr--;
- if (dir->sorted > entry_index)
- dir->sorted--;
- free_ref_entry(entry);
- return dir->nr;
-}
-
-int add_ref_entry(struct ref_dir *dir, struct ref_entry *ref)
-{
- dir = find_containing_dir(dir, ref->name, 1);
- if (!dir)
- return -1;
- add_entry_to_dir(dir, ref);
- return 0;
-}
-
/*
* Emit a warning and return true iff ref1 and ref2 have the same name
* and the same oid. Die if they have the same name but different
@@ -522,7 +465,7 @@ struct ref_iterator *cache_ref_iterator_begin(struct ref_cache *cache,
dir = get_ref_dir(cache->root);
if (prefix && *prefix)
- dir = find_containing_dir(dir, prefix, 0);
+ dir = find_containing_dir(dir, prefix);
if (!dir)
/* There's nothing to iterate over. */
return empty_ref_iterator_begin();
diff --git a/refs/ref-cache.h b/refs/ref-cache.h
index 3bfb89d2b3..5c042ae718 100644
--- a/refs/ref-cache.h
+++ b/refs/ref-cache.h
@@ -169,8 +169,7 @@ struct ref_dir *get_ref_dir(struct ref_entry *entry);
* "refs/heads/") or "" for the top-level directory.
*/
struct ref_entry *create_dir_entry(struct ref_cache *cache,
- const char *dirname, size_t len,
- int incomplete);
+ const char *dirname, size_t len);
struct ref_entry *create_ref_entry(const char *refname,
const struct object_id *oid, int flag);
@@ -200,29 +199,6 @@ void free_ref_cache(struct ref_cache *cache);
void add_entry_to_dir(struct ref_dir *dir, struct ref_entry *entry);
/*
- * Remove the entry with the given name from dir, recursing into
- * subdirectories as necessary. If refname is the name of a directory
- * (i.e., ends with '/'), then remove the directory and its contents.
- * If the removal was successful, return the number of entries
- * remaining in the directory entry that contained the deleted entry.
- * If the name was not found, return -1. Please note that this
- * function only deletes the entry from the cache; it does not delete
- * it from the filesystem or ensure that other cache entries (which
- * might be symbolic references to the removed entry) are updated.
- * Nor does it remove any containing dir entries that might be made
- * empty by the removal. dir must represent the top-level directory
- * and must already be complete.
- */
-int remove_entry_from_dir(struct ref_dir *dir, const char *refname);
-
-/*
- * Add a ref_entry to the ref_dir (unsorted), recursing into
- * subdirectories as necessary. dir must represent the top-level
- * directory. Return 0 on success.
- */
-int add_ref_entry(struct ref_dir *dir, struct ref_entry *ref);
-
-/*
* Find the value entry with the given name in dir, sorting ref_dirs
* and recursing into subdirectories as necessary. If the name is not
* found or it corresponds to a directory entry, return NULL.
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 3155708345..72746407fc 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -245,8 +245,36 @@ int refs_rename_ref_available(struct ref_store *refs,
/* We allow "recursive" symbolic refs. Only within reason, though */
#define SYMREF_MAXDEPTH 5
-/* Include broken references in a do_for_each_ref*() iteration: */
-#define DO_FOR_EACH_INCLUDE_BROKEN 0x01
+/*
+ * These flags are passed to refs_ref_iterator_begin() (and do_for_each_ref(),
+ * which feeds it).
+ */
+enum do_for_each_ref_flags {
+ /*
+ * Include broken references in a do_for_each_ref*() iteration, which
+ * would normally be omitted. This includes both refs that point to
+ * missing objects (a true repository corruption), ones with illegal
+ * names (which we prefer not to expose to callers), as well as
+ * dangling symbolic refs (i.e., those that point to a non-existent
+ * ref; this is not a corruption, but as they have no valid oid, we
+ * omit them from normal iteration results).
+ */
+ DO_FOR_EACH_INCLUDE_BROKEN = (1 << 0),
+
+ /*
+ * Only include per-worktree refs in a do_for_each_ref*() iteration.
+ * Normally this will be used with a files ref_store, since that's
+ * where all reference backends will presumably store their
+ * per-worktree refs.
+ */
+ DO_FOR_EACH_PER_WORKTREE_ONLY = (1 << 1),
+
+ /*
+ * Omit dangling symrefs from output; this only has an effect with
+ * INCLUDE_BROKEN, since they are otherwise not included at all.
+ */
+ DO_FOR_EACH_OMIT_DANGLING_SYMREFS = (1 << 2),
+};
/*
* Reference iterators
@@ -349,16 +377,12 @@ int is_empty_ref_iterator(struct ref_iterator *ref_iterator);
* Return an iterator that goes over each reference in `refs` for
* which the refname begins with prefix. If trim is non-zero, then
* trim that many characters off the beginning of each refname.
- * The output is ordered by refname. The following flags are supported:
- *
- * DO_FOR_EACH_INCLUDE_BROKEN: include broken references in
- * the iteration.
- *
- * DO_FOR_EACH_PER_WORKTREE_ONLY: only produce REF_TYPE_PER_WORKTREE refs.
+ * The output is ordered by refname.
*/
struct ref_iterator *refs_ref_iterator_begin(
struct ref_store *refs,
- const char *prefix, int trim, int flags);
+ const char *prefix, int trim,
+ enum do_for_each_ref_flags flags);
/*
* A callback function used to instruct merge_ref_iterator how to
@@ -446,10 +470,8 @@ void base_ref_iterator_free(struct ref_iterator *iter);
/*
* backend-specific implementation of ref_iterator_advance. For symrefs, the
* function should set REF_ISSYMREF, and it should also dereference the symref
- * to provide the OID referent. If DO_FOR_EACH_INCLUDE_BROKEN is set, symrefs
- * with non-existent referents and refs pointing to non-existent object names
- * should also be returned. If DO_FOR_EACH_PER_WORKTREE_ONLY, only
- * REF_TYPE_PER_WORKTREE refs should be returned.
+ * to provide the OID referent. It should respect do_for_each_ref_flags
+ * that were passed to refs_ref_iterator_begin().
*/
typedef int ref_iterator_advance_fn(struct ref_iterator *ref_iterator);
@@ -498,14 +520,6 @@ int do_for_each_repo_ref_iterator(struct repository *r,
struct ref_iterator *iter,
each_repo_ref_fn fn, void *cb_data);
-/*
- * Only include per-worktree refs in a do_for_each_ref*() iteration.
- * Normally this will be used with a files ref_store, since that's
- * where all reference backends will presumably store their
- * per-worktree refs.
- */
-#define DO_FOR_EACH_PER_WORKTREE_ONLY 0x02
-
struct ref_store;
/* refs backends */
@@ -593,7 +607,7 @@ typedef int create_reflog_fn(struct ref_store *ref_store, const char *refname,
int force_create, struct strbuf *err);
typedef int delete_reflog_fn(struct ref_store *ref_store, const char *refname);
typedef int reflog_expire_fn(struct ref_store *ref_store,
- const char *refname, const struct object_id *oid,
+ const char *refname,
unsigned int flags,
reflog_expiry_prepare_fn prepare_fn,
reflog_expiry_should_prune_fn should_prune_fn,
@@ -620,11 +634,15 @@ typedef int reflog_expire_fn(struct ref_store *ref_store,
* properly-formatted or even safe reference name. NEITHER INPUT NOR
* OUTPUT REFERENCE NAMES ARE VALIDATED WITHIN THIS FUNCTION.
*
- * Return 0 on success. If the ref doesn't exist, set errno to ENOENT
- * and return -1. If the ref exists but is neither a symbolic ref nor
- * an object ID, it is broken; set REF_ISBROKEN in type, set errno to
- * EINVAL, and return -1. If there is another error reading the ref,
- * set errno appropriately and return -1.
+ * Return 0 on success, or -1 on failure. If the ref exists but is neither a
+ * symbolic ref nor an object ID, it is broken. In this case set REF_ISBROKEN in
+ * type, and return -1 (failure_errno should not be ENOENT)
+ *
+ * failure_errno provides errno codes that are interpreted beyond error
+ * reporting. The following error codes have special meaning:
+ * * ENOENT: the ref doesn't exist
+ * * EISDIR: ref name is a directory
+ * * ENOTDIR: ref prefix is not a directory
*
* Backend-specific flags might be set in type as well, regardless of
* outcome.
@@ -638,9 +656,9 @@ typedef int reflog_expire_fn(struct ref_store *ref_store,
* - in all other cases, referent will be untouched, and therefore
* refname will still be valid and unchanged.
*/
-typedef int read_raw_ref_fn(struct ref_store *ref_store,
- const char *refname, struct object_id *oid,
- struct strbuf *referent, unsigned int *type);
+typedef int read_raw_ref_fn(struct ref_store *ref_store, const char *refname,
+ struct object_id *oid, struct strbuf *referent,
+ unsigned int *type, int *failure_errno);
struct ref_storage_be {
struct ref_storage_be *next;
diff --git a/remote-curl.c b/remote-curl.c
index 6c320d5704..5975103b96 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -185,8 +185,6 @@ static int set_option(const char *name, const char *value)
strbuf_detach(&unquoted, NULL));
}
return 0;
-
-#if LIBCURL_VERSION_NUM >= 0x070a08
} else if (!strcmp(name, "family")) {
if (!strcmp(value, "ipv4"))
git_curl_ipresolve = CURL_IPRESOLVE_V4;
@@ -197,7 +195,6 @@ static int set_option(const char *name, const char *value)
else
return -1;
return 0;
-#endif /* LIBCURL_VERSION_NUM >= 0x070a08 */
} else if (!strcmp(name, "from-promisor")) {
options.from_promisor = 1;
return 0;
@@ -502,6 +499,10 @@ static struct discovery *discover_refs(const char *service, int for_push)
show_http_message(&type, &charset, &buffer);
die(_("Authentication failed for '%s'"),
transport_anonymize_url(url.buf));
+ case HTTP_NOMATCHPUBLICKEY:
+ show_http_message(&type, &charset, &buffer);
+ die(_("unable to access '%s' with http.pinnedPubkey configuration: %s"),
+ transport_anonymize_url(url.buf), curl_errorstr);
default:
show_http_message(&type, &charset, &buffer);
die(_("unable to access '%s': %s"),
@@ -709,7 +710,6 @@ static size_t rpc_out(void *ptr, size_t eltsize,
return avail;
}
-#ifndef NO_CURL_IOCTL
static curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp)
{
struct rpc_state *rpc = clientp;
@@ -730,7 +730,6 @@ static curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp)
return CURLIOE_UNKNOWNCMD;
}
}
-#endif
struct check_pktline_state {
char len_buf[4];
@@ -858,7 +857,7 @@ static int probe_rpc(struct rpc_state *rpc, struct slot_results *results)
curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE, 4);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_FILE, &buf);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEDATA, &buf);
err = run_slot(slot, results);
@@ -949,10 +948,8 @@ retry:
rpc->initial_buffer = 1;
curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, rpc_out);
curl_easy_setopt(slot->curl, CURLOPT_INFILE, rpc);
-#ifndef NO_CURL_IOCTL
curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, rpc_ioctl);
curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, rpc);
-#endif
if (options.verbosity > 1) {
fprintf(stderr, "POST %s (chunked)\n", rpc->service_name);
fflush(stderr);
@@ -1023,7 +1020,7 @@ retry:
rpc_in_data.slot = slot;
rpc_in_data.check_pktline = stateless_connect;
memset(&rpc_in_data.pktline_state, 0, sizeof(rpc_in_data.pktline_state));
- curl_easy_setopt(slot->curl, CURLOPT_FILE, &rpc_in_data);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEDATA, &rpc_in_data);
curl_easy_setopt(slot->curl, CURLOPT_FAILONERROR, 0);
@@ -1485,8 +1482,8 @@ int cmd_main(int argc, const char **argv)
options.verbosity = 1;
options.progress = !!isatty(2);
options.thin = 1;
- string_list_init(&options.deepen_not, 1);
- string_list_init(&options.push_options, 1);
+ string_list_init_dup(&options.deepen_not);
+ string_list_init_dup(&options.push_options);
/*
* Just report "remote-curl" here (folding all the various aliases
diff --git a/remote.c b/remote.c
index dfb863d808..f958543d70 100644
--- a/remote.c
+++ b/remote.c
@@ -135,7 +135,7 @@ static inline void init_remotes_hash(void)
static struct remote *make_remote(const char *name, int len)
{
- struct remote *ret, *replaced;
+ struct remote *ret;
struct remotes_hash_key lookup;
struct hashmap_entry lookup_entry, *e;
@@ -162,8 +162,8 @@ static struct remote *make_remote(const char *name, int len)
remotes[remotes_nr++] = ret;
hashmap_entry_init(&ret->ent, lookup_entry.hash);
- replaced = hashmap_put_entry(&remotes_hash, ret, ent);
- assert(replaced == NULL); /* no previous entry overwritten */
+ if (hashmap_put_entry(&remotes_hash, ret, ent))
+ BUG("hashmap_put overwrote entry after hashmap_get returned NULL");
return ret;
}
@@ -1111,7 +1111,7 @@ static void show_push_unqualified_ref_name_error(const char *dst_value,
"Neither worked, so we gave up. You must fully qualify the ref."),
dst_value, matched_src_name);
- if (!advice_push_unqualified_ref_name)
+ if (!advice_enabled(ADVICE_PUSH_UNQUALIFIED_REF_NAME))
return;
if (get_oid(matched_src_name, &oid))
@@ -2118,7 +2118,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),
base);
- if (advice_status_hints)
+ if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git branch --unset-upstream\" to fixup)\n"));
} else if (!sti) {
@@ -2129,7 +2129,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
strbuf_addf(sb,
_("Your branch and '%s' refer to different commits.\n"),
base);
- if (advice_status_hints)
+ if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
} else if (!theirs) {
@@ -2138,7 +2138,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
"Your branch is ahead of '%s' by %d commits.\n",
ours),
base, ours);
- if (advice_status_hints)
+ if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
} else if (!ours) {
@@ -2149,7 +2149,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
"and can be fast-forwarded.\n",
theirs),
base, theirs);
- if (advice_status_hints)
+ if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
} else {
@@ -2162,7 +2162,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
"respectively.\n",
ours + theirs),
base, ours, theirs);
- if (advice_status_hints)
+ if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" to merge the remote branch into yours)\n"));
}
@@ -2403,7 +2403,7 @@ struct reflog_commit_array {
size_t nr, alloc;
};
-#define REFLOG_COMMIT_ARRAY_INIT { NULL, 0, 0 }
+#define REFLOG_COMMIT_ARRAY_INIT { 0 }
/* Append a commit to the array. */
static void append_commit(struct reflog_commit_array *arr,
diff --git a/repo-settings.c b/repo-settings.c
index 0cfe8b787d..b93e91a212 100644
--- a/repo-settings.c
+++ b/repo-settings.c
@@ -3,40 +3,77 @@
#include "repository.h"
#include "midx.h"
-#define UPDATE_DEFAULT_BOOL(s,v) do { if (s == -1) { s = v; } } while(0)
+static void repo_cfg_bool(struct repository *r, const char *key, int *dest,
+ int def)
+{
+ if (repo_config_get_bool(r, key, dest))
+ *dest = def;
+}
void prepare_repo_settings(struct repository *r)
{
+ int experimental;
int value;
char *strval;
+ int manyfiles;
- if (r->settings.initialized)
+ if (r->settings.initialized++)
return;
/* Defaults */
- memset(&r->settings, -1, sizeof(r->settings));
+ r->settings.index_version = -1;
+ r->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP;
+ r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_DEFAULT;
+
+ /* Booleans config or default, cascades to other settings */
+ repo_cfg_bool(r, "feature.manyfiles", &manyfiles, 0);
+ repo_cfg_bool(r, "feature.experimental", &experimental, 0);
+
+ /* Defaults modified by feature.* */
+ if (experimental) {
+ r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_SKIPPING;
+ }
+ if (manyfiles) {
+ r->settings.index_version = 4;
+ r->settings.core_untracked_cache = UNTRACKED_CACHE_WRITE;
+ }
- if (!repo_config_get_bool(r, "core.commitgraph", &value))
- r->settings.core_commit_graph = value;
- if (!repo_config_get_bool(r, "commitgraph.readchangedpaths", &value))
- r->settings.commit_graph_read_changed_paths = value;
- if (!repo_config_get_bool(r, "gc.writecommitgraph", &value))
- r->settings.gc_write_commit_graph = value;
- UPDATE_DEFAULT_BOOL(r->settings.core_commit_graph, 1);
- UPDATE_DEFAULT_BOOL(r->settings.commit_graph_read_changed_paths, 1);
- UPDATE_DEFAULT_BOOL(r->settings.gc_write_commit_graph, 1);
+ /* Boolean config or default, does not cascade (simple) */
+ repo_cfg_bool(r, "core.commitgraph", &r->settings.core_commit_graph, 1);
+ repo_cfg_bool(r, "commitgraph.readchangedpaths", &r->settings.commit_graph_read_changed_paths, 1);
+ repo_cfg_bool(r, "gc.writecommitgraph", &r->settings.gc_write_commit_graph, 1);
+ repo_cfg_bool(r, "fetch.writecommitgraph", &r->settings.fetch_write_commit_graph, 0);
+ repo_cfg_bool(r, "pack.usesparse", &r->settings.pack_use_sparse, 1);
+ repo_cfg_bool(r, "core.multipackindex", &r->settings.core_multi_pack_index, 1);
+ repo_cfg_bool(r, "index.sparse", &r->settings.sparse_index, 0);
+ /*
+ * The GIT_TEST_MULTI_PACK_INDEX variable is special in that
+ * either it *or* the config sets
+ * r->settings.core_multi_pack_index if true. We don't take
+ * the environment variable if it exists (even if false) over
+ * any config, as in most other cases.
+ */
+ if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0))
+ r->settings.core_multi_pack_index = 1;
+
+ /*
+ * Non-boolean config
+ */
if (!repo_config_get_int(r, "index.version", &value))
r->settings.index_version = value;
- if (!repo_config_get_maybe_bool(r, "core.untrackedcache", &value)) {
- if (value == 0)
- r->settings.core_untracked_cache = UNTRACKED_CACHE_REMOVE;
- else
- r->settings.core_untracked_cache = UNTRACKED_CACHE_WRITE;
- } else if (!repo_config_get_string(r, "core.untrackedcache", &strval)) {
- if (!strcasecmp(strval, "keep"))
- r->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP;
+ if (!repo_config_get_string(r, "core.untrackedcache", &strval)) {
+ int v = git_parse_maybe_bool(strval);
+
+ /*
+ * If it's set to "keep", or some other non-boolean
+ * value then "v < 0". Then we do nothing and keep it
+ * at the default of UNTRACKED_CACHE_KEEP.
+ */
+ if (v >= 0)
+ r->settings.core_untracked_cache = v ?
+ UNTRACKED_CACHE_WRITE : UNTRACKED_CACHE_REMOVE;
free(strval);
}
@@ -45,39 +82,8 @@ void prepare_repo_settings(struct repository *r)
r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_SKIPPING;
else if (!strcasecmp(strval, "noop"))
r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_NOOP;
- else
- r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_DEFAULT;
}
- if (!repo_config_get_bool(r, "pack.usesparse", &value))
- r->settings.pack_use_sparse = value;
- UPDATE_DEFAULT_BOOL(r->settings.pack_use_sparse, 1);
-
- value = git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0);
- if (value || !repo_config_get_bool(r, "core.multipackindex", &value))
- r->settings.core_multi_pack_index = value;
- UPDATE_DEFAULT_BOOL(r->settings.core_multi_pack_index, 1);
-
- if (!repo_config_get_bool(r, "feature.manyfiles", &value) && value) {
- UPDATE_DEFAULT_BOOL(r->settings.index_version, 4);
- UPDATE_DEFAULT_BOOL(r->settings.core_untracked_cache, UNTRACKED_CACHE_WRITE);
- }
-
- if (!repo_config_get_bool(r, "fetch.writecommitgraph", &value))
- r->settings.fetch_write_commit_graph = value;
- UPDATE_DEFAULT_BOOL(r->settings.fetch_write_commit_graph, 0);
-
- if (!repo_config_get_bool(r, "feature.experimental", &value) && value)
- UPDATE_DEFAULT_BOOL(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_SKIPPING);
-
- /* Hack for test programs like test-dump-untracked-cache */
- if (ignore_untracked_cache_config)
- r->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP;
- else
- UPDATE_DEFAULT_BOOL(r->settings.core_untracked_cache, UNTRACKED_CACHE_KEEP);
-
- UPDATE_DEFAULT_BOOL(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_DEFAULT);
-
/*
* This setting guards all index reads to require a full index
* over a sparse index. After suitable guards are placed in the
@@ -85,11 +91,4 @@ void prepare_repo_settings(struct repository *r)
* removed.
*/
r->settings.command_requires_full_index = 1;
-
- /*
- * Initialize this as off.
- */
- r->settings.sparse_index = 0;
- if (!repo_config_get_bool(r, "index.sparse", &value) && value)
- r->settings.sparse_index = 1;
}
diff --git a/repository.c b/repository.c
index b2bf44c6fa..c5b90ba93e 100644
--- a/repository.c
+++ b/repository.c
@@ -190,19 +190,15 @@ error:
int repo_submodule_init(struct repository *subrepo,
struct repository *superproject,
- const struct submodule *sub)
+ const char *path,
+ const struct object_id *treeish_name)
{
struct strbuf gitdir = STRBUF_INIT;
struct strbuf worktree = STRBUF_INIT;
int ret = 0;
- if (!sub) {
- ret = -1;
- goto out;
- }
-
- strbuf_repo_worktree_path(&gitdir, superproject, "%s/.git", sub->path);
- strbuf_repo_worktree_path(&worktree, superproject, "%s", sub->path);
+ strbuf_repo_worktree_path(&gitdir, superproject, "%s/.git", path);
+ strbuf_repo_worktree_path(&worktree, superproject, "%s", path);
if (repo_init(subrepo, gitdir.buf, worktree.buf)) {
/*
@@ -212,9 +208,15 @@ int repo_submodule_init(struct repository *subrepo,
* in the superproject's 'modules' directory. In this case the
* submodule would not have a worktree.
*/
+ const struct submodule *sub =
+ submodule_from_path(superproject, treeish_name, path);
+ if (!sub) {
+ ret = -1;
+ goto out;
+ }
+
strbuf_reset(&gitdir);
- strbuf_repo_git_path(&gitdir, superproject,
- "modules/%s", sub->name);
+ submodule_name_to_gitdir(&gitdir, superproject, sub->name);
if (repo_init(subrepo, gitdir.buf, NULL)) {
ret = -1;
@@ -225,7 +227,7 @@ int repo_submodule_init(struct repository *subrepo,
subrepo->submodule_prefix = xstrfmt("%s%s/",
superproject->submodule_prefix ?
superproject->submodule_prefix :
- "", sub->path);
+ "", path);
out:
strbuf_release(&gitdir);
diff --git a/repository.h b/repository.h
index 3740c93bc0..a057653981 100644
--- a/repository.h
+++ b/repository.h
@@ -13,18 +13,15 @@ struct submodule_cache;
struct promisor_remote_config;
enum untracked_cache_setting {
- UNTRACKED_CACHE_UNSET = -1,
- UNTRACKED_CACHE_REMOVE = 0,
- UNTRACKED_CACHE_KEEP = 1,
- UNTRACKED_CACHE_WRITE = 2
+ UNTRACKED_CACHE_KEEP,
+ UNTRACKED_CACHE_REMOVE,
+ UNTRACKED_CACHE_WRITE,
};
enum fetch_negotiation_setting {
- FETCH_NEGOTIATION_UNSET = -1,
- FETCH_NEGOTIATION_NONE = 0,
- FETCH_NEGOTIATION_DEFAULT = 1,
- FETCH_NEGOTIATION_SKIPPING = 2,
- FETCH_NEGOTIATION_NOOP = 3,
+ FETCH_NEGOTIATION_DEFAULT,
+ FETCH_NEGOTIATION_SKIPPING,
+ FETCH_NEGOTIATION_NOOP,
};
struct repo_settings {
@@ -34,6 +31,8 @@ struct repo_settings {
int commit_graph_read_changed_paths;
int gc_write_commit_graph;
int fetch_write_commit_graph;
+ int command_requires_full_index;
+ int sparse_index;
int index_version;
enum untracked_cache_setting core_untracked_cache;
@@ -42,9 +41,6 @@ struct repo_settings {
enum fetch_negotiation_setting fetch_negotiation_algorithm;
int core_multi_pack_index;
-
- unsigned command_requires_full_index:1,
- sparse_index:1;
};
struct repository {
@@ -172,15 +168,18 @@ void initialize_the_repository(void);
int repo_init(struct repository *r, const char *gitdir, const char *worktree);
/*
- * Initialize the repository 'subrepo' as the submodule given by the
- * struct submodule 'sub' in parent repository 'superproject'.
- * Return 0 upon success and a non-zero value upon failure, which may happen
- * if the submodule is not found, or 'sub' is NULL.
+ * Initialize the repository 'subrepo' as the submodule at the given path. If
+ * the submodule's gitdir cannot be found at <path>/.git, this function calls
+ * submodule_from_path() to try to find it. treeish_name is only used if
+ * submodule_from_path() needs to be called; see its documentation for more
+ * information.
+ * Return 0 upon success and a non-zero value upon failure.
*/
-struct submodule;
+struct object_id;
int repo_submodule_init(struct repository *subrepo,
struct repository *superproject,
- const struct submodule *sub);
+ const char *path,
+ const struct object_id *treeish_name);
void repo_clear(struct repository *repo);
/*
diff --git a/reset.c b/reset.c
index 79310ae071..f214df3d96 100644
--- a/reset.c
+++ b/reset.c
@@ -56,9 +56,10 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,
unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge;
unpack_tree_opts.update = 1;
unpack_tree_opts.merge = 1;
+ unpack_tree_opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
init_checkout_metadata(&unpack_tree_opts.meta, switch_to_branch, oid, NULL);
if (!detach_head)
- unpack_tree_opts.reset = 1;
+ unpack_tree_opts.reset = UNPACK_RESET_PROTECT_UNTRACKED;
if (repo_read_index_unmerged(r) < 0) {
ret = error(_("could not read index"));
diff --git a/revision.c b/revision.c
index cddd0542a6..ab7c135804 100644
--- a/revision.c
+++ b/revision.c
@@ -249,7 +249,7 @@ struct commit_stack {
struct commit **items;
size_t nr, alloc;
};
-#define COMMIT_STACK_INIT { NULL, 0, 0 }
+#define COMMIT_STACK_INIT { 0 }
static void commit_stack_push(struct commit_stack *stack, struct commit *commit)
{
@@ -360,20 +360,18 @@ static struct object *get_reference(struct rev_info *revs, const char *name,
unsigned int flags)
{
struct object *object;
+ struct commit *commit;
/*
- * If the repository has commit graphs, repo_parse_commit() avoids
- * reading the object buffer, so use it whenever possible.
+ * If the repository has commit graphs, we try to opportunistically
+ * look up the object ID in those graphs. Like this, we can avoid
+ * parsing commit data from disk.
*/
- if (oid_object_info(revs->repo, oid, NULL) == OBJ_COMMIT) {
- struct commit *c = lookup_commit(revs->repo, oid);
- if (!repo_parse_commit(revs->repo, c))
- object = (struct object *) c;
- else
- object = NULL;
- } else {
+ commit = lookup_commit_in_graph(revs->repo, oid);
+ if (commit)
+ object = &commit->object;
+ else
object = parse_object(revs->repo, oid);
- }
if (!object) {
if (revs->ignore_missing)
@@ -1534,7 +1532,7 @@ static int handle_one_ref(const char *path, const struct object_id *oid,
object = get_reference(cb->all_revs, path, oid, cb->all_flags);
add_rev_cmdline(cb->all_revs, object, path, REV_CMD_REF, cb->all_flags);
- add_pending_oid(cb->all_revs, path, oid, cb->all_flags);
+ add_pending_object(cb->all_revs, object, path);
return 0;
}
@@ -2256,6 +2254,10 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
} else if (!strcmp(arg, "--author-date-order")) {
revs->sort_order = REV_SORT_BY_AUTHOR_DATE;
revs->topo_order = 1;
+ } else if (!strcmp(arg, "--unsorted-input")) {
+ if (revs->no_walk)
+ die(_("--unsorted-input is incompatible with --no-walk"));
+ revs->unsorted_input = 1;
} else if (!strcmp(arg, "--early-output")) {
revs->early_output = 100;
revs->topo_order = 1;
@@ -2546,7 +2548,7 @@ static int for_each_bisect_ref(struct ref_store *refs, each_ref_fn fn,
struct strbuf bisect_refs = STRBUF_INIT;
int status;
strbuf_addf(&bisect_refs, "refs/bisect/%s", term);
- status = refs_for_each_fullref_in(refs, bisect_refs.buf, fn, cb_data, 0);
+ status = refs_for_each_fullref_in(refs, bisect_refs.buf, fn, cb_data);
strbuf_release(&bisect_refs);
return status;
}
@@ -2561,8 +2563,7 @@ static int for_each_good_bisect_ref(struct ref_store *refs, each_ref_fn fn, void
return for_each_bisect_ref(refs, fn, cb_data, term_good);
}
-static int handle_revision_pseudo_opt(const char *submodule,
- struct rev_info *revs,
+static int handle_revision_pseudo_opt(struct rev_info *revs,
const char **argv, int *flags)
{
const char *arg = argv[0];
@@ -2570,7 +2571,7 @@ static int handle_revision_pseudo_opt(const char *submodule,
struct ref_store *refs;
int argcount;
- if (submodule) {
+ if (revs->repo != the_repository) {
/*
* We need some something like get_submodule_worktrees()
* before we can go through all worktrees of a submodule,
@@ -2579,9 +2580,8 @@ static int handle_revision_pseudo_opt(const char *submodule,
*/
if (!revs->single_worktree)
BUG("--single-worktree cannot be used together with submodule");
- refs = get_submodule_ref_store(submodule);
- } else
- refs = get_main_ref_store(revs->repo);
+ }
+ refs = get_main_ref_store(revs->repo);
/*
* NOTE!
@@ -2651,16 +2651,22 @@ static int handle_revision_pseudo_opt(const char *submodule,
} else if (!strcmp(arg, "--not")) {
*flags ^= UNINTERESTING | BOTTOM;
} else if (!strcmp(arg, "--no-walk")) {
- revs->no_walk = REVISION_WALK_NO_WALK_SORTED;
+ if (!revs->no_walk && revs->unsorted_input)
+ die(_("--no-walk is incompatible with --unsorted-input"));
+ revs->no_walk = 1;
} else if (skip_prefix(arg, "--no-walk=", &optarg)) {
+ if (!revs->no_walk && revs->unsorted_input)
+ die(_("--no-walk is incompatible with --unsorted-input"));
+
/*
* Detached form ("--no-walk X" as opposed to "--no-walk=X")
* not allowed, since the argument is optional.
*/
+ revs->no_walk = 1;
if (!strcmp(optarg, "sorted"))
- revs->no_walk = REVISION_WALK_NO_WALK_SORTED;
+ revs->unsorted_input = 0;
else if (!strcmp(optarg, "unsorted"))
- revs->no_walk = REVISION_WALK_NO_WALK_UNSORTED;
+ revs->unsorted_input = 1;
else
return error("invalid argument to --no-walk");
} else if (!strcmp(arg, "--do-walk")) {
@@ -2699,12 +2705,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
{
int i, flags, left, seen_dashdash, revarg_opt;
struct strvec prune_data = STRVEC_INIT;
- const char *submodule = NULL;
int seen_end_of_options = 0;
- if (opt)
- submodule = opt->submodule;
-
/* First, search for "--" */
if (opt && opt->assume_dashdash) {
seen_dashdash = 1;
@@ -2733,7 +2735,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
if (!seen_end_of_options && *arg == '-') {
int opts;
- opts = handle_revision_pseudo_opt(submodule,
+ opts = handle_revision_pseudo_opt(
revs, argv + i,
&flags);
if (opts > 0) {
@@ -3584,7 +3586,7 @@ int prepare_revision_walk(struct rev_info *revs)
if (!revs->reflog_info)
prepare_to_use_bloom_filter(revs);
- if (revs->no_walk != REVISION_WALK_NO_WALK_UNSORTED)
+ if (!revs->unsorted_input)
commit_list_sort_by_date(&revs->commits);
if (revs->no_walk)
return 0;
diff --git a/revision.h b/revision.h
index fbb068da9f..5578bb4720 100644
--- a/revision.h
+++ b/revision.h
@@ -79,10 +79,6 @@ struct rev_cmdline_info {
} *rev;
};
-#define REVISION_WALK_WALK 0
-#define REVISION_WALK_NO_WALK_SORTED 1
-#define REVISION_WALK_NO_WALK_UNSORTED 2
-
struct oidset;
struct topo_walk_info;
@@ -129,7 +125,8 @@ struct rev_info {
/* Traversal flags */
unsigned int dense:1,
prune:1,
- no_walk:2,
+ no_walk:1,
+ unsorted_input:1,
remove_empty_trees:1,
simplify_history:1,
show_pulls:1,
@@ -339,7 +336,6 @@ extern volatile show_early_output_fn_t show_early_output;
struct setup_revision_opt {
const char *def;
void (*tweak)(struct rev_info *, struct setup_revision_opt *);
- const char *submodule; /* TODO: drop this and use rev_info->repo */
unsigned int assume_dashdash:1,
allow_exclude_promisor_objects:1;
unsigned revarg_opt;
diff --git a/run-command.c b/run-command.c
index f72e72cce7..7ef5cc712a 100644
--- a/run-command.c
+++ b/run-command.c
@@ -8,6 +8,8 @@
#include "string-list.h"
#include "quote.h"
#include "config.h"
+#include "packfile.h"
+#include "hook.h"
void child_process_init(struct child_process *child)
{
@@ -210,9 +212,9 @@ static char *locate_in_PATH(const char *file)
return NULL;
}
-static int exists_in_PATH(const char *file)
+int exists_in_PATH(const char *command)
{
- char *r = locate_in_PATH(file);
+ char *r = locate_in_PATH(command);
int found = r != NULL;
free(r);
return found;
@@ -740,6 +742,9 @@ fail_pipe:
fflush(NULL);
+ if (cmd->close_object_store)
+ close_object_store(the_repository->objects);
+
#ifndef GIT_WINDOWS_NATIVE
{
int notify_pipe[2];
@@ -761,9 +766,7 @@ fail_pipe:
notify_pipe[0] = notify_pipe[1] = -1;
if (cmd->no_stdin || cmd->no_stdout || cmd->no_stderr) {
- null_fd = open("/dev/null", O_RDWR | O_CLOEXEC);
- if (null_fd < 0)
- die_errno(_("open /dev/null failed"));
+ null_fd = xopen("/dev/null", O_RDWR | O_CLOEXEC);
set_cloexec(null_fd);
}
@@ -1044,6 +1047,7 @@ int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,
cmd.use_shell = opt & RUN_USING_SHELL ? 1 : 0;
cmd.clean_on_exit = opt & RUN_CLEAN_ON_EXIT ? 1 : 0;
cmd.wait_after_clean = opt & RUN_WAIT_AFTER_CLEAN ? 1 : 0;
+ cmd.close_object_store = opt & RUN_CLOSE_OBJECT_STORE ? 1 : 0;
cmd.dir = dir;
cmd.env = env;
cmd.trace2_child_class = tr2_class;
@@ -1319,40 +1323,6 @@ int async_with_fork(void)
#endif
}
-const char *find_hook(const char *name)
-{
- static struct strbuf path = STRBUF_INIT;
-
- strbuf_reset(&path);
- strbuf_git_path(&path, "hooks/%s", name);
- if (access(path.buf, X_OK) < 0) {
- int err = errno;
-
-#ifdef STRIP_EXTENSION
- strbuf_addstr(&path, STRIP_EXTENSION);
- if (access(path.buf, X_OK) >= 0)
- return path.buf;
- if (errno == EACCES)
- err = errno;
-#endif
-
- if (err == EACCES && advice_ignored_hook) {
- static struct string_list advise_given = STRING_LIST_INIT_DUP;
-
- if (!string_list_lookup(&advise_given, name)) {
- string_list_insert(&advise_given, name);
- advise(_("The '%s' hook was ignored because "
- "it's not set as executable.\n"
- "You can disable this warning with "
- "`git config advice.ignoredHook false`."),
- path.buf);
- }
- }
- return NULL;
- }
- return path.buf;
-}
-
int run_hook_ve(const char *const *env, const char *name, va_list args)
{
struct child_process hook = CHILD_PROCESS_INIT;
@@ -1886,6 +1856,7 @@ int run_auto_maintenance(int quiet)
return 0;
maint.git_cmd = 1;
+ maint.close_object_store = 1;
strvec_pushl(&maint.args, "maintenance", "run", "--auto", NULL);
strvec_push(&maint.args, quiet ? "--quiet" : "--no-quiet");
@@ -1903,3 +1874,132 @@ void prepare_other_repo_env(struct strvec *env_array, const char *new_git_dir)
}
strvec_pushf(env_array, "%s=%s", GIT_DIR_ENVIRONMENT, new_git_dir);
}
+
+enum start_bg_result start_bg_command(struct child_process *cmd,
+ start_bg_wait_cb *wait_cb,
+ void *cb_data,
+ unsigned int timeout_sec)
+{
+ enum start_bg_result sbgr = SBGR_ERROR;
+ int ret;
+ int wait_status;
+ pid_t pid_seen;
+ time_t time_limit;
+
+ /*
+ * We do not allow clean-on-exit because the child process
+ * should persist in the background and possibly/probably
+ * after this process exits. So we don't want to kill the
+ * child during our atexit routine.
+ */
+ if (cmd->clean_on_exit)
+ BUG("start_bg_command() does not allow non-zero clean_on_exit");
+
+ if (!cmd->trace2_child_class)
+ cmd->trace2_child_class = "background";
+
+ ret = start_command(cmd);
+ if (ret) {
+ /*
+ * We assume that if `start_command()` fails, we
+ * either get a complete `trace2_child_start() /
+ * trace2_child_exit()` pair or it fails before the
+ * `trace2_child_start()` is emitted, so we do not
+ * need to worry about it here.
+ *
+ * We also assume that `start_command()` does not add
+ * us to the cleanup list. And that it calls
+ * calls `child_process_clear()`.
+ */
+ sbgr = SBGR_ERROR;
+ goto done;
+ }
+
+ time(&time_limit);
+ time_limit += timeout_sec;
+
+wait:
+ pid_seen = waitpid(cmd->pid, &wait_status, WNOHANG);
+
+ if (!pid_seen) {
+ /*
+ * The child is currently running. Ask the callback
+ * if the child is ready to do work or whether we
+ * should keep waiting for it to boot up.
+ */
+ ret = (*wait_cb)(cmd, cb_data);
+ if (!ret) {
+ /*
+ * The child is running and "ready".
+ */
+ trace2_child_ready(cmd, "ready");
+ sbgr = SBGR_READY;
+ goto done;
+ } else if (ret > 0) {
+ /*
+ * The callback said to give it more time to boot up
+ * (subject to our timeout limit).
+ */
+ time_t now;
+
+ time(&now);
+ if (now < time_limit)
+ goto wait;
+
+ /*
+ * Our timeout has expired. We don't try to
+ * kill the child, but rather let it continue
+ * (hopefully) trying to startup.
+ */
+ trace2_child_ready(cmd, "timeout");
+ sbgr = SBGR_TIMEOUT;
+ goto done;
+ } else {
+ /*
+ * The cb gave up on this child. It is still running,
+ * but our cb got an error trying to probe it.
+ */
+ trace2_child_ready(cmd, "error");
+ sbgr = SBGR_CB_ERROR;
+ goto done;
+ }
+ }
+
+ else if (pid_seen == cmd->pid) {
+ int child_code = -1;
+
+ /*
+ * The child started, but exited or was terminated
+ * before becoming "ready".
+ *
+ * We try to match the behavior of `wait_or_whine()`
+ * WRT the handling of WIFSIGNALED() and WIFEXITED()
+ * and convert the child's status to a return code for
+ * tracing purposes and emit the `trace2_child_exit()`
+ * event.
+ *
+ * We do not want the wait_or_whine() error message
+ * because we will be called by client-side library
+ * routines.
+ */
+ if (WIFEXITED(wait_status))
+ child_code = WEXITSTATUS(wait_status);
+ else if (WIFSIGNALED(wait_status))
+ child_code = WTERMSIG(wait_status) + 128;
+ trace2_child_exit(cmd, child_code);
+
+ sbgr = SBGR_DIED;
+ goto done;
+ }
+
+ else if (pid_seen < 0 && errno == EINTR)
+ goto wait;
+
+ trace2_child_exit(cmd, -1);
+ sbgr = SBGR_ERROR;
+
+done:
+ child_process_clear(cmd);
+ invalidate_lstat_cache();
+ return sbgr;
+}
diff --git a/run-command.h b/run-command.h
index af1296769f..4987826258 100644
--- a/run-command.h
+++ b/run-command.h
@@ -134,6 +134,14 @@ struct child_process {
*/
unsigned use_shell:1;
+ /**
+ * Release any open file handles to the object store before running
+ * the command; This is necessary e.g. when the spawned process may
+ * want to repack because that would delete `.pack` files (and on
+ * Windows, you cannot delete files that are still in use).
+ */
+ unsigned close_object_store:1;
+
unsigned stdout_to_stderr:1;
unsigned clean_on_exit:1;
unsigned wait_after_clean:1;
@@ -183,6 +191,18 @@ void child_process_clear(struct child_process *);
int is_executable(const char *name);
/**
+ * Check if the command exists on $PATH. This emulates the path search that
+ * execvp would perform, without actually executing the command so it
+ * can be used before fork() to prepare to run a command using
+ * execve() or after execvp() to diagnose why it failed.
+ *
+ * The caller should ensure that command contains no directory separators.
+ *
+ * Returns 1 if it is found in $PATH or 0 if the command could not be found.
+ */
+int exists_in_PATH(const char *command);
+
+/**
* Start a sub-process. Takes a pointer to a `struct child_process`
* that specifies the details and returns pipe FDs (if requested).
* See below for details.
@@ -204,13 +224,6 @@ int finish_command_in_signal(struct child_process *);
*/
int run_command(struct child_process *);
-/*
- * Returns the path to the hook file, or NULL if the hook is missing
- * or disabled. Note that this points to static storage that will be
- * overwritten by further calls to find_hook and run_hook_*.
- */
-const char *find_hook(const char *name);
-
/**
* Run a hook.
* The first argument is a pathname to an index file, or NULL
@@ -233,13 +246,14 @@ int run_hook_ve(const char *const *env, const char *name, va_list args);
*/
int run_auto_maintenance(int quiet);
-#define RUN_COMMAND_NO_STDIN 1
-#define RUN_GIT_CMD 2 /*If this is to be git sub-command */
-#define RUN_COMMAND_STDOUT_TO_STDERR 4
-#define RUN_SILENT_EXEC_FAILURE 8
-#define RUN_USING_SHELL 16
-#define RUN_CLEAN_ON_EXIT 32
-#define RUN_WAIT_AFTER_CLEAN 64
+#define RUN_COMMAND_NO_STDIN (1<<0)
+#define RUN_GIT_CMD (1<<1)
+#define RUN_COMMAND_STDOUT_TO_STDERR (1<<2)
+#define RUN_SILENT_EXEC_FAILURE (1<<3)
+#define RUN_USING_SHELL (1<<4)
+#define RUN_CLEAN_ON_EXIT (1<<5)
+#define RUN_WAIT_AFTER_CLEAN (1<<6)
+#define RUN_CLOSE_OBJECT_STORE (1<<7)
/**
* Convenience functions that encapsulate a sequence of
@@ -496,4 +510,61 @@ int run_processes_parallel_tr2(int n, get_next_task_fn, start_failure_fn,
*/
void prepare_other_repo_env(struct strvec *env_array, const char *new_git_dir);
+/**
+ * Possible return values for start_bg_command().
+ */
+enum start_bg_result {
+ /* child process is "ready" */
+ SBGR_READY = 0,
+
+ /* child process could not be started */
+ SBGR_ERROR,
+
+ /* callback error when testing for "ready" */
+ SBGR_CB_ERROR,
+
+ /* timeout expired waiting for child to become "ready" */
+ SBGR_TIMEOUT,
+
+ /* child process exited or was signalled before becomming "ready" */
+ SBGR_DIED,
+};
+
+/**
+ * Callback used by start_bg_command() to ask whether the
+ * child process is ready or needs more time to become "ready".
+ *
+ * The callback will receive the cmd and cb_data arguments given to
+ * start_bg_command().
+ *
+ * Returns 1 is child needs more time (subject to the requested timeout).
+ * Returns 0 if child is "ready".
+ * Returns -1 on any error and cause start_bg_command() to also error out.
+ */
+typedef int(start_bg_wait_cb)(const struct child_process *cmd, void *cb_data);
+
+/**
+ * Start a command in the background. Wait long enough for the child
+ * to become "ready" (as defined by the provided callback). Capture
+ * immediate errors (like failure to start) and any immediate exit
+ * status (such as a shutdown/signal before the child became "ready")
+ * and return this like start_command().
+ *
+ * We run a custom wait loop using the provided callback to wait for
+ * the child to start and become "ready". This is limited by the given
+ * timeout value.
+ *
+ * If the child does successfully start and become "ready", we orphan
+ * it into the background.
+ *
+ * The caller must not call finish_command().
+ *
+ * The opaque cb_data argument will be forwarded to the callback for
+ * any instance data that it might require. This may be NULL.
+ */
+enum start_bg_result start_bg_command(struct child_process *cmd,
+ start_bg_wait_cb *wait_cb,
+ void *cb_data,
+ unsigned int timeout_sec);
+
#endif
diff --git a/send-pack.c b/send-pack.c
index 5a79e0e711..b3a495b7b1 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -425,8 +425,10 @@ static void get_commons_through_negotiation(const char *url,
child.no_stdin = 1;
child.out = -1;
strvec_pushl(&child.args, "fetch", "--negotiate-only", NULL);
- for (ref = remote_refs; ref; ref = ref->next)
- strvec_pushf(&child.args, "--negotiation-tip=%s", oid_to_hex(&ref->new_oid));
+ for (ref = remote_refs; ref; ref = ref->next) {
+ if (!is_null_oid(&ref->new_oid))
+ strvec_pushf(&child.args, "--negotiation-tip=%s", oid_to_hex(&ref->new_oid));
+ }
strvec_push(&child.args, url);
if (start_command(&child))
diff --git a/sequencer.c b/sequencer.c
index 7f07cd00f3..74ecb23d20 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -8,6 +8,7 @@
#include "sequencer.h"
#include "tag.h"
#include "run-command.h"
+#include "hook.h"
#include "exec-cmd.h"
#include "utf8.h"
#include "cache-tree.h"
@@ -403,7 +404,7 @@ static void print_advice(struct repository *r, int show_hint,
char *msg = getenv("GIT_CHERRY_PICK_HELP");
if (msg) {
- fprintf(stderr, "%s\n", msg);
+ advise("%s\n", msg);
/*
* A conflict has occurred but the porcelain
* (typically rebase --interactive) wants to take care
@@ -418,10 +419,22 @@ static void print_advice(struct repository *r, int show_hint,
if (opts->no_commit)
advise(_("after resolving the conflicts, mark the corrected paths\n"
"with 'git add <paths>' or 'git rm <paths>'"));
+ else if (opts->action == REPLAY_PICK)
+ advise(_("After resolving the conflicts, mark them with\n"
+ "\"git add/rm <pathspec>\", then run\n"
+ "\"git cherry-pick --continue\".\n"
+ "You can instead skip this commit with \"git cherry-pick --skip\".\n"
+ "To abort and get back to the state before \"git cherry-pick\",\n"
+ "run \"git cherry-pick --abort\"."));
+ else if (opts->action == REPLAY_REVERT)
+ advise(_("After resolving the conflicts, mark them with\n"
+ "\"git add/rm <pathspec>\", then run\n"
+ "\"git revert --continue\".\n"
+ "You can instead skip this commit with \"git revert --skip\".\n"
+ "To abort and get back to the state before \"git revert\",\n"
+ "run \"git revert --abort\"."));
else
- advise(_("after resolving the conflicts, mark the corrected paths\n"
- "with 'git add <paths>' or 'git rm <paths>'\n"
- "and commit the result with 'git commit'"));
+ BUG("unexpected pick action in print_advice()");
}
}
@@ -486,7 +499,7 @@ static int error_dirty_index(struct repository *repo, struct replay_opts *opts)
error(_("your local changes would be overwritten by %s."),
_(action_name(opts)));
- if (advice_commit_before_merge)
+ if (advice_enabled(ADVICE_COMMIT_BEFORE_MERGE))
advise(_("commit your changes or stash them to proceed."));
return -1;
}
@@ -636,7 +649,7 @@ static int do_recursive_merge(struct repository *r,
for (i = 0; i < opts->xopts_nr; i++)
parse_merge_opt(&o, opts->xopts[i]);
- if (opts->strategy && !strcmp(opts->strategy, "ort")) {
+ if (!opts->strategy || !strcmp(opts->strategy, "ort")) {
memset(&result, 0, sizeof(result));
merge_incore_nonrecursive(&o, base_tree, head_tree, next_tree,
&result);
@@ -652,6 +665,7 @@ static int do_recursive_merge(struct repository *r,
merge_switch_to_result(&o, head_tree, &result, 1, show_output);
clean = result.clean;
} else {
+ ensure_full_index(r->index);
clean = merge_trees(&o, head_tree, next_tree, base_tree);
if (is_rebase_i(opts) && clean <= 0)
fputs(o.obuf.buf, stdout);
@@ -983,7 +997,8 @@ static int run_git_commit(const char *defmsg,
cmd.git_cmd = 1;
- if (is_rebase_i(opts) && read_env_script(&cmd.env_array)) {
+ if (is_rebase_i(opts) && !(!defmsg && (flags & AMEND_MSG)) &&
+ read_env_script(&cmd.env_array)) {
const char *gpg_opt = gpg_sign_opt_quoted(opts);
return error(_(staged_changes_advice),
@@ -1241,7 +1256,7 @@ N_("Your name and email address were configured automatically based\n"
static const char *implicit_ident_advice(void)
{
- char *user_config = expand_user_path("~/.gitconfig", 0);
+ char *user_config = interpolate_path("~/.gitconfig", 0);
char *xdg_config = xdg_config_home("config");
int config_exists = file_exists(user_config) || file_exists(xdg_config);
@@ -1293,7 +1308,7 @@ void print_commit_summary(struct repository *r,
if (!committer_ident_sufficiently_given()) {
strbuf_addstr(&format, "\n Committer: ");
strbuf_addbuf_percentquote(&format, &committer_ident);
- if (advice_implicit_identity) {
+ if (advice_enabled(ADVICE_IMPLICIT_IDENTITY)) {
strbuf_addch(&format, '\n');
strbuf_addstr(&format, implicit_ident_advice());
}
@@ -1445,7 +1460,7 @@ static int try_to_commit(struct repository *r,
}
}
- if (find_hook("prepare-commit-msg")) {
+ if (hook_exists("prepare-commit-msg")) {
res = run_prepare_commit_msg_hook(r, msg, hook_commit);
if (res)
goto out;
@@ -2065,7 +2080,7 @@ static int do_pick_commit(struct repository *r,
/*
* We do not intend to commit immediately. We just want to
* merge the differences in, so let's compute the tree
- * that represents the "current" state for merge-recursive
+ * that represents the "current" state for the merge machinery
* to work on.
*/
if (write_index_as_tree(&head, r->index, r->index_file, 0, NULL))
@@ -2346,6 +2361,7 @@ static int read_and_refresh_cache(struct repository *r,
_(action_name(opts)));
}
refresh_index(r->index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
+
if (index_fd >= 0) {
if (write_locked_index(r->index, &index_lock,
COMMIT_LOCK | SKIP_IF_UNCHANGED)) {
@@ -2353,6 +2369,13 @@ static int read_and_refresh_cache(struct repository *r,
_(action_name(opts)));
}
}
+
+ /*
+ * If we are resolving merges in any way other than "ort", then
+ * expand the sparse index.
+ */
+ if (opts->strategy && strcmp(opts->strategy, "ort"))
+ ensure_full_index(r->index);
return 0;
}
@@ -2671,7 +2694,6 @@ static int read_populate_todo(struct repository *r,
struct todo_list *todo_list,
struct replay_opts *opts)
{
- struct stat st;
const char *todo_file = get_todo_path(opts);
int res;
@@ -2679,11 +2701,6 @@ static int read_populate_todo(struct repository *r,
if (strbuf_read_file_or_whine(&todo_list->buf, todo_file) < 0)
return -1;
- res = stat(todo_file, &st);
- if (res)
- return error(_("could not stat '%s'"), todo_file);
- fill_stat_data(&todo_list->stat, &st);
-
res = todo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list);
if (res) {
if (is_rebase_i(opts))
@@ -3041,7 +3058,7 @@ static int create_seq_dir(struct repository *r)
}
if (in_progress_error) {
error("%s", in_progress_error);
- if (advice_sequencer_in_use)
+ if (advice_enabled(ADVICE_SEQUENCER_IN_USE))
advise(in_progress_advice,
advise_skip ? "--skip | " : "");
return -1;
@@ -3245,7 +3262,7 @@ int sequencer_skip(struct repository *r, struct replay_opts *opts)
give_advice:
error(_("there is nothing to skip"));
- if (advice_resolve_conflict) {
+ if (advice_enabled(ADVICE_RESOLVE_CONFLICT)) {
advise(_("have you committed already?\n"
"try \"git %s --continue\""),
action == REPLAY_REVERT ? "revert" : "cherry-pick");
@@ -3677,6 +3694,7 @@ static int do_reset(struct repository *r,
unpack_tree_opts.fn = oneway_merge;
unpack_tree_opts.merge = 1;
unpack_tree_opts.update = 1;
+ unpack_tree_opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
init_checkout_metadata(&unpack_tree_opts.meta, name, &oid, NULL);
if (repo_read_index_unmerged(r)) {
@@ -3739,10 +3757,9 @@ static struct commit *lookup_label(const char *label, int len,
static int do_merge(struct repository *r,
struct commit *commit,
const char *arg, int arg_len,
- int flags, struct replay_opts *opts)
+ int flags, int *check_todo, struct replay_opts *opts)
{
- int run_commit_flags = (flags & TODO_EDIT_MERGE_MSG) ?
- EDIT_MSG | VERIFY_MSG : 0;
+ int run_commit_flags = 0;
struct strbuf ref_name = STRBUF_INIT;
struct commit *head_commit, *merge_commit, *i;
struct commit_list *bases, *j, *reversed = NULL;
@@ -3816,6 +3833,45 @@ static int do_merge(struct repository *r,
goto leave_merge;
}
+ /*
+ * If HEAD is not identical to the first parent of the original merge
+ * commit, we cannot fast-forward.
+ */
+ can_fast_forward = opts->allow_ff && commit && commit->parents &&
+ oideq(&commit->parents->item->object.oid,
+ &head_commit->object.oid);
+
+ /*
+ * If any merge head is different from the original one, we cannot
+ * fast-forward.
+ */
+ if (can_fast_forward) {
+ struct commit_list *p = commit->parents->next;
+
+ for (j = to_merge; j && p; j = j->next, p = p->next)
+ if (!oideq(&j->item->object.oid,
+ &p->item->object.oid)) {
+ can_fast_forward = 0;
+ break;
+ }
+ /*
+ * If the number of merge heads differs from the original merge
+ * commit, we cannot fast-forward.
+ */
+ if (j || p)
+ can_fast_forward = 0;
+ }
+
+ if (can_fast_forward) {
+ rollback_lock_file(&lock);
+ ret = fast_forward_to(r, &commit->object.oid,
+ &head_commit->object.oid, 0, opts);
+ if (flags & TODO_EDIT_MERGE_MSG)
+ goto fast_forward_edit;
+
+ goto leave_merge;
+ }
+
if (commit) {
const char *encoding = get_commit_output_encoding();
const char *message = logmsg_reencode(commit, NULL, encoding);
@@ -3865,46 +3921,6 @@ static int do_merge(struct repository *r,
}
}
- /*
- * If HEAD is not identical to the first parent of the original merge
- * commit, we cannot fast-forward.
- */
- can_fast_forward = opts->allow_ff && commit && commit->parents &&
- oideq(&commit->parents->item->object.oid,
- &head_commit->object.oid);
-
- /*
- * If any merge head is different from the original one, we cannot
- * fast-forward.
- */
- if (can_fast_forward) {
- struct commit_list *p = commit->parents->next;
-
- for (j = to_merge; j && p; j = j->next, p = p->next)
- if (!oideq(&j->item->object.oid,
- &p->item->object.oid)) {
- can_fast_forward = 0;
- break;
- }
- /*
- * If the number of merge heads differs from the original merge
- * commit, we cannot fast-forward.
- */
- if (j || p)
- can_fast_forward = 0;
- }
-
- if (can_fast_forward) {
- rollback_lock_file(&lock);
- ret = fast_forward_to(r, &commit->object.oid,
- &head_commit->object.oid, 0, opts);
- if (flags & TODO_EDIT_MERGE_MSG) {
- run_commit_flags |= AMEND_MSG;
- goto fast_forward_edit;
- }
- goto leave_merge;
- }
-
if (strategy || to_merge->next) {
/* Octopus merge */
struct child_process cmd = CHILD_PROCESS_INIT;
@@ -3935,7 +3951,10 @@ static int do_merge(struct repository *r,
strvec_pushf(&cmd.args,
"-X%s", opts->xopts[k]);
}
- strvec_push(&cmd.args, "--no-edit");
+ if (!(flags & TODO_EDIT_MERGE_MSG))
+ strvec_push(&cmd.args, "--no-edit");
+ else
+ strvec_push(&cmd.args, "--edit");
strvec_push(&cmd.args, "--no-ff");
strvec_push(&cmd.args, "--no-log");
strvec_push(&cmd.args, "--no-stat");
@@ -3988,7 +4007,7 @@ static int do_merge(struct repository *r,
o.branch2 = ref_name.buf;
o.buffer_output = 2;
- if (opts->strategy && !strcmp(opts->strategy, "ort")) {
+ if (!opts->strategy || !strcmp(opts->strategy, "ort")) {
/*
* TODO: Should use merge_incore_recursive() and
* merge_switch_to_result(), skipping the call to
@@ -4035,10 +4054,17 @@ static int do_merge(struct repository *r,
* value (a negative one would indicate that the `merge`
* command needs to be rescheduled).
*/
- fast_forward_edit:
ret = !!run_git_commit(git_path_merge_msg(r), opts,
run_commit_flags);
+ if (!ret && flags & TODO_EDIT_MERGE_MSG) {
+ fast_forward_edit:
+ *check_todo = 1;
+ run_commit_flags |= AMEND_MSG | EDIT_MSG | VERIFY_MSG;
+ ret = !!run_git_commit(NULL, opts, run_commit_flags);
+ }
+
+
leave_merge:
strbuf_release(&ref_name);
rollback_lock_file(&lock);
@@ -4254,6 +4280,30 @@ static int stopped_at_head(struct repository *r)
}
+static int reread_todo_if_changed(struct repository *r,
+ struct todo_list *todo_list,
+ struct replay_opts *opts)
+{
+ int offset;
+ struct strbuf buf = STRBUF_INIT;
+
+ if (strbuf_read_file_or_whine(&buf, get_todo_path(opts)) < 0)
+ return -1;
+ offset = get_item_line_offset(todo_list, todo_list->current + 1);
+ if (buf.len != todo_list->buf.len - offset ||
+ memcmp(buf.buf, todo_list->buf.buf + offset, buf.len)) {
+ /* Reread the todo file if it has changed. */
+ todo_list_release(todo_list);
+ if (read_populate_todo(r, todo_list, opts))
+ return -1; /* message was printed */
+ /* `current` will be incremented on return */
+ todo_list->current = -1;
+ }
+ strbuf_release(&buf);
+
+ return 0;
+}
+
static const char rescheduled_advice[] =
N_("Could not execute the todo command\n"
"\n"
@@ -4405,9 +4455,8 @@ static int pick_commits(struct repository *r,
if ((res = do_reset(r, arg, item->arg_len, opts)))
reschedule = 1;
} else if (item->command == TODO_MERGE) {
- if ((res = do_merge(r, item->commit,
- arg, item->arg_len,
- item->flags, opts)) < 0)
+ if ((res = do_merge(r, item->commit, arg, item->arg_len,
+ item->flags, &check_todo, opts)) < 0)
reschedule = 1;
else if (item->commit)
record_in_rewritten(&item->commit->object.oid,
@@ -4433,20 +4482,9 @@ static int pick_commits(struct repository *r,
item->commit,
arg, item->arg_len,
opts, res, 0);
- } else if (is_rebase_i(opts) && check_todo && !res) {
- struct stat st;
-
- if (stat(get_todo_path(opts), &st)) {
- res = error_errno(_("could not stat '%s'"),
- get_todo_path(opts));
- } else if (match_stat_data(&todo_list->stat, &st)) {
- /* Reread the todo file if it has changed. */
- todo_list_release(todo_list);
- if (read_populate_todo(r, todo_list, opts))
- res = -1; /* message was printed */
- /* `current` will be incremented below */
- todo_list->current = -1;
- }
+ } else if (is_rebase_i(opts) && check_todo && !res &&
+ reread_todo_if_changed(r, todo_list, opts)) {
+ return -1;
}
todo_list->current++;
@@ -4716,6 +4754,9 @@ static int commit_staged_changes(struct repository *r,
refs_delete_ref(get_main_ref_store(r), "",
"CHERRY_PICK_HEAD", NULL, 0))
return error(_("could not remove CHERRY_PICK_HEAD"));
+ if (unlink(git_path_merge_msg(r)) && errno != ENOENT)
+ return error_errno(_("could not remove '%s'"),
+ git_path_merge_msg(r));
if (!final_fixup)
return 0;
}
@@ -5099,6 +5140,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,
int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;
+ int skipped_commit = 0;
struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
struct strbuf label = STRBUF_INIT;
struct commit_list *commits = NULL, **tail = &commits, *iter;
@@ -5149,8 +5191,13 @@ static int make_script_with_merges(struct pretty_print_context *pp,
oidset_insert(&interesting, &commit->object.oid);
is_empty = is_original_commit_empty(commit);
- if (!is_empty && (commit->object.flags & PATCHSAME))
+ if (!is_empty && (commit->object.flags & PATCHSAME)) {
+ if (flags & TODO_LIST_WARN_SKIPPED_CHERRY_PICKS)
+ warning(_("skipped previously applied commit %s"),
+ short_commit_name(commit));
+ skipped_commit = 1;
continue;
+ }
if (is_empty && !keep_empty)
continue;
@@ -5214,6 +5261,9 @@ static int make_script_with_merges(struct pretty_print_context *pp,
oidcpy(&entry->entry.oid, &commit->object.oid);
oidmap_put(&commit2todo, entry);
}
+ if (skipped_commit)
+ advise_if_enabled(ADVICE_SKIPPED_CHERRY_PICKS,
+ _("use --reapply-cherry-picks to include skipped commits"));
/*
* Second phase:
@@ -5334,6 +5384,7 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
int reapply_cherry_picks = flags & TODO_LIST_REAPPLY_CHERRY_PICKS;
+ int skipped_commit = 0;
repo_init_revisions(r, &revs, NULL);
revs.verbose_header = 1;
@@ -5369,8 +5420,13 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
while ((commit = get_revision(&revs))) {
int is_empty = is_original_commit_empty(commit);
- if (!is_empty && (commit->object.flags & PATCHSAME))
+ if (!is_empty && (commit->object.flags & PATCHSAME)) {
+ if (flags & TODO_LIST_WARN_SKIPPED_CHERRY_PICKS)
+ warning(_("skipped previously applied commit %s"),
+ short_commit_name(commit));
+ skipped_commit = 1;
continue;
+ }
if (is_empty && !keep_empty)
continue;
strbuf_addf(out, "%s %s ", insn,
@@ -5380,6 +5436,9 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
strbuf_addf(out, " %c empty", comment_line_char);
strbuf_addch(out, '\n');
}
+ if (skipped_commit)
+ advise_if_enabled(ADVICE_SKIPPED_CHERRY_PICKS,
+ _("use --reapply-cherry-picks to include skipped commits"));
return 0;
}
@@ -5387,8 +5446,8 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
* Add commands after pick and (series of) squash/fixup commands
* in the todo list.
*/
-void todo_list_add_exec_commands(struct todo_list *todo_list,
- struct string_list *commands)
+static void todo_list_add_exec_commands(struct todo_list *todo_list,
+ struct string_list *commands)
{
struct strbuf *buf = &todo_list->buf;
size_t base_offset = buf->len;
diff --git a/sequencer.h b/sequencer.h
index d57d8ea23d..05a7d2ba6b 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -116,10 +116,11 @@ struct todo_list {
struct todo_item *items;
int nr, alloc, current;
int done_nr, total_nr;
- struct stat_data stat;
};
-#define TODO_LIST_INIT { STRBUF_INIT }
+#define TODO_LIST_INIT { \
+ .buf = STRBUF_INIT, \
+}
int todo_list_parse_insn_buffer(struct repository *r, char *buf,
struct todo_list *todo_list);
@@ -156,12 +157,11 @@ int sequencer_remove_state(struct replay_opts *opts);
*/
#define TODO_LIST_ROOT_WITH_ONTO (1U << 6)
#define TODO_LIST_REAPPLY_CHERRY_PICKS (1U << 7)
+#define TODO_LIST_WARN_SKIPPED_CHERRY_PICKS (1U << 8)
int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
const char **argv, unsigned flags);
-void todo_list_add_exec_commands(struct todo_list *todo_list,
- struct string_list *commands);
int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags,
const char *shortrevisions, const char *onto_name,
struct commit *onto, const struct object_id *orig_head,
diff --git a/serve.c b/serve.c
index f11c0e07c4..b3fe9b5126 100644
--- a/serve.c
+++ b/serve.c
@@ -9,7 +9,8 @@
#include "serve.h"
#include "upload-pack.h"
-static int advertise_sid;
+static int advertise_sid = -1;
+static int client_hash_algo = GIT_HASH_SHA1;
static int always_advertise(struct repository *r,
struct strbuf *value)
@@ -33,8 +34,22 @@ static int object_format_advertise(struct repository *r,
return 1;
}
+static void object_format_receive(struct repository *r,
+ const char *algo_name)
+{
+ if (!algo_name)
+ die("object-format capability requires an argument");
+
+ client_hash_algo = hash_algo_by_name(algo_name);
+ if (client_hash_algo == GIT_HASH_UNKNOWN)
+ die("unknown object format '%s'", algo_name);
+}
+
static int session_id_advertise(struct repository *r, struct strbuf *value)
{
+ if (advertise_sid == -1 &&
+ git_config_get_bool("transfer.advertisesid", &advertise_sid))
+ advertise_sid = 0;
if (!advertise_sid)
return 0;
if (value)
@@ -42,6 +57,14 @@ static int session_id_advertise(struct repository *r, struct strbuf *value)
return 1;
}
+static void session_id_receive(struct repository *r,
+ const char *client_sid)
+{
+ if (!client_sid)
+ client_sid = "";
+ trace2_data_string("transfer", NULL, "client-sid", client_sid);
+}
+
struct protocol_capability {
/*
* The name of the capability. The server uses this name when
@@ -60,34 +83,70 @@ struct protocol_capability {
/*
* Function called when a client requests the capability as a command.
- * The function will be provided the capabilities requested via 'keys'
- * as well as a struct packet_reader 'request' which the command should
+ * Will be provided a struct packet_reader 'request' which it should
* use to read the command specific part of the request. Every command
* MUST read until a flush packet is seen before sending a response.
*
* This field should be NULL for capabilities which are not commands.
*/
- int (*command)(struct repository *r,
- struct strvec *keys,
- struct packet_reader *request);
+ int (*command)(struct repository *r, struct packet_reader *request);
+
+ /*
+ * Function called when a client requests the capability as a
+ * non-command. This may be NULL if the capability does nothing.
+ *
+ * For a capability of the form "foo=bar", the value string points to
+ * the content after the "=" (i.e., "bar"). For simple capabilities
+ * (just "foo"), it is NULL.
+ */
+ void (*receive)(struct repository *r, const char *value);
};
static struct protocol_capability capabilities[] = {
- { "agent", agent_advertise, NULL },
- { "ls-refs", ls_refs_advertise, ls_refs },
- { "fetch", upload_pack_advertise, upload_pack_v2 },
- { "server-option", always_advertise, NULL },
- { "object-format", object_format_advertise, NULL },
- { "session-id", session_id_advertise, NULL },
- { "object-info", always_advertise, cap_object_info },
+ {
+ .name = "agent",
+ .advertise = agent_advertise,
+ },
+ {
+ .name = "ls-refs",
+ .advertise = ls_refs_advertise,
+ .command = ls_refs,
+ },
+ {
+ .name = "fetch",
+ .advertise = upload_pack_advertise,
+ .command = upload_pack_v2,
+ },
+ {
+ .name = "server-option",
+ .advertise = always_advertise,
+ },
+ {
+ .name = "object-format",
+ .advertise = object_format_advertise,
+ .receive = object_format_receive,
+ },
+ {
+ .name = "session-id",
+ .advertise = session_id_advertise,
+ .receive = session_id_receive,
+ },
+ {
+ .name = "object-info",
+ .advertise = always_advertise,
+ .command = cap_object_info,
+ },
};
-static void advertise_capabilities(void)
+void protocol_v2_advertise_capabilities(void)
{
struct strbuf capability = STRBUF_INIT;
struct strbuf value = STRBUF_INIT;
int i;
+ /* serve by default supports v2 */
+ packet_write_fmt(1, "version 2\n");
+
for (i = 0; i < ARRAY_SIZE(capabilities); i++) {
struct protocol_capability *c = &capabilities[i];
@@ -112,7 +171,7 @@ static void advertise_capabilities(void)
strbuf_release(&value);
}
-static struct protocol_capability *get_capability(const char *key)
+static struct protocol_capability *get_capability(const char *key, const char **value)
{
int i;
@@ -122,31 +181,46 @@ static struct protocol_capability *get_capability(const char *key)
for (i = 0; i < ARRAY_SIZE(capabilities); i++) {
struct protocol_capability *c = &capabilities[i];
const char *out;
- if (skip_prefix(key, c->name, &out) && (!*out || *out == '='))
+ if (!skip_prefix(key, c->name, &out))
+ continue;
+ if (!*out) {
+ *value = NULL;
return c;
+ }
+ if (*out++ == '=') {
+ *value = out;
+ return c;
+ }
}
return NULL;
}
-static int is_valid_capability(const char *key)
+static int receive_client_capability(const char *key)
{
- const struct protocol_capability *c = get_capability(key);
+ const char *value;
+ const struct protocol_capability *c = get_capability(key, &value);
- return c && c->advertise(the_repository, NULL);
+ if (!c || c->command || !c->advertise(the_repository, NULL))
+ return 0;
+
+ if (c->receive)
+ c->receive(the_repository, value);
+ return 1;
}
-static int is_command(const char *key, struct protocol_capability **command)
+static int parse_command(const char *key, struct protocol_capability **command)
{
const char *out;
if (skip_prefix(key, "command=", &out)) {
- struct protocol_capability *cmd = get_capability(out);
+ const char *value;
+ struct protocol_capability *cmd = get_capability(out, &value);
if (*command)
die("command '%s' requested after already requesting command '%s'",
out, (*command)->name);
- if (!cmd || !cmd->advertise(the_repository, NULL) || !cmd->command)
+ if (!cmd || !cmd->advertise(the_repository, NULL) || !cmd->command || value)
die("invalid command '%s'", out);
*command = cmd;
@@ -156,42 +230,6 @@ static int is_command(const char *key, struct protocol_capability **command)
return 0;
}
-int has_capability(const struct strvec *keys, const char *capability,
- const char **value)
-{
- int i;
- for (i = 0; i < keys->nr; i++) {
- const char *out;
- if (skip_prefix(keys->v[i], capability, &out) &&
- (!*out || *out == '=')) {
- if (value) {
- if (*out == '=')
- out++;
- *value = out;
- }
- return 1;
- }
- }
-
- return 0;
-}
-
-static void check_algorithm(struct repository *r, struct strvec *keys)
-{
- int client = GIT_HASH_SHA1, server = hash_algo_by_ptr(r->hash_algo);
- const char *algo_name;
-
- if (has_capability(keys, "object-format", &algo_name)) {
- client = hash_algo_by_name(algo_name);
- if (client == GIT_HASH_UNKNOWN)
- die("unknown object format '%s'", algo_name);
- }
-
- if (client != server)
- die("mismatched object format: server %s; client %s\n",
- r->hash_algo->name, hash_algos[client].name);
-}
-
enum request_state {
PROCESS_REQUEST_KEYS,
PROCESS_REQUEST_DONE,
@@ -201,9 +239,8 @@ static int process_request(void)
{
enum request_state state = PROCESS_REQUEST_KEYS;
struct packet_reader reader;
- struct strvec keys = STRVEC_INIT;
+ int seen_capability_or_command = 0;
struct protocol_capability *command = NULL;
- const char *client_sid;
packet_reader_init(&reader, 0, NULL, 0,
PACKET_READ_CHOMP_NEWLINE |
@@ -223,10 +260,9 @@ static int process_request(void)
case PACKET_READ_EOF:
BUG("Should have already died when seeing EOF");
case PACKET_READ_NORMAL:
- /* collect request; a sequence of keys and values */
- if (is_command(reader.line, &command) ||
- is_valid_capability(reader.line))
- strvec_push(&keys, reader.line);
+ if (parse_command(reader.line, &command) ||
+ receive_client_capability(reader.line))
+ seen_capability_or_command = 1;
else
die("unknown capability '%s'", reader.line);
@@ -238,7 +274,7 @@ static int process_request(void)
* If no command and no keys were given then the client
* wanted to terminate the connection.
*/
- if (!keys.nr)
+ if (!seen_capability_or_command)
return 1;
/*
@@ -265,40 +301,26 @@ static int process_request(void)
if (!command)
die("no command requested");
- check_algorithm(the_repository, &keys);
-
- if (has_capability(&keys, "session-id", &client_sid))
- trace2_data_string("transfer", NULL, "client-sid", client_sid);
+ if (client_hash_algo != hash_algo_by_ptr(the_repository->hash_algo))
+ die("mismatched object format: server %s; client %s\n",
+ the_repository->hash_algo->name,
+ hash_algos[client_hash_algo].name);
- command->command(the_repository, &keys, &reader);
+ command->command(the_repository, &reader);
- strvec_clear(&keys);
return 0;
}
-/* Main serve loop for protocol version 2 */
-void serve(struct serve_options *options)
+void protocol_v2_serve_loop(int stateless_rpc)
{
- git_config_get_bool("transfer.advertisesid", &advertise_sid);
-
- if (options->advertise_capabilities || !options->stateless_rpc) {
- /* serve by default supports v2 */
- packet_write_fmt(1, "version 2\n");
-
- advertise_capabilities();
- /*
- * If only the list of capabilities was requested exit
- * immediately after advertising capabilities
- */
- if (options->advertise_capabilities)
- return;
- }
+ if (!stateless_rpc)
+ protocol_v2_advertise_capabilities();
/*
* If stateless-rpc was requested then exit after
* a single request/response exchange
*/
- if (options->stateless_rpc) {
+ if (stateless_rpc) {
process_request();
} else {
for (;;)
diff --git a/serve.h b/serve.h
index fc2683e24d..f946cf904a 100644
--- a/serve.h
+++ b/serve.h
@@ -1,15 +1,7 @@
#ifndef SERVE_H
#define SERVE_H
-struct strvec;
-int has_capability(const struct strvec *keys, const char *capability,
- const char **value);
-
-struct serve_options {
- unsigned advertise_capabilities;
- unsigned stateless_rpc;
-};
-#define SERVE_OPTIONS_INIT { 0 }
-void serve(struct serve_options *options);
+void protocol_v2_advertise_capabilities(void);
+void protocol_v2_serve_loop(int stateless_rpc);
#endif /* SERVE_H */
diff --git a/setup.c b/setup.c
index eb9367ca5c..347d7181ae 100644
--- a/setup.c
+++ b/setup.c
@@ -1423,11 +1423,9 @@ const char *resolve_gitdir_gently(const char *suspect, int *return_error_code)
/* if any standard file descriptor is missing open it to /dev/null */
void sanitize_stdfds(void)
{
- int fd = open("/dev/null", O_RDWR, 0);
- while (fd != -1 && fd < 2)
- fd = dup(fd);
- if (fd == -1)
- die_errno(_("open /dev/null or dup failed"));
+ int fd = xopen("/dev/null", O_RDWR);
+ while (fd < 2)
+ fd = xdup(fd);
if (fd > 2)
close(fd);
}
diff --git a/shallow.h b/shallow.h
index 5b4a96dcd6..aba6ff5829 100644
--- a/shallow.h
+++ b/shallow.h
@@ -23,7 +23,9 @@ int is_repository_shallow(struct repository *r);
struct shallow_lock {
struct lock_file lock;
};
-#define SHALLOW_LOCK_INIT { LOCK_INIT }
+#define SHALLOW_LOCK_INIT { \
+ .lock = LOCK_INIT, \
+}
/* commit $GIT_DIR/shallow and reset stat-validity checks */
int commit_shallow_file(struct repository *r, struct shallow_lock *lk);
diff --git a/simple-ipc.h b/simple-ipc.h
index 2c48a5ee00..a849d9f841 100644
--- a/simple-ipc.h
+++ b/simple-ipc.h
@@ -5,13 +5,6 @@
* See Documentation/technical/api-simple-ipc.txt
*/
-#ifdef SUPPORTS_SIMPLE_IPC
-#include "pkt-line.h"
-
-/*
- * Simple IPC Client Side API.
- */
-
enum ipc_active_state {
/*
* The pipe/socket exists and the daemon is waiting for connections.
@@ -43,6 +36,13 @@ enum ipc_active_state {
IPC_STATE__OTHER_ERROR,
};
+#ifdef SUPPORTS_SIMPLE_IPC
+#include "pkt-line.h"
+
+/*
+ * Simple IPC Client Side API.
+ */
+
struct ipc_client_connect_options {
/*
* Spin under timeout if the server is running but can't
@@ -65,11 +65,7 @@ struct ipc_client_connect_options {
unsigned int uds_disallow_chdir:1;
};
-#define IPC_CLIENT_CONNECT_OPTIONS_INIT { \
- .wait_if_busy = 0, \
- .wait_if_not_found = 0, \
- .uds_disallow_chdir = 0, \
-}
+#define IPC_CLIENT_CONNECT_OPTIONS_INIT { 0 }
/*
* Determine if a server is listening on this named pipe or socket using
@@ -107,7 +103,8 @@ void ipc_client_close_connection(struct ipc_client_connection *connection);
*/
int ipc_client_send_command_to_connection(
struct ipc_client_connection *connection,
- const char *message, struct strbuf *answer);
+ const char *message, size_t message_len,
+ struct strbuf *answer);
/*
* Used by the client to synchronously connect and send and receive a
@@ -119,7 +116,8 @@ int ipc_client_send_command_to_connection(
*/
int ipc_client_send_command(const char *path,
const struct ipc_client_connect_options *options,
- const char *message, struct strbuf *answer);
+ const char *message, size_t message_len,
+ struct strbuf *answer);
/*
* Simple IPC Server Side API.
@@ -144,6 +142,7 @@ typedef int (ipc_server_reply_cb)(struct ipc_server_reply_data *,
*/
typedef int (ipc_server_application_cb)(void *application_data,
const char *request,
+ size_t request_len,
ipc_server_reply_cb *reply_cb,
struct ipc_server_reply_data *reply_data);
diff --git a/sparse-index.c b/sparse-index.c
index c6b4feec41..7b7ff79e04 100644
--- a/sparse-index.c
+++ b/sparse-index.c
@@ -33,19 +33,14 @@ static int convert_to_sparse_rec(struct index_state *istate,
{
int i, can_convert = 1;
int start_converted = num_converted;
- enum pattern_match_result match;
- int dtype = DT_UNKNOWN;
struct strbuf child_path = STRBUF_INIT;
- struct pattern_list *pl = istate->sparse_checkout_patterns;
/*
* Is the current path outside of the sparse cone?
* Then check if the region can be replaced by a sparse
* directory entry (everything is sparse and merged).
*/
- match = path_matches_pattern_list(ct_path, ct_pathlen,
- NULL, &dtype, pl, istate);
- if (match != NOT_MATCHED)
+ if (path_in_sparse_checkout(ct_path, istate))
can_convert = 0;
for (i = start; can_convert && i < end; i++) {
@@ -127,41 +122,51 @@ static int index_has_unmerged_entries(struct index_state *istate)
return 0;
}
-int convert_to_sparse(struct index_state *istate)
+int convert_to_sparse(struct index_state *istate, int flags)
{
int test_env;
- if (istate->split_index || istate->sparse_index ||
+ if (istate->sparse_index || !istate->cache_nr ||
!core_apply_sparse_checkout || !core_sparse_checkout_cone)
return 0;
if (!istate->repo)
istate->repo = the_repository;
- /*
- * The GIT_TEST_SPARSE_INDEX environment variable triggers the
- * index.sparse config variable to be on.
- */
- test_env = git_env_bool("GIT_TEST_SPARSE_INDEX", -1);
- if (test_env >= 0)
- set_sparse_index_config(istate->repo, test_env);
-
- /*
- * Only convert to sparse if index.sparse is set.
- */
- prepare_repo_settings(istate->repo);
- if (!istate->repo->settings.sparse_index)
- return 0;
+ if (!(flags & SPARSE_INDEX_MEMORY_ONLY)) {
+ /*
+ * The sparse index is not (yet) integrated with a split index.
+ */
+ if (istate->split_index)
+ return 0;
+ /*
+ * The GIT_TEST_SPARSE_INDEX environment variable triggers the
+ * index.sparse config variable to be on.
+ */
+ test_env = git_env_bool("GIT_TEST_SPARSE_INDEX", -1);
+ if (test_env >= 0)
+ set_sparse_index_config(istate->repo, test_env);
- if (!istate->sparse_checkout_patterns) {
- istate->sparse_checkout_patterns = xcalloc(1, sizeof(struct pattern_list));
- if (get_sparse_checkout_patterns(istate->sparse_checkout_patterns) < 0)
+ /*
+ * Only convert to sparse if index.sparse is set.
+ */
+ prepare_repo_settings(istate->repo);
+ if (!istate->repo->settings.sparse_index)
return 0;
}
- if (!istate->sparse_checkout_patterns->use_cone_patterns) {
- warning(_("attempting to use sparse-index without cone mode"));
- return -1;
- }
+ if (init_sparse_checkout_patterns(istate))
+ return 0;
+
+ /*
+ * We need cone-mode patterns to use sparse-index. If a user edits
+ * their sparse-checkout file manually, then we can detect during
+ * parsing that they are not actually using cone-mode patterns and
+ * hence we need to abort this conversion _without error_. Warnings
+ * already exist in the pattern parsing to inform the user of their
+ * bad patterns.
+ */
+ if (!istate->sparse_checkout_patterns->use_cone_patterns)
+ return 0;
/*
* NEEDSWORK: If we have unmerged entries, then stay full.
@@ -172,10 +177,15 @@ int convert_to_sparse(struct index_state *istate)
/* Clear and recompute the cache-tree */
cache_tree_free(&istate->cache_tree);
- if (cache_tree_update(istate, 0)) {
- warning(_("unable to update cache-tree, staying full"));
- return -1;
- }
+ /*
+ * Silently return if there is a problem with the cache tree update,
+ * which might just be due to a conflict state in some entry.
+ *
+ * This might create new tree objects, so be sure to use
+ * WRITE_TREE_MISSING_OK.
+ */
+ if (cache_tree_update(istate, WRITE_TREE_MISSING_OK))
+ return 0;
remove_fsmonitor(istate);
@@ -283,6 +293,7 @@ void ensure_full_index(struct index_state *istate)
/* Copy back into original index. */
memcpy(&istate->name_hash, &full->name_hash, sizeof(full->name_hash));
+ memcpy(&istate->dir_hash, &full->dir_hash, sizeof(full->dir_hash));
istate->sparse_index = 0;
free(istate->cache);
istate->cache = full->cache;
diff --git a/sparse-index.h b/sparse-index.h
index 1115a0d7dd..9f3d7bc7fa 100644
--- a/sparse-index.h
+++ b/sparse-index.h
@@ -2,7 +2,8 @@
#define SPARSE_INDEX_H__
struct index_state;
-int convert_to_sparse(struct index_state *istate);
+#define SPARSE_INDEX_MEMORY_ONLY (1 << 0)
+int convert_to_sparse(struct index_state *istate, int flags);
/*
* Some places in the codebase expect to search for a specific path.
diff --git a/strbuf.h b/strbuf.h
index 5b1113abf8..3b36bbc49f 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -70,7 +70,7 @@ struct strbuf {
};
extern char strbuf_slopbuf[];
-#define STRBUF_INIT { .alloc = 0, .len = 0, .buf = strbuf_slopbuf }
+#define STRBUF_INIT { .buf = strbuf_slopbuf }
/*
* Predeclare this here, since cache.h includes this file before it defines the
diff --git a/string-list.c b/string-list.c
index 43576ad126..549fc416d6 100644
--- a/string-list.c
+++ b/string-list.c
@@ -13,14 +13,6 @@ void string_list_init_dup(struct string_list *list)
memcpy(list, &blank, sizeof(*list));
}
-void string_list_init(struct string_list *list, int strdup_strings)
-{
- if (strdup_strings)
- string_list_init_dup(list);
- else
- string_list_init_nodup(list);
-}
-
/* if there is no exact match, point to the index where the entry could be
* inserted */
static int get_entry_index(const struct string_list *list, const char *string,
diff --git a/string-list.h b/string-list.h
index 0d6b469239..267d6e5769 100644
--- a/string-list.h
+++ b/string-list.h
@@ -104,11 +104,6 @@ struct string_list {
void string_list_init_nodup(struct string_list *list);
void string_list_init_dup(struct string_list *list);
-/**
- * TODO remove: For compatibility with any in-flight older API users
- */
-void string_list_init(struct string_list *list, int strdup_strings);
-
/** Callback function type for for_each_string_list */
typedef int (*string_list_each_func_t)(struct string_list_item *, void *);
diff --git a/strvec.h b/strvec.h
index fdcad75b45..9f55c8766b 100644
--- a/strvec.h
+++ b/strvec.h
@@ -29,11 +29,13 @@ extern const char *empty_strvec[];
*/
struct strvec {
const char **v;
- int nr;
- int alloc;
+ size_t nr;
+ size_t alloc;
};
-#define STRVEC_INIT { empty_strvec, 0, 0 }
+#define STRVEC_INIT { \
+ .v = empty_strvec, \
+}
/**
* Initialize an array. This is no different than assigning from
diff --git a/submodule-config.c b/submodule-config.c
index 2026120fb3..f95344028b 100644
--- a/submodule-config.c
+++ b/submodule-config.c
@@ -649,9 +649,10 @@ static void config_from_gitmodules(config_fn_t fn, struct repository *repo, void
config_source.file = file;
} else if (repo_get_oid(repo, GITMODULES_INDEX, &oid) >= 0 ||
repo_get_oid(repo, GITMODULES_HEAD, &oid) >= 0) {
+ config_source.repo = repo;
config_source.blob = oidstr = xstrdup(oid_to_hex(&oid));
if (repo != the_repository)
- add_to_alternates_memory(repo->objects->odb->path);
+ add_submodule_odb_by_path(repo->objects->odb->path);
} else {
goto out;
}
@@ -702,7 +703,7 @@ void gitmodules_config_oid(const struct object_id *commit_oid)
if (gitmodule_oid_from_commit(commit_oid, &oid, &rev)) {
git_config_from_blob_oid(gitmodules_cb, rev.buf,
- &oid, the_repository);
+ the_repository, &oid, the_repository);
}
strbuf_release(&rev);
diff --git a/submodule-config.h b/submodule-config.h
index c11e22cf50..65875b94ea 100644
--- a/submodule-config.h
+++ b/submodule-config.h
@@ -45,10 +45,6 @@ struct submodule {
struct object_id gitmodules_oid;
int recommend_shallow;
};
-
-#define SUBMODULE_INIT { NULL, NULL, NULL, RECURSE_SUBMODULES_NONE, \
- NULL, NULL, SUBMODULE_UPDATE_STRATEGY_INIT, { { 0 } }, -1 };
-
struct submodule_cache;
struct repository;
diff --git a/submodule.c b/submodule.c
index 8e611fe1db..f3c99634a9 100644
--- a/submodule.c
+++ b/submodule.c
@@ -165,6 +165,8 @@ void stage_updated_gitmodules(struct index_state *istate)
die(_("staging updated .gitmodules failed"));
}
+static struct string_list added_submodule_odb_paths = STRING_LIST_INIT_NODUP;
+
/* TODO: remove this function, use repo_submodule_init instead. */
int add_submodule_odb(const char *path)
{
@@ -178,12 +180,33 @@ int add_submodule_odb(const char *path)
ret = -1;
goto done;
}
- add_to_alternates_memory(objects_directory.buf);
+ string_list_insert(&added_submodule_odb_paths,
+ strbuf_detach(&objects_directory, NULL));
done:
strbuf_release(&objects_directory);
return ret;
}
+void add_submodule_odb_by_path(const char *path)
+{
+ string_list_insert(&added_submodule_odb_paths, xstrdup(path));
+}
+
+int register_all_submodule_odb_as_alternates(void)
+{
+ int i;
+ int ret = added_submodule_odb_paths.nr;
+
+ for (i = 0; i < added_submodule_odb_paths.nr; i++)
+ add_to_alternates_memory(added_submodule_odb_paths.items[i].string);
+ if (ret) {
+ string_list_clear(&added_submodule_odb_paths, 0);
+ if (git_env_bool("GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB", 0))
+ BUG("register_all_submodule_odb_as_alternates() called");
+ }
+ return ret;
+}
+
void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
const char *path)
{
@@ -237,6 +260,11 @@ int option_parse_recurse_submodules_worktree_updater(const struct option *opt,
/*
* Determine if a submodule has been initialized at a given 'path'
*/
+/*
+ * NEEDSWORK: Emit a warning if submodule.active exists, but is valueless,
+ * ie, the config looks like: "[submodule] active\n".
+ * Since that is an invalid pathspec, we should inform the user.
+ */
int is_submodule_active(struct repository *repo, const char *path)
{
int ret = 0;
@@ -497,9 +525,6 @@ static void prepare_submodule_repo_env_in_gitdir(struct strvec *out)
/*
* Initialize a repository struct for a submodule based on the provided 'path'.
*
- * Unlike repo_submodule_init, this tolerates submodules not present
- * in .gitmodules. This function exists only to preserve historical behavior,
- *
* Returns the repository struct on success,
* NULL when the submodule is not present.
*/
@@ -697,8 +722,20 @@ void show_submodule_inline_diff(struct diff_options *o, const char *path,
strvec_push(&cp.args, oid_to_hex(new_oid));
prepare_submodule_repo_env(&cp.env_array);
- if (start_command(&cp))
+
+ if (!is_directory(path)) {
+ /* fall back to absorbed git dir, if any */
+ if (!sub)
+ goto done;
+ cp.dir = sub->gitdir;
+ strvec_push(&cp.env_array, GIT_DIR_ENVIRONMENT "=.");
+ strvec_push(&cp.env_array, GIT_WORK_TREE_ENVIRONMENT "=.");
+ }
+
+ if (start_command(&cp)) {
diff_emit_submodule_error(o, "(diff failed)\n");
+ goto done;
+ }
while (strbuf_getwholeline_fd(&sb, cp.out, '\n') != EOF)
diff_emit_submodule_pipethrough(o, sb.buf, sb.len);
@@ -1281,9 +1318,11 @@ struct submodule_parallel_fetch {
struct strbuf submodules_with_errors;
};
-#define SPF_INIT {0, STRVEC_INIT, NULL, NULL, 0, 0, 0, 0, \
- STRING_LIST_INIT_DUP, \
- NULL, 0, 0, STRBUF_INIT}
+#define SPF_INIT { \
+ .args = STRVEC_INIT, \
+ .changed_submodule_names = STRING_LIST_INIT_DUP, \
+ .submodules_with_errors = STRBUF_INIT, \
+}
static int get_fetch_recurse_config(const struct submodule *submodule,
struct submodule_parallel_fetch *spf)
@@ -1381,24 +1420,13 @@ static void fetch_task_release(struct fetch_task *p)
}
static struct repository *get_submodule_repo_for(struct repository *r,
- const struct submodule *sub)
+ const char *path)
{
struct repository *ret = xmalloc(sizeof(*ret));
- if (repo_submodule_init(ret, r, sub)) {
- /*
- * No entry in .gitmodules? Technically not a submodule,
- * but historically we supported repositories that happen to be
- * in-place where a gitlink is. Keep supporting them.
- */
- struct strbuf gitdir = STRBUF_INIT;
- strbuf_repo_worktree_path(&gitdir, r, "%s/.git", sub->path);
- if (repo_init(ret, gitdir.buf, NULL)) {
- strbuf_release(&gitdir);
- free(ret);
- return NULL;
- }
- strbuf_release(&gitdir);
+ if (repo_submodule_init(ret, r, path, null_oid())) {
+ free(ret);
+ return NULL;
}
return ret;
@@ -1440,7 +1468,7 @@ static int get_next_submodule(struct child_process *cp,
continue;
}
- task->repo = get_submodule_repo_for(spf->r, task->sub);
+ task->repo = get_submodule_repo_for(spf->r, task->sub->path);
if (task->repo) {
struct strbuf submodule_prefix = STRBUF_INIT;
child_process_init(cp);
@@ -1819,14 +1847,16 @@ out:
void submodule_unset_core_worktree(const struct submodule *sub)
{
- char *config_path = xstrfmt("%s/modules/%s/config",
- get_git_dir(), sub->name);
+ struct strbuf config_path = STRBUF_INIT;
+
+ submodule_name_to_gitdir(&config_path, the_repository, sub->name);
+ strbuf_addstr(&config_path, "/config");
- if (git_config_set_in_file_gently(config_path, "core.worktree", NULL))
+ if (git_config_set_in_file_gently(config_path.buf, "core.worktree", NULL))
warning(_("Could not unset core.worktree setting in submodule '%s'"),
sub->path);
- free(config_path);
+ strbuf_release(&config_path);
}
static const char *get_super_prefix_or_empty(void)
@@ -1866,6 +1896,7 @@ static void submodule_reset_index(const char *path)
strvec_pushf(&cp.args, "--super-prefix=%s%s/",
get_super_prefix_or_empty(), path);
+ /* TODO: determine if this might overwright untracked files */
strvec_pushl(&cp.args, "read-tree", "-u", "--reset", NULL);
strvec_push(&cp.args, empty_tree_oid_hex());
@@ -1922,20 +1953,22 @@ int submodule_move_head(const char *path,
absorb_git_dir_into_superproject(path,
ABSORB_GITDIR_RECURSE_SUBMODULES);
} else {
- char *gitdir = xstrfmt("%s/modules/%s",
- get_git_dir(), sub->name);
- connect_work_tree_and_git_dir(path, gitdir, 0);
- free(gitdir);
+ struct strbuf gitdir = STRBUF_INIT;
+ submodule_name_to_gitdir(&gitdir, the_repository,
+ sub->name);
+ connect_work_tree_and_git_dir(path, gitdir.buf, 0);
+ strbuf_release(&gitdir);
/* make sure the index is clean as well */
submodule_reset_index(path);
}
if (old_head && (flags & SUBMODULE_MOVE_HEAD_FORCE)) {
- char *gitdir = xstrfmt("%s/modules/%s",
- get_git_dir(), sub->name);
- connect_work_tree_and_git_dir(path, gitdir, 1);
- free(gitdir);
+ struct strbuf gitdir = STRBUF_INIT;
+ submodule_name_to_gitdir(&gitdir, the_repository,
+ sub->name);
+ connect_work_tree_and_git_dir(path, gitdir.buf, 1);
+ strbuf_release(&gitdir);
}
}
@@ -2050,7 +2083,7 @@ int validate_submodule_git_dir(char *git_dir, const char *submodule_name)
static void relocate_single_git_dir_into_superproject(const char *path)
{
char *old_git_dir = NULL, *real_old_git_dir = NULL, *real_new_git_dir = NULL;
- char *new_git_dir;
+ struct strbuf new_gitdir = STRBUF_INIT;
const struct submodule *sub;
if (submodule_uses_worktrees(path))
@@ -2068,14 +2101,13 @@ static void relocate_single_git_dir_into_superproject(const char *path)
if (!sub)
die(_("could not lookup name for submodule '%s'"), path);
- new_git_dir = git_pathdup("modules/%s", sub->name);
- if (validate_submodule_git_dir(new_git_dir, sub->name) < 0)
+ submodule_name_to_gitdir(&new_gitdir, the_repository, sub->name);
+ if (validate_submodule_git_dir(new_gitdir.buf, sub->name) < 0)
die(_("refusing to move '%s' into an existing git dir"),
real_old_git_dir);
- if (safe_create_leading_directories_const(new_git_dir) < 0)
- die(_("could not create directory '%s'"), new_git_dir);
- real_new_git_dir = real_pathdup(new_git_dir, 1);
- free(new_git_dir);
+ if (safe_create_leading_directories_const(new_gitdir.buf) < 0)
+ die(_("could not create directory '%s'"), new_gitdir.buf);
+ real_new_git_dir = real_pathdup(new_gitdir.buf, 1);
fprintf(stderr, _("Migrating git directory of '%s%s' from\n'%s' to\n'%s'\n"),
get_super_prefix_or_empty(), path,
@@ -2086,6 +2118,7 @@ static void relocate_single_git_dir_into_superproject(const char *path)
free(old_git_dir);
free(real_old_git_dir);
free(real_new_git_dir);
+ strbuf_release(&new_gitdir);
}
/*
@@ -2105,6 +2138,7 @@ void absorb_git_dir_into_superproject(const char *path,
/* Not populated? */
if (!sub_git_dir) {
const struct submodule *sub;
+ struct strbuf sub_gitdir = STRBUF_INIT;
if (err_code == READ_GITFILE_ERR_STAT_FAILED) {
/* unpopulated as expected */
@@ -2126,8 +2160,9 @@ void absorb_git_dir_into_superproject(const char *path,
sub = submodule_from_path(the_repository, null_oid(), path);
if (!sub)
die(_("could not lookup name for submodule '%s'"), path);
- connect_work_tree_and_git_dir(path,
- git_path("modules/%s", sub->name), 0);
+ submodule_name_to_gitdir(&sub_gitdir, the_repository, sub->name);
+ connect_work_tree_and_git_dir(path, sub_gitdir.buf, 0);
+ strbuf_release(&sub_gitdir);
} else {
/* Is it already absorbed into the superprojects git dir? */
char *real_sub_git_dir = real_pathdup(sub_git_dir, 1);
@@ -2278,9 +2313,36 @@ int submodule_to_gitdir(struct strbuf *buf, const char *submodule)
goto cleanup;
}
strbuf_reset(buf);
- strbuf_git_path(buf, "%s/%s", "modules", sub->name);
+ submodule_name_to_gitdir(buf, the_repository, sub->name);
}
cleanup:
return ret;
}
+
+void submodule_name_to_gitdir(struct strbuf *buf, struct repository *r,
+ const char *submodule_name)
+{
+ /*
+ * NEEDSWORK: The current way of mapping a submodule's name to
+ * its location in .git/modules/ has problems with some naming
+ * schemes. For example, if a submodule is named "foo" and
+ * another is named "foo/bar" (whether present in the same
+ * superproject commit or not - the problem will arise if both
+ * superproject commits have been checked out at any point in
+ * time), or if two submodule names only have different cases in
+ * a case-insensitive filesystem.
+ *
+ * There are several solutions, including encoding the path in
+ * some way, introducing a submodule.<name>.gitdir config in
+ * .git/config (not .gitmodules) that allows overriding what the
+ * gitdir of a submodule would be (and teach Git, upon noticing
+ * a clash, to automatically determine a non-clashing name and
+ * to write such a config), or introducing a
+ * submodule.<name>.gitdir config in .gitmodules that repo
+ * administrators can explicitly set. Nothing has been decided,
+ * so for now, just append the name at the end of the path.
+ */
+ strbuf_repo_git_path(buf, r, "modules/");
+ strbuf_addstr(buf, submodule_name);
+}
diff --git a/submodule.h b/submodule.h
index 84640c49c1..6bd2c99fd9 100644
--- a/submodule.h
+++ b/submodule.h
@@ -37,7 +37,9 @@ struct submodule_update_strategy {
enum submodule_update_type type;
const char *command;
};
-#define SUBMODULE_UPDATE_STRATEGY_INIT {SM_UPDATE_UNSPECIFIED, NULL}
+#define SUBMODULE_UPDATE_STRATEGY_INIT { \
+ .type = SM_UPDATE_UNSPECIFIED, \
+}
int is_gitmodules_unmerged(struct index_state *istate);
int is_writing_gitmodules_ok(void);
@@ -97,7 +99,15 @@ int submodule_uses_gitfile(const char *path);
#define SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED (1<<2)
int bad_to_remove_submodule(const char *path, unsigned flags);
+/*
+ * Call add_submodule_odb() to add the submodule at the given path to a list.
+ * When register_all_submodule_odb_as_alternates() is called, the object stores
+ * of all submodules in that list will be added as alternates in
+ * the_repository.
+ */
int add_submodule_odb(const char *path);
+void add_submodule_odb_by_path(const char *path);
+int register_all_submodule_odb_as_alternates(void);
/*
* Checks if there are submodule changes in a..b. If a is the null OID,
@@ -125,6 +135,13 @@ int push_unpushed_submodules(struct repository *r,
int submodule_to_gitdir(struct strbuf *buf, const char *submodule);
/*
+ * Given a submodule name, create a path to where the submodule's gitdir lives
+ * inside of the provided repository's 'modules' directory.
+ */
+void submodule_name_to_gitdir(struct strbuf *buf, struct repository *r,
+ const char *submodule_name);
+
+/*
* Make sure that no submodule's git dir is nested in a sibling submodule's.
*/
int validate_submodule_git_dir(char *git_dir, const char *submodule_name);
diff --git a/t/README b/t/README
index 12014aa988..b92155a822 100644
--- a/t/README
+++ b/t/README
@@ -366,6 +366,13 @@ excluded as so much relies on it, but this might change in the future.
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_PASSING_SANITIZE_LEAK=<boolean> when compiled with
+SANITIZE=leak will run only those tests that have whitelisted
+themselves as passing with no memory leaks. Tests can be whitelisted
+by setting "TEST_PASSES_SANITIZE_LEAK=true" before sourcing
+"test-lib.sh" itself at the top of the test script. This test mode is
+used by the "linux-leaks" CI target.
+
GIT_TEST_PROTOCOL_VERSION=<n>, when set, makes 'protocol.version'
default to n.
@@ -452,6 +459,16 @@ GIT_TEST_CHECKOUT_WORKERS=<n> overrides the 'checkout.workers' setting
to <n> and 'checkout.thresholdForParallelism' to 0, forcing the
execution of the parallel-checkout code.
+GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=<boolean>, when true, makes
+registering submodule ODBs as alternates a fatal action. Support for
+this environment variable can be removed once the migration to
+explicitly providing repositories when accessing submodule objects is
+complete (in which case we might want to replace this with a trace2
+call so that users can make it visible if accessing submodule objects
+without an explicit repository still happens) or needs to be abandoned
+for whatever reason (in which case the migrated codepaths still retain
+their performance benefits).
+
Naming Tests
------------
@@ -757,7 +774,8 @@ Test harness library
--------------------
There are a handful helper functions defined in the test harness
-library for your script to use.
+library for your script to use. Some of them are listed below;
+see test-lib-functions.sh for the full list and their options.
- test_expect_success [<prereq>] <message> <script>
@@ -803,10 +821,12 @@ library for your script to use.
argument. This is primarily meant for use during the
development of a new test script.
- - debug <git-command>
+ - debug [options] <git-command>
Run a git command inside a debugger. This is primarily meant for
- use when debugging a failing test script.
+ use when debugging a failing test script. With '-t', use your
+ original TERM instead of test-lib.sh's "dumb", so that your
+ debugger interface has colors.
- test_done
@@ -993,7 +1013,7 @@ library for your script to use.
EOF
- - test_pause
+ - test_pause [options]
This command is useful for writing and debugging tests and must be
removed before submitting. It halts the execution of the test and
diff --git a/t/helper/test-bitmap.c b/t/helper/test-bitmap.c
index 134a1e9d76..ff35f5999b 100644
--- a/t/helper/test-bitmap.c
+++ b/t/helper/test-bitmap.c
@@ -7,6 +7,11 @@ static int bitmap_list_commits(void)
return test_bitmap_commits(the_repository);
}
+static int bitmap_dump_hashes(void)
+{
+ return test_bitmap_hashes(the_repository);
+}
+
int cmd__bitmap(int argc, const char **argv)
{
setup_git_directory();
@@ -16,9 +21,12 @@ int cmd__bitmap(int argc, const char **argv)
if (!strcmp(argv[1], "list-commits"))
return bitmap_list_commits();
+ if (!strcmp(argv[1], "dump-hashes"))
+ return bitmap_dump_hashes();
usage:
- usage("\ttest-tool bitmap list-commits");
+ usage("\ttest-tool bitmap list-commits\n"
+ "\ttest-tool bitmap dump-hashes");
return -1;
}
diff --git a/t/helper/test-dump-untracked-cache.c b/t/helper/test-dump-untracked-cache.c
index cf0f2c7228..99010614f6 100644
--- a/t/helper/test-dump-untracked-cache.c
+++ b/t/helper/test-dump-untracked-cache.c
@@ -45,8 +45,10 @@ int cmd__dump_untracked_cache(int ac, const char **av)
struct untracked_cache *uc;
struct strbuf base = STRBUF_INIT;
- /* Hack to avoid modifying the untracked cache when we read it */
- ignore_untracked_cache_config = 1;
+ /* Set core.untrackedCache=keep before setup_git_directory() */
+ xsetenv("GIT_CONFIG_COUNT", "1", 1);
+ xsetenv("GIT_CONFIG_KEY_0", "core.untrackedCache", 1);
+ xsetenv("GIT_CONFIG_VALUE_0", "keep", 1);
setup_git_directory();
if (read_cache() < 0)
diff --git a/t/helper/test-mergesort.c b/t/helper/test-mergesort.c
index c5cffaa4b7..ebf68f7de8 100644
--- a/t/helper/test-mergesort.c
+++ b/t/helper/test-mergesort.c
@@ -2,6 +2,12 @@
#include "cache.h"
#include "mergesort.h"
+static uint32_t minstd_rand(uint32_t *state)
+{
+ *state = (uint64_t)*state * 48271 % 2147483647;
+ return *state;
+}
+
struct line {
char *text;
struct line *next;
@@ -23,14 +29,12 @@ static int compare_strings(const void *a, const void *b)
return strcmp(x->text, y->text);
}
-int cmd__mergesort(int argc, const char **argv)
+static int sort_stdin(void)
{
struct line *line, *p = NULL, *lines = NULL;
struct strbuf sb = STRBUF_INIT;
- for (;;) {
- if (strbuf_getwholeline(&sb, stdin, '\n'))
- break;
+ while (!strbuf_getline(&sb, stdin)) {
line = xmalloc(sizeof(struct line));
line->text = strbuf_detach(&sb, NULL);
if (p) {
@@ -46,8 +50,362 @@ int cmd__mergesort(int argc, const char **argv)
lines = llist_mergesort(lines, get_next, set_next, compare_strings);
while (lines) {
- printf("%s", lines->text);
+ puts(lines->text);
lines = lines->next;
}
return 0;
}
+
+static void dist_sawtooth(int *arr, int n, int m)
+{
+ int i;
+ for (i = 0; i < n; i++)
+ arr[i] = i % m;
+}
+
+static void dist_rand(int *arr, int n, int m)
+{
+ int i;
+ uint32_t seed = 1;
+ for (i = 0; i < n; i++)
+ arr[i] = minstd_rand(&seed) % m;
+}
+
+static void dist_stagger(int *arr, int n, int m)
+{
+ int i;
+ for (i = 0; i < n; i++)
+ arr[i] = (i * m + i) % n;
+}
+
+static void dist_plateau(int *arr, int n, int m)
+{
+ int i;
+ for (i = 0; i < n; i++)
+ arr[i] = (i < m) ? i : m;
+}
+
+static void dist_shuffle(int *arr, int n, int m)
+{
+ int i, j, k;
+ uint32_t seed = 1;
+ for (i = j = 0, k = 1; i < n; i++)
+ arr[i] = minstd_rand(&seed) % m ? (j += 2) : (k += 2);
+}
+
+#define DIST(name) { #name, dist_##name }
+
+static struct dist {
+ const char *name;
+ void (*fn)(int *arr, int n, int m);
+} dist[] = {
+ DIST(sawtooth),
+ DIST(rand),
+ DIST(stagger),
+ DIST(plateau),
+ DIST(shuffle),
+};
+
+static const struct dist *get_dist_by_name(const char *name)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(dist); i++) {
+ if (!strcmp(dist[i].name, name))
+ return &dist[i];
+ }
+ return NULL;
+}
+
+static void mode_copy(int *arr, int n)
+{
+ /* nothing */
+}
+
+static void mode_reverse(int *arr, int n)
+{
+ int i, j;
+ for (i = 0, j = n - 1; i < j; i++, j--)
+ SWAP(arr[i], arr[j]);
+}
+
+static void mode_reverse_1st_half(int *arr, int n)
+{
+ mode_reverse(arr, n / 2);
+}
+
+static void mode_reverse_2nd_half(int *arr, int n)
+{
+ int half = n / 2;
+ mode_reverse(arr + half, n - half);
+}
+
+static int compare_ints(const void *av, const void *bv)
+{
+ const int *ap = av, *bp = bv;
+ int a = *ap, b = *bp;
+ return (a > b) - (a < b);
+}
+
+static void mode_sort(int *arr, int n)
+{
+ QSORT(arr, n, compare_ints);
+}
+
+static void mode_dither(int *arr, int n)
+{
+ int i;
+ for (i = 0; i < n; i++)
+ arr[i] += i % 5;
+}
+
+static void unriffle(int *arr, int n, int *tmp)
+{
+ int i, j;
+ COPY_ARRAY(tmp, arr, n);
+ for (i = j = 0; i < n; i += 2)
+ arr[j++] = tmp[i];
+ for (i = 1; i < n; i += 2)
+ arr[j++] = tmp[i];
+}
+
+static void unriffle_recursively(int *arr, int n, int *tmp)
+{
+ if (n > 1) {
+ int half = n / 2;
+ unriffle(arr, n, tmp);
+ unriffle_recursively(arr, half, tmp);
+ unriffle_recursively(arr + half, n - half, tmp);
+ }
+}
+
+static void mode_unriffle(int *arr, int n)
+{
+ int *tmp;
+ ALLOC_ARRAY(tmp, n);
+ unriffle_recursively(arr, n, tmp);
+ free(tmp);
+}
+
+static unsigned int prev_pow2(unsigned int n)
+{
+ unsigned int pow2 = 1;
+ while (pow2 * 2 < n)
+ pow2 *= 2;
+ return pow2;
+}
+
+static void unriffle_recursively_skewed(int *arr, int n, int *tmp)
+{
+ if (n > 1) {
+ int pow2 = prev_pow2(n);
+ int rest = n - pow2;
+ unriffle(arr + pow2 - rest, rest * 2, tmp);
+ unriffle_recursively_skewed(arr, pow2, tmp);
+ unriffle_recursively_skewed(arr + pow2, rest, tmp);
+ }
+}
+
+static void mode_unriffle_skewed(int *arr, int n)
+{
+ int *tmp;
+ ALLOC_ARRAY(tmp, n);
+ unriffle_recursively_skewed(arr, n, tmp);
+ free(tmp);
+}
+
+#define MODE(name) { #name, mode_##name }
+
+static struct mode {
+ const char *name;
+ void (*fn)(int *arr, int n);
+} mode[] = {
+ MODE(copy),
+ MODE(reverse),
+ MODE(reverse_1st_half),
+ MODE(reverse_2nd_half),
+ MODE(sort),
+ MODE(dither),
+ MODE(unriffle),
+ MODE(unriffle_skewed),
+};
+
+static const struct mode *get_mode_by_name(const char *name)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(mode); i++) {
+ if (!strcmp(mode[i].name, name))
+ return &mode[i];
+ }
+ return NULL;
+}
+
+static int generate(int argc, const char **argv)
+{
+ const struct dist *dist = NULL;
+ const struct mode *mode = NULL;
+ int i, n, m, *arr;
+
+ if (argc != 4)
+ return 1;
+
+ dist = get_dist_by_name(argv[0]);
+ mode = get_mode_by_name(argv[1]);
+ n = strtol(argv[2], NULL, 10);
+ m = strtol(argv[3], NULL, 10);
+ if (!dist || !mode)
+ return 1;
+
+ ALLOC_ARRAY(arr, n);
+ dist->fn(arr, n, m);
+ mode->fn(arr, n);
+ for (i = 0; i < n; i++)
+ printf("%08x\n", arr[i]);
+ free(arr);
+ return 0;
+}
+
+static struct stats {
+ int get_next, set_next, compare;
+} stats;
+
+struct number {
+ int value, rank;
+ struct number *next;
+};
+
+static void *get_next_number(const void *a)
+{
+ stats.get_next++;
+ return ((const struct number *)a)->next;
+}
+
+static void set_next_number(void *a, void *b)
+{
+ stats.set_next++;
+ ((struct number *)a)->next = b;
+}
+
+static int compare_numbers(const void *av, const void *bv)
+{
+ const struct number *an = av, *bn = bv;
+ int a = an->value, b = bn->value;
+ stats.compare++;
+ return (a > b) - (a < b);
+}
+
+static void clear_numbers(struct number *list)
+{
+ while (list) {
+ struct number *next = list->next;
+ free(list);
+ list = next;
+ }
+}
+
+static int test(const struct dist *dist, const struct mode *mode, int n, int m)
+{
+ int *arr;
+ size_t i;
+ struct number *curr, *list, **tail;
+ int is_sorted = 1;
+ int is_stable = 1;
+ const char *verdict;
+ int result = -1;
+
+ ALLOC_ARRAY(arr, n);
+ dist->fn(arr, n, m);
+ mode->fn(arr, n);
+ for (i = 0, tail = &list; i < n; i++) {
+ curr = xmalloc(sizeof(*curr));
+ curr->value = arr[i];
+ curr->rank = i;
+ *tail = curr;
+ tail = &curr->next;
+ }
+ *tail = NULL;
+
+ stats.get_next = stats.set_next = stats.compare = 0;
+ list = llist_mergesort(list, get_next_number, set_next_number,
+ compare_numbers);
+
+ QSORT(arr, n, compare_ints);
+ for (i = 0, curr = list; i < n && curr; i++, curr = curr->next) {
+ if (arr[i] != curr->value)
+ is_sorted = 0;
+ if (curr->next && curr->value == curr->next->value &&
+ curr->rank >= curr->next->rank)
+ is_stable = 0;
+ }
+ if (i < n) {
+ verdict = "too short";
+ } else if (curr) {
+ verdict = "too long";
+ } else if (!is_sorted) {
+ verdict = "not sorted";
+ } else if (!is_stable) {
+ verdict = "unstable";
+ } else {
+ verdict = "OK";
+ result = 0;
+ }
+
+ printf("%-9s %-16s %8d %8d %8d %8d %8d %s\n",
+ dist->name, mode->name, n, m, stats.get_next, stats.set_next,
+ stats.compare, verdict);
+
+ clear_numbers(list);
+ free(arr);
+
+ return result;
+}
+
+/*
+ * A version of the qsort certification program from "Engineering a Sort
+ * Function" by Bentley and McIlroy, Software—Practice and Experience,
+ * Volume 23, Issue 11, 1249–1265 (November 1993).
+ */
+static int run_tests(int argc, const char **argv)
+{
+ const char *argv_default[] = { "100", "1023", "1024", "1025" };
+ if (!argc)
+ return run_tests(ARRAY_SIZE(argv_default), argv_default);
+ printf("%-9s %-16s %8s %8s %8s %8s %8s %s\n",
+ "distribut", "mode", "n", "m", "get_next", "set_next",
+ "compare", "verdict");
+ while (argc--) {
+ int i, j, m, n = strtol(*argv++, NULL, 10);
+ for (i = 0; i < ARRAY_SIZE(dist); i++) {
+ for (j = 0; j < ARRAY_SIZE(mode); j++) {
+ for (m = 1; m < 2 * n; m *= 2) {
+ if (test(&dist[i], &mode[j], n, m))
+ return 1;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+int cmd__mergesort(int argc, const char **argv)
+{
+ int i;
+ const char *sep;
+
+ if (argc == 6 && !strcmp(argv[1], "generate"))
+ return generate(argc - 2, argv + 2);
+ if (argc == 2 && !strcmp(argv[1], "sort"))
+ return sort_stdin();
+ if (argc > 1 && !strcmp(argv[1], "test"))
+ return run_tests(argc - 2, argv + 2);
+ fprintf(stderr, "usage: test-tool mergesort generate <distribution> <mode> <n> <m>\n");
+ fprintf(stderr, " or: test-tool mergesort sort\n");
+ fprintf(stderr, " or: test-tool mergesort test [<n>...]\n");
+ fprintf(stderr, "\n");
+ for (i = 0, sep = "distributions: "; i < ARRAY_SIZE(dist); i++, sep = ", ")
+ fprintf(stderr, "%s%s", sep, dist[i].name);
+ fprintf(stderr, "\n");
+ for (i = 0, sep = "modes: "; i < ARRAY_SIZE(mode); i++, sep = ", ")
+ fprintf(stderr, "%s%s", sep, mode[i].name);
+ fprintf(stderr, "\n");
+ return 129;
+}
diff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c
index 2051ce57db..a282b6ff13 100644
--- a/t/helper/test-parse-options.c
+++ b/t/helper/test-parse-options.c
@@ -134,7 +134,6 @@ int cmd__parse_options(int argc, const char **argv)
OPT_NOOP_NOARG(0, "obsolete"),
OPT_STRING_LIST(0, "list", &list, "str", "add str to list"),
OPT_GROUP("Magic arguments"),
- OPT_ARGUMENT("quux", NULL, "means --quux"),
OPT_NUMBER_CALLBACK(&integer, "set integer to NUM",
number_callback),
{ OPTION_COUNTUP, '+', NULL, &boolean, NULL, "same as -b",
diff --git a/t/helper/test-run-command.c b/t/helper/test-run-command.c
index 7ae03dc712..3c4fb86223 100644
--- a/t/helper/test-run-command.c
+++ b/t/helper/test-run-command.c
@@ -60,8 +60,10 @@ struct testsuite {
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 }
+#define TESTSUITE_INIT { \
+ .tests = STRING_LIST_INIT_DUP, \
+ .failed = STRING_LIST_INIT_DUP, \
+}
static int next_test(struct child_process *cp, struct strbuf *err, void *cb,
void **task_cb)
@@ -142,9 +144,6 @@ static int testsuite(int argc, const char **argv)
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);
diff --git a/t/helper/test-serve-v2.c b/t/helper/test-serve-v2.c
index aee35e5aef..28e905afc3 100644
--- a/t/helper/test-serve-v2.c
+++ b/t/helper/test-serve-v2.c
@@ -10,12 +10,12 @@ static char const * const serve_usage[] = {
int cmd__serve_v2(int argc, const char **argv)
{
- struct serve_options opts = SERVE_OPTIONS_INIT;
-
+ int stateless_rpc = 0;
+ int advertise_capabilities = 0;
struct option options[] = {
- OPT_BOOL(0, "stateless-rpc", &opts.stateless_rpc,
+ OPT_BOOL(0, "stateless-rpc", &stateless_rpc,
N_("quit after a single request/response exchange")),
- OPT_BOOL(0, "advertise-capabilities", &opts.advertise_capabilities,
+ OPT_BOOL(0, "advertise-capabilities", &advertise_capabilities,
N_("exit immediately after advertising capabilities")),
OPT_END()
};
@@ -25,7 +25,11 @@ int cmd__serve_v2(int argc, const char **argv)
argc = parse_options(argc, argv, prefix, options, serve_usage,
PARSE_OPT_KEEP_DASHDASH |
PARSE_OPT_KEEP_UNKNOWN);
- serve(&opts);
+
+ if (advertise_capabilities)
+ protocol_v2_advertise_capabilities();
+ else
+ protocol_v2_serve_loop(stateless_rpc);
return 0;
}
diff --git a/t/helper/test-simple-ipc.c b/t/helper/test-simple-ipc.c
index 42040ef81b..28365ff85b 100644
--- a/t/helper/test-simple-ipc.c
+++ b/t/helper/test-simple-ipc.c
@@ -9,6 +9,7 @@
#include "parse-options.h"
#include "thread-utils.h"
#include "strvec.h"
+#include "run-command.h"
#ifndef SUPPORTS_SIMPLE_IPC
int cmd__simple_ipc(int argc, const char **argv)
@@ -112,7 +113,7 @@ static int app__slow_command(ipc_server_reply_cb *reply_cb,
/*
* The client sent a command followed by a (possibly very) large buffer.
*/
-static int app__sendbytes_command(const char *received,
+static int app__sendbytes_command(const char *received, size_t received_len,
ipc_server_reply_cb *reply_cb,
struct ipc_server_reply_data *reply_data)
{
@@ -123,6 +124,13 @@ static int app__sendbytes_command(const char *received,
int errs = 0;
int ret;
+ /*
+ * The test is setup to send:
+ * "sendbytes" SP <n * char>
+ */
+ if (received_len < strlen("sendbytes "))
+ BUG("received_len is short in app__sendbytes_command");
+
if (skip_prefix(received, "sendbytes ", &p))
len_ballast = strlen(p);
@@ -160,7 +168,7 @@ static ipc_server_application_cb test_app_cb;
* by this application.
*/
static int test_app_cb(void *application_data,
- const char *command,
+ const char *command, size_t command_len,
ipc_server_reply_cb *reply_cb,
struct ipc_server_reply_data *reply_data)
{
@@ -173,7 +181,7 @@ static int test_app_cb(void *application_data,
if (application_data != (void*)&my_app_data)
BUG("application_cb: application_data pointer wrong");
- if (!strcmp(command, "quit")) {
+ if (command_len == 4 && !strncmp(command, "quit", 4)) {
/*
* The client sent a "quit" command. This is an async
* request for the server to shutdown.
@@ -193,22 +201,23 @@ static int test_app_cb(void *application_data,
return SIMPLE_IPC_QUIT;
}
- if (!strcmp(command, "ping")) {
+ if (command_len == 4 && !strncmp(command, "ping", 4)) {
const char *answer = "pong";
return reply_cb(reply_data, answer, strlen(answer));
}
- if (!strcmp(command, "big"))
+ if (command_len == 3 && !strncmp(command, "big", 3))
return app__big_command(reply_cb, reply_data);
- if (!strcmp(command, "chunk"))
+ if (command_len == 5 && !strncmp(command, "chunk", 5))
return app__chunk_command(reply_cb, reply_data);
- if (!strcmp(command, "slow"))
+ if (command_len == 4 && !strncmp(command, "slow", 4))
return app__slow_command(reply_cb, reply_data);
- if (starts_with(command, "sendbytes "))
- return app__sendbytes_command(command, reply_cb, reply_data);
+ if (command_len >= 10 && starts_with(command, "sendbytes "))
+ return app__sendbytes_command(command, command_len,
+ reply_cb, reply_data);
return app__unhandled_command(command, reply_cb, reply_data);
}
@@ -259,186 +268,72 @@ static int daemon__run_server(void)
*/
ret = ipc_server_run(cl_args.path, &opts, test_app_cb, (void*)&my_app_data);
if (ret == -2)
- error(_("socket/pipe already in use: '%s'"), cl_args.path);
+ error("socket/pipe already in use: '%s'", cl_args.path);
else if (ret == -1)
- error_errno(_("could not start server on: '%s'"), cl_args.path);
+ error_errno("could not start server on: '%s'", cl_args.path);
return ret;
}
-#ifndef GIT_WINDOWS_NATIVE
-/*
- * This is adapted from `daemonize()`. Use `fork()` to directly create and
- * run the daemon in a child process.
- */
-static int spawn_server(pid_t *pid)
-{
- struct ipc_server_opts opts = {
- .nr_threads = cl_args.nr_threads,
- };
-
- *pid = fork();
+static start_bg_wait_cb bg_wait_cb;
- switch (*pid) {
- case 0:
- if (setsid() == -1)
- error_errno(_("setsid failed"));
- close(0);
- close(1);
- close(2);
- sanitize_stdfds();
+static int bg_wait_cb(const struct child_process *cp, void *cb_data)
+{
+ int s = ipc_get_active_state(cl_args.path);
- return ipc_server_run(cl_args.path, &opts, test_app_cb,
- (void*)&my_app_data);
+ switch (s) {
+ case IPC_STATE__LISTENING:
+ /* child is "ready" */
+ return 0;
- case -1:
- return error_errno(_("could not spawn daemon in the background"));
+ case IPC_STATE__NOT_LISTENING:
+ case IPC_STATE__PATH_NOT_FOUND:
+ /* give child more time */
+ return 1;
default:
- return 0;
+ case IPC_STATE__INVALID_PATH:
+ case IPC_STATE__OTHER_ERROR:
+ /* all the time in world won't help */
+ return -1;
}
}
-#else
-/*
- * Conceptually like `daemonize()` but different because Windows does not
- * have `fork(2)`. Spawn a normal Windows child process but without the
- * limitations of `start_command()` and `finish_command()`.
- */
-static int spawn_server(pid_t *pid)
-{
- char test_tool_exe[MAX_PATH];
- struct strvec args = STRVEC_INIT;
- int in, out;
-
- GetModuleFileNameA(NULL, test_tool_exe, MAX_PATH);
-
- in = open("/dev/null", O_RDONLY);
- out = open("/dev/null", O_WRONLY);
-
- strvec_push(&args, test_tool_exe);
- strvec_push(&args, "simple-ipc");
- strvec_push(&args, "run-daemon");
- strvec_pushf(&args, "--name=%s", cl_args.path);
- strvec_pushf(&args, "--threads=%d", cl_args.nr_threads);
- *pid = mingw_spawnvpe(args.v[0], args.v, NULL, NULL, in, out, out);
- close(in);
- close(out);
-
- strvec_clear(&args);
-
- if (*pid < 0)
- return error(_("could not spawn daemon in the background"));
-
- return 0;
-}
-#endif
-
-/*
- * This is adapted from `wait_or_whine()`. Watch the child process and
- * let it get started and begin listening for requests on the socket
- * before reporting our success.
- */
-static int wait_for_server_startup(pid_t pid_child)
+static int daemon__start_server(void)
{
- int status;
- pid_t pid_seen;
- enum ipc_active_state s;
- time_t time_limit, now;
+ struct child_process cp = CHILD_PROCESS_INIT;
+ enum start_bg_result sbgr;
- time(&time_limit);
- time_limit += cl_args.max_wait_sec;
+ strvec_push(&cp.args, "test-tool");
+ strvec_push(&cp.args, "simple-ipc");
+ strvec_push(&cp.args, "run-daemon");
+ strvec_pushf(&cp.args, "--name=%s", cl_args.path);
+ strvec_pushf(&cp.args, "--threads=%d", cl_args.nr_threads);
- for (;;) {
- pid_seen = waitpid(pid_child, &status, WNOHANG);
-
- if (pid_seen == -1)
- return error_errno(_("waitpid failed"));
-
- else if (pid_seen == 0) {
- /*
- * The child is still running (this should be
- * the normal case). Try to connect to it on
- * the socket and see if it is ready for
- * business.
- *
- * If there is another daemon already running,
- * our child will fail to start (possibly
- * after a timeout on the lock), but we don't
- * care (who responds) if the socket is live.
- */
- s = ipc_get_active_state(cl_args.path);
- if (s == IPC_STATE__LISTENING)
- return 0;
+ cp.no_stdin = 1;
+ cp.no_stdout = 1;
+ cp.no_stderr = 1;
- time(&now);
- if (now > time_limit)
- return error(_("daemon not online yet"));
+ sbgr = start_bg_command(&cp, bg_wait_cb, NULL, cl_args.max_wait_sec);
- continue;
- }
+ switch (sbgr) {
+ case SBGR_READY:
+ return 0;
- else if (pid_seen == pid_child) {
- /*
- * The new child daemon process shutdown while
- * it was starting up, so it is not listening
- * on the socket.
- *
- * Try to ping the socket in the odd chance
- * that another daemon started (or was already
- * running) while our child was starting.
- *
- * Again, we don't care who services the socket.
- */
- s = ipc_get_active_state(cl_args.path);
- if (s == IPC_STATE__LISTENING)
- return 0;
+ default:
+ case SBGR_ERROR:
+ case SBGR_CB_ERROR:
+ return error("daemon failed to start");
- /*
- * We don't care about the WEXITSTATUS() nor
- * any of the WIF*(status) values because
- * `cmd__simple_ipc()` does the `!!result`
- * trick on all function return values.
- *
- * So it is sufficient to just report the
- * early shutdown as an error.
- */
- return error(_("daemon failed to start"));
- }
+ case SBGR_TIMEOUT:
+ return error("daemon not online yet");
- else
- return error(_("waitpid is confused"));
+ case SBGR_DIED:
+ return error("daemon terminated");
}
}
/*
- * This process will start a simple-ipc server in a background process and
- * wait for it to become ready. This is like `daemonize()` but gives us
- * more control and better error reporting (and makes it easier to write
- * unit tests).
- */
-static int daemon__start_server(void)
-{
- pid_t pid_child;
- int ret;
-
- /*
- * Run the actual daemon in a background process.
- */
- ret = spawn_server(&pid_child);
- if (pid_child <= 0)
- return ret;
-
- /*
- * Let the parent wait for the child process to get started
- * and begin listening for requests on the socket.
- */
- ret = wait_for_server_startup(pid_child);
-
- return ret;
-}
-
-/*
* This process will run a quick probe to see if a simple-ipc server
* is active on this path.
*
@@ -488,7 +383,9 @@ static int client__send_ipc(void)
options.wait_if_busy = 1;
options.wait_if_not_found = 0;
- if (!ipc_client_send_command(cl_args.path, &options, command, &buf)) {
+ if (!ipc_client_send_command(cl_args.path, &options,
+ command, strlen(command),
+ &buf)) {
if (buf.len) {
printf("%s\n", buf.buf);
fflush(stdout);
@@ -538,7 +435,7 @@ static int client__stop_server(void)
time(&now);
if (now > time_limit)
- return error(_("daemon has not shutdown yet"));
+ return error("daemon has not shutdown yet");
}
}
@@ -556,7 +453,9 @@ static int do_sendbytes(int bytecount, char byte, const char *path,
strbuf_addstr(&buf_send, "sendbytes ");
strbuf_addchars(&buf_send, byte, bytecount);
- if (!ipc_client_send_command(path, options, buf_send.buf, &buf_resp)) {
+ if (!ipc_client_send_command(path, options,
+ buf_send.buf, buf_send.len,
+ &buf_resp)) {
strbuf_rtrim(&buf_resp);
printf("sent:%c%08d %s\n", byte, bytecount, buf_resp.buf);
fflush(stdout);
diff --git a/t/helper/test-submodule-nested-repo-config.c b/t/helper/test-submodule-nested-repo-config.c
index e3f11ff5a7..dc1c14bde3 100644
--- a/t/helper/test-submodule-nested-repo-config.c
+++ b/t/helper/test-submodule-nested-repo-config.c
@@ -11,15 +11,13 @@ static void die_usage(const char **argv, const char *msg)
int cmd__submodule_nested_repo_config(int argc, const char **argv)
{
struct repository subrepo;
- const struct submodule *sub;
if (argc < 3)
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)) {
+ if (repo_submodule_init(&subrepo, the_repository, argv[1], null_oid())) {
die_usage(argv, "Submodule not found.");
}
diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf
index afa91e38b0..180a41fe96 100644
--- a/t/lib-httpd/apache.conf
+++ b/t/lib-httpd/apache.conf
@@ -81,8 +81,6 @@ PassEnv GIT_TRACE
PassEnv GIT_CONFIG_NOSYSTEM
PassEnv GIT_TEST_SIDEBAND_ALL
-SetEnvIf Git-Protocol ".*" GIT_PROTOCOL=$0
-
Alias /dumb/ www/
Alias /auth/dumb/ www/auth/dumb/
@@ -117,6 +115,11 @@ Alias /auth/dumb/ www/auth/dumb/
SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
SetEnv GIT_HTTP_EXPORT_ALL
</LocationMatch>
+<LocationMatch /smart_v0/>
+ SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
+ SetEnv GIT_HTTP_EXPORT_ALL
+ SetEnv GIT_PROTOCOL
+</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/
diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh
index dc75b83451..ec6b9b107d 100644
--- a/t/lib-rebase.sh
+++ b/t/lib-rebase.sh
@@ -151,3 +151,59 @@ test_editor_unchanged () {
EOF
test_cmp expect actual
}
+
+# Set up an editor for testing reword commands
+# Checks that there are no uncommitted changes when rewording and that the
+# todo-list is reread after each
+set_reword_editor () {
+ >reword-actual &&
+ >reword-oid &&
+
+ # Check rewording keeps the original authorship
+ GIT_AUTHOR_NAME="Reword Author"
+ GIT_AUTHOR_EMAIL="reword.author@example.com"
+ GIT_AUTHOR_DATE=@123456
+
+ write_script reword-sequence-editor.sh <<-\EOF &&
+ todo="$(cat "$1")" &&
+ echo "exec git log -1 --pretty=format:'%an <%ae> %at%n%B%n' \
+ >>reword-actual" >"$1" &&
+ printf "%s\n" "$todo" >>"$1"
+ EOF
+
+ write_script reword-editor.sh <<-EOF &&
+ # Save the oid of the first reworded commit so we can check rebase
+ # fast-forwards to it. Also check that we do not write .git/MERGE_MSG
+ # when fast-forwarding
+ if ! test -s reword-oid
+ then
+ git rev-parse HEAD >reword-oid &&
+ if test -f .git/MERGE_MSG
+ then
+ echo 1>&2 "error: .git/MERGE_MSG exists"
+ exit 1
+ fi
+ fi &&
+ # There should be no uncommited changes
+ git diff --exit-code HEAD &&
+ # The todo-list should be re-read after a reword
+ GIT_SEQUENCE_EDITOR="\"$PWD/reword-sequence-editor.sh\"" \
+ git rebase --edit-todo &&
+ echo edited >>"\$1"
+ EOF
+
+ test_set_editor "$PWD/reword-editor.sh"
+}
+
+# Check the results of a rebase after calling set_reword_editor
+# Pass the commits that were reworded in the order that they were picked
+# Expects the first pick to be a fast-forward
+check_reworded_commits () {
+ test_cmp_rev "$(cat reword-oid)" "$1^{commit}" &&
+ git log --format="%an <%ae> %at%n%B%nedited%n" --no-walk=unsorted "$@" \
+ >reword-expected &&
+ test_cmp reword-expected reword-actual &&
+ git log --format="%an <%ae> %at%n%B" -n $# --first-parent --reverse \
+ >reword-log &&
+ test_cmp reword-expected reword-log
+}
diff --git a/t/lib-subtest.sh b/t/lib-subtest.sh
new file mode 100644
index 0000000000..56ee927f0c
--- /dev/null
+++ b/t/lib-subtest.sh
@@ -0,0 +1,95 @@
+write_sub_test_lib_test () {
+ name="$1" # stdin is the body of the test code
+ mkdir "$name" &&
+ write_script "$name/$name.sh" "$TEST_SHELL_PATH" <<-EOF &&
+ test_description='A test of test-lib.sh itself'
+
+ # Point to the t/test-lib.sh, which isn't in ../ as usual
+ . "\$TEST_DIRECTORY"/test-lib.sh
+ EOF
+ cat >>"$name/$name.sh"
+}
+
+_run_sub_test_lib_test_common () {
+ cmp_op="$1" want_code="$2" name="$3" # stdin is the body of the test code
+ shift 3
+
+ # intercept pseudo-options at the front of the argument list that we
+ # will not pass to child script
+ skip=
+ while test $# -gt 0
+ do
+ case "$1" in
+ --skip=*)
+ skip=${1#--*=}
+ shift
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+
+ (
+ cd "$name" &&
+
+ # Pretend we're not running under a test harness, whether we
+ # are or not. The test-lib output depends on the setting of
+ # this variable, so we need a stable setting under which to run
+ # the sub-test.
+ sane_unset HARNESS_ACTIVE &&
+
+ export TEST_DIRECTORY &&
+ # The child test re-sources GIT-BUILD-OPTIONS and may thus
+ # override the test output directory. We thus pass it as an
+ # explicit override to the child.
+ TEST_OUTPUT_DIRECTORY_OVERRIDE=$(pwd) &&
+ export TEST_OUTPUT_DIRECTORY_OVERRIDE &&
+ GIT_SKIP_TESTS=$skip &&
+ export GIT_SKIP_TESTS &&
+ sane_unset GIT_TEST_FAIL_PREREQS &&
+ ./"$name.sh" "$@" >out 2>err;
+ ret=$? &&
+ test "$ret" "$cmp_op" "$want_code"
+ )
+}
+
+write_and_run_sub_test_lib_test () {
+ name="$1" descr="$2" # stdin is the body of the test code
+ write_sub_test_lib_test "$@" || return 1
+ _run_sub_test_lib_test_common -eq 0 "$@"
+}
+
+write_and_run_sub_test_lib_test_err () {
+ name="$1" descr="$2" # stdin is the body of the test code
+ write_sub_test_lib_test "$@" || return 1
+ _run_sub_test_lib_test_common -eq 1 "$@"
+}
+
+run_sub_test_lib_test () {
+ _run_sub_test_lib_test_common -eq 0 "$@"
+}
+
+run_sub_test_lib_test_err () {
+ _run_sub_test_lib_test_common -eq 1 "$@"
+}
+
+_check_sub_test_lib_test_common () {
+ name="$1" &&
+ sed -e 's/^> //' -e 's/Z$//' >"$name"/expect.out &&
+ test_cmp "$name"/expect.out "$name"/out
+}
+
+check_sub_test_lib_test () {
+ name="$1" # stdin is the expected output from the test
+ _check_sub_test_lib_test_common "$name" &&
+ test_must_be_empty "$name"/err
+}
+
+check_sub_test_lib_test_err () {
+ name="$1" # stdin is the expected output from the test
+ _check_sub_test_lib_test_common "$name" &&
+ # expected error output is in descriptor 3
+ sed -e 's/^> //' -e 's/Z$//' <&3 >"$name"/expect.err &&
+ test_cmp "$name"/expect.err "$name"/err
+}
diff --git a/t/perf/aggregate.perl b/t/perf/aggregate.perl
index 82c0df4553..575d2000cc 100755
--- a/t/perf/aggregate.perl
+++ b/t/perf/aggregate.perl
@@ -17,8 +17,8 @@ sub get_times {
my $rt = ((defined $1 ? $1 : 0.0)*60+$2)*60+$3;
return ($rt, $4, $5);
# size
- } elsif ($line =~ /^\d+$/) {
- return $&;
+ } elsif ($line =~ /^\s*(\d+)$/) {
+ return $1;
} else {
die "bad input line: $line";
}
diff --git a/t/perf/p0071-sort.sh b/t/perf/p0071-sort.sh
index 6e924f5fa3..ed366e2e12 100755
--- a/t/perf/p0071-sort.sh
+++ b/t/perf/p0071-sort.sh
@@ -11,16 +11,42 @@ test_expect_success 'setup' '
git cat-file --batch >unsorted
'
-test_perf 'sort(1)' '
- sort <unsorted >expect
+test_perf 'sort(1) unsorted' '
+ sort <unsorted >sorted
'
-test_perf 'string_list_sort()' '
- test-tool string-list sort <unsorted >actual
+test_expect_success 'reverse' '
+ sort -r <unsorted >reversed
'
-test_expect_success 'string_list_sort() sorts like sort(1)' '
- test_cmp_bin expect actual
-'
+for file in sorted reversed
+do
+ test_perf "sort(1) $file" "
+ sort <$file >actual
+ "
+done
+
+for file in unsorted sorted reversed
+do
+
+ test_perf "string_list_sort() $file" "
+ test-tool string-list sort <$file >actual
+ "
+
+ test_expect_success "string_list_sort() $file sorts like sort(1)" "
+ test_cmp_bin sorted actual
+ "
+done
+
+for file in unsorted sorted reversed
+do
+ test_perf "llist_mergesort() $file" "
+ test-tool mergesort sort <$file >actual
+ "
+
+ test_expect_success "llist_mergesort() $file sorts like sort(1)" "
+ test_cmp_bin sorted actual
+ "
+done
test_done
diff --git a/t/perf/p3400-rebase.sh b/t/perf/p3400-rebase.sh
index 7a0bb29448..43d5a34e8c 100755
--- a/t/perf/p3400-rebase.sh
+++ b/t/perf/p3400-rebase.sh
@@ -18,7 +18,7 @@ test_expect_success 'setup rebasing on top of a lot of changes' '
test_tick &&
git commit -m commit$i unrelated-file$i &&
echo change$i >unrelated-file$i &&
- test_seq 1000 | tac >>unrelated-file$i &&
+ test_seq 1000 | sort -nr >>unrelated-file$i &&
git add unrelated-file$i &&
test_tick &&
git commit -m commit$i-reverse unrelated-file$i ||
diff --git a/t/perf/p5326-multi-pack-bitmaps.sh b/t/perf/p5326-multi-pack-bitmaps.sh
index 5845109ac7..f2fa228f16 100755
--- a/t/perf/p5326-multi-pack-bitmaps.sh
+++ b/t/perf/p5326-multi-pack-bitmaps.sh
@@ -6,15 +6,24 @@ test_description='Tests performance using midx bitmaps'
test_perf_large_repo
-test_expect_success 'enable multi-pack index' '
- git config core.multiPackIndex true
+# we need to create the tag up front such that it is covered by the repack and
+# thus by generated bitmaps.
+test_expect_success 'create tags' '
+ git tag --message="tag pointing to HEAD" perf-tag HEAD
+'
+
+test_expect_success 'start with bitmapped pack' '
+ git repack -adb
'
test_perf 'setup multi-pack index' '
- git repack -ad &&
git multi-pack-index write --bitmap
'
+test_expect_success 'drop pack bitmap' '
+ rm -f .git/objects/pack/pack-*.bitmap
+'
+
test_full_bitmap
test_expect_success 'create partial bitmap state' '
diff --git a/t/perf/run b/t/perf/run
index d19dec258a..55219aa405 100755
--- a/t/perf/run
+++ b/t/perf/run
@@ -74,7 +74,7 @@ set_git_test_installed () {
mydir=$1
mydir_abs=$(cd $mydir && pwd)
- mydir_abs_wrappers="$mydir_abs_wrappers/bin-wrappers"
+ mydir_abs_wrappers="$mydir_abs/bin-wrappers"
if test -d "$mydir_abs_wrappers"
then
GIT_TEST_INSTALLED=$mydir_abs_wrappers
diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
index cb87768513..b007f0efef 100755
--- a/t/t0000-basic.sh
+++ b/t/t0000-basic.sh
@@ -19,6 +19,7 @@ modification *should* take notice and update the test vectors here.
'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-subtest.sh
try_local_xy () {
local x="local" y="alsolocal" &&
@@ -66,95 +67,8 @@ test_expect_success 'success is reported like this' '
:
'
-_run_sub_test_lib_test_common () {
- neg="$1" name="$2" descr="$3" # stdin is the body of the test code
- shift 3
-
- # intercept pseudo-options at the front of the argument list that we
- # will not pass to child script
- skip=
- while test $# -gt 0
- do
- case "$1" in
- --skip=*)
- skip=${1#--*=}
- shift
- ;;
- *)
- break
- ;;
- esac
- done
-
- mkdir "$name" &&
- (
- # Pretend we're not running under a test harness, whether we
- # are or not. The test-lib output depends on the setting of
- # this variable, so we need a stable setting under which to run
- # the sub-test.
- sane_unset HARNESS_ACTIVE &&
- cd "$name" &&
- 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
- passing metrics
- '
-
- # Point to the t/test-lib.sh, which isn't in ../ as usual
- . "\$TEST_DIRECTORY"/test-lib.sh
- EOF
- cat >>"$name.sh" &&
- export TEST_DIRECTORY &&
- # The child test re-sources GIT-BUILD-OPTIONS and may thus
- # override the test output directory. We thus pass it as an
- # explicit override to the child.
- TEST_OUTPUT_DIRECTORY_OVERRIDE=$(pwd) &&
- export TEST_OUTPUT_DIRECTORY_OVERRIDE &&
- GIT_SKIP_TESTS=$skip &&
- export GIT_SKIP_TESTS &&
- sane_unset GIT_TEST_FAIL_PREREQS &&
- if test -z "$neg"
- then
- ./"$name.sh" "$@" >out 2>err
- else
- ! ./"$name.sh" "$@" >out 2>err
- fi
- )
-}
-
-run_sub_test_lib_test () {
- _run_sub_test_lib_test_common '' "$@"
-}
-
-run_sub_test_lib_test_err () {
- _run_sub_test_lib_test_common '!' "$@"
-}
-
-check_sub_test_lib_test () {
- name="$1" # stdin is the expected output from the test
- (
- cd "$name" &&
- test_must_be_empty err &&
- sed -e 's/^> //' -e 's/Z$//' >expect &&
- test_cmp expect out
- )
-}
-
-check_sub_test_lib_test_err () {
- name="$1" # stdin is the expected output from the test
- # expected error output is in descriptor 3
- (
- cd "$name" &&
- sed -e 's/^> //' -e 's/Z$//' >expect.out &&
- test_cmp expect.out out &&
- sed -e 's/^> //' -e 's/Z$//' <&3 >expect.err &&
- test_cmp expect.err err
- )
-}
-
-test_expect_success 'pretend we have a fully passing test suite' '
- run_sub_test_lib_test full-pass "3 passing tests" <<-\EOF &&
+test_expect_success 'subtest: 3 passing tests' '
+ write_and_run_sub_test_lib_test full-pass <<-\EOF &&
for i in 1 2 3
do
test_expect_success "passing test #$i" "true"
@@ -170,9 +84,8 @@ test_expect_success 'pretend we have a fully passing test suite' '
EOF
'
-test_expect_success 'pretend we have a partially passing test suite' '
- run_sub_test_lib_test_err \
- partial-pass "2/3 tests passing" <<-\EOF &&
+test_expect_success 'subtest: 2/3 tests passing' '
+ write_and_run_sub_test_lib_test_err partial-pass <<-\EOF &&
test_expect_success "passing test #1" "true"
test_expect_success "failing test #2" "false"
test_expect_success "passing test #3" "true"
@@ -188,8 +101,8 @@ test_expect_success 'pretend we have a partially passing test suite' '
EOF
'
-test_expect_success 'pretend we have a known breakage' '
- run_sub_test_lib_test failing-todo "A failing TODO test" <<-\EOF &&
+test_expect_success 'subtest: a failing TODO test' '
+ write_and_run_sub_test_lib_test failing-todo <<-\EOF &&
test_expect_success "passing test" "true"
test_expect_failure "pretend we have a known breakage" "false"
test_done
@@ -203,8 +116,8 @@ test_expect_success 'pretend we have a known breakage' '
EOF
'
-test_expect_success 'pretend we have fixed a known breakage' '
- run_sub_test_lib_test passing-todo "A passing TODO test" <<-\EOF &&
+test_expect_success 'subtest: a passing TODO test' '
+ write_and_run_sub_test_lib_test passing-todo <<-\EOF &&
test_expect_failure "pretend we have fixed a known breakage" "true"
test_done
EOF
@@ -215,9 +128,8 @@ test_expect_success 'pretend we have fixed a known breakage' '
EOF
'
-test_expect_success 'pretend we have fixed one of two known breakages (run in sub test-lib)' '
- run_sub_test_lib_test partially-passing-todos \
- "2 TODO tests, one passing" <<-\EOF &&
+test_expect_success 'subtest: 2 TODO tests, one passin' '
+ write_and_run_sub_test_lib_test partially-passing-todos <<-\EOF &&
test_expect_failure "pretend we have a known breakage" "false"
test_expect_success "pretend we have a passing test" "true"
test_expect_failure "pretend we have fixed another known breakage" "true"
@@ -234,9 +146,8 @@ test_expect_success 'pretend we have fixed one of two known breakages (run in su
EOF
'
-test_expect_success 'pretend we have a pass, fail, and known breakage' '
- run_sub_test_lib_test_err \
- mixed-results1 "mixed results #1" <<-\EOF &&
+test_expect_success 'subtest: mixed results: pass, failure and a TODO test' '
+ write_and_run_sub_test_lib_test_err mixed-results1 <<-\EOF &&
test_expect_success "passing test" "true"
test_expect_success "failing test" "false"
test_expect_failure "pretend we have a known breakage" "false"
@@ -253,9 +164,8 @@ test_expect_success 'pretend we have a pass, fail, and known breakage' '
EOF
'
-test_expect_success 'pretend we have a mix of all possible results' '
- run_sub_test_lib_test_err \
- mixed-results2 "mixed results #2" <<-\EOF &&
+test_expect_success 'subtest: mixed results: a mixture of all possible results' '
+ write_and_run_sub_test_lib_test_err mixed-results2 <<-\EOF &&
test_expect_success "passing test" "true"
test_expect_success "passing test" "true"
test_expect_success "passing test" "true"
@@ -289,9 +199,8 @@ test_expect_success 'pretend we have a mix of all possible results' '
EOF
'
-test_expect_success 'test --verbose' '
- run_sub_test_lib_test_err \
- t1234-verbose "test verbose" --verbose <<-\EOF &&
+test_expect_success 'subtest: --verbose option' '
+ write_and_run_sub_test_lib_test_err t1234-verbose --verbose <<-\EOF &&
test_expect_success "passing test" true
test_expect_success "test with output" "echo foo"
test_expect_success "failing test" false
@@ -316,19 +225,14 @@ test_expect_success 'test --verbose' '
EOF
'
-test_expect_success 'test --verbose-only' '
+test_expect_success 'subtest: --verbose-only option' '
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 t2345-verbose-only-2 <<-\EOF
+ t1234-verbose \
+ --verbose-only=2 &&
+ check_sub_test_lib_test t1234-verbose <<-\EOF
> ok 1 - passing test
> Z
- > expecting success of 2345.2 '\''test with output'\'': echo foo
+ > expecting success of 1234.2 '\''test with output'\'': echo foo
> foo
> ok 2 - test with output
> Z
@@ -339,18 +243,11 @@ test_expect_success 'test --verbose-only' '
EOF
'
-test_expect_success 'GIT_SKIP_TESTS' '
+test_expect_success 'subtest: skip one with GIT_SKIP_TESTS' '
(
- run_sub_test_lib_test git-skip-tests-basic \
- "GIT_SKIP_TESTS" \
- --skip="git.2" <<-\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-basic <<-\EOF
+ run_sub_test_lib_test full-pass \
+ --skip="full.2" &&
+ check_sub_test_lib_test full-pass <<-\EOF
> ok 1 - passing test #1
> ok 2 # skip passing test #2 (GIT_SKIP_TESTS)
> ok 3 - passing test #3
@@ -360,10 +257,9 @@ test_expect_success 'GIT_SKIP_TESTS' '
)
'
-test_expect_success 'GIT_SKIP_TESTS several tests' '
+test_expect_success 'subtest: skip several with GIT_SKIP_TESTS' '
(
- run_sub_test_lib_test git-skip-tests-several \
- "GIT_SKIP_TESTS several tests" \
+ write_and_run_sub_test_lib_test git-skip-tests-several \
--skip="git.2 git.5" <<-\EOF &&
for i in 1 2 3 4 5 6
do
@@ -384,18 +280,11 @@ test_expect_success 'GIT_SKIP_TESTS several tests' '
)
'
-test_expect_success 'GIT_SKIP_TESTS sh pattern' '
+test_expect_success 'subtest: sh pattern skipping with GIT_SKIP_TESTS' '
(
- run_sub_test_lib_test git-skip-tests-sh-pattern \
- "GIT_SKIP_TESTS sh pattern" \
- --skip="git.[2-5]" <<-\EOF &&
- for i in 1 2 3 4 5 6
- do
- test_expect_success "passing test #$i" "true"
- done
- test_done
- EOF
- check_sub_test_lib_test git-skip-tests-sh-pattern <<-\EOF
+ run_sub_test_lib_test git-skip-tests-several \
+ --skip="git.[2-5]" &&
+ check_sub_test_lib_test git-skip-tests-several <<-\EOF
> ok 1 - passing test #1
> ok 2 # skip passing test #2 (GIT_SKIP_TESTS)
> ok 3 # skip passing test #3 (GIT_SKIP_TESTS)
@@ -408,35 +297,23 @@ test_expect_success 'GIT_SKIP_TESTS sh pattern' '
)
'
-test_expect_success 'GIT_SKIP_TESTS entire suite' '
+test_expect_success 'subtest: skip entire test suite with GIT_SKIP_TESTS' '
(
- run_sub_test_lib_test git-skip-tests-entire-suite \
- "GIT_SKIP_TESTS entire suite" \
- --skip="git" <<-\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
+ GIT_SKIP_TESTS="git" && export GIT_SKIP_TESTS &&
+ run_sub_test_lib_test git-skip-tests-several \
+ --skip="git" &&
+ check_sub_test_lib_test git-skip-tests-several <<-\EOF
> 1..0 # SKIP skip all tests in git
EOF
)
'
-test_expect_success 'GIT_SKIP_TESTS does not skip unmatched suite' '
+test_expect_success 'subtest: GIT_SKIP_TESTS does not skip unmatched suite' '
(
- run_sub_test_lib_test git-skip-tests-unmatched-suite \
- "GIT_SKIP_TESTS does not skip unmatched suite" \
- --skip="notgit" <<-\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
+ GIT_SKIP_TESTS="notgit" && export GIT_SKIP_TESTS &&
+ run_sub_test_lib_test full-pass \
+ --skip="notfull" &&
+ check_sub_test_lib_test full-pass <<-\EOF
> ok 1 - passing test #1
> ok 2 - passing test #2
> ok 3 - passing test #3
@@ -446,16 +323,9 @@ test_expect_success 'GIT_SKIP_TESTS does not skip unmatched suite' '
)
'
-test_expect_success '--run basic' '
- run_sub_test_lib_test run-basic \
- "--run basic" --run="1,3,5" <<-\EOF &&
- for i in 1 2 3 4 5 6
- do
- test_expect_success "passing test #$i" "true"
- done
- test_done
- EOF
- check_sub_test_lib_test run-basic <<-\EOF
+test_expect_success 'subtest: --run basic' '
+ run_sub_test_lib_test git-skip-tests-several --run="1,3,5" &&
+ check_sub_test_lib_test git-skip-tests-several <<-\EOF
> ok 1 - passing test #1
> ok 2 # skip passing test #2 (--run)
> ok 3 - passing test #3
@@ -467,16 +337,10 @@ test_expect_success '--run basic' '
EOF
'
-test_expect_success '--run with a range' '
- run_sub_test_lib_test run-range \
- "--run with a range" --run="1-3" <<-\EOF &&
- for i in 1 2 3 4 5 6
- do
- test_expect_success "passing test #$i" "true"
- done
- test_done
- EOF
- check_sub_test_lib_test run-range <<-\EOF
+test_expect_success 'subtest: --run with a range' '
+ run_sub_test_lib_test git-skip-tests-several \
+ --run="1-3" &&
+ check_sub_test_lib_test git-skip-tests-several <<-\EOF
> ok 1 - passing test #1
> ok 2 - passing test #2
> ok 3 - passing test #3
@@ -488,16 +352,10 @@ test_expect_success '--run with a range' '
EOF
'
-test_expect_success '--run with two ranges' '
- run_sub_test_lib_test run-two-ranges \
- "--run with two ranges" --run="1-2,5-6" <<-\EOF &&
- for i in 1 2 3 4 5 6
- do
- test_expect_success "passing test #$i" "true"
- done
- test_done
- EOF
- check_sub_test_lib_test run-two-ranges <<-\EOF
+test_expect_success 'subtest: --run with two ranges' '
+ run_sub_test_lib_test git-skip-tests-several \
+ --run="1-2,5-6" &&
+ check_sub_test_lib_test git-skip-tests-several <<-\EOF
> ok 1 - passing test #1
> ok 2 - passing test #2
> ok 3 # skip passing test #3 (--run)
@@ -509,16 +367,10 @@ test_expect_success '--run with two ranges' '
EOF
'
-test_expect_success '--run with a left open range' '
- run_sub_test_lib_test run-left-open-range \
- "--run with a left open range" --run="-3" <<-\EOF &&
- for i in 1 2 3 4 5 6
- do
- test_expect_success "passing test #$i" "true"
- done
- test_done
- EOF
- check_sub_test_lib_test run-left-open-range <<-\EOF
+test_expect_success 'subtest: --run with a left open range' '
+ run_sub_test_lib_test git-skip-tests-several \
+ --run="-3" &&
+ check_sub_test_lib_test git-skip-tests-several <<-\EOF
> ok 1 - passing test #1
> ok 2 - passing test #2
> ok 3 - passing test #3
@@ -530,16 +382,10 @@ test_expect_success '--run with a left open range' '
EOF
'
-test_expect_success '--run with a right open range' '
- run_sub_test_lib_test run-right-open-range \
- "--run with a right open range" --run="4-" <<-\EOF &&
- for i in 1 2 3 4 5 6
- do
- test_expect_success "passing test #$i" "true"
- done
- test_done
- EOF
- check_sub_test_lib_test run-right-open-range <<-\EOF
+test_expect_success 'subtest: --run with a right open range' '
+ run_sub_test_lib_test git-skip-tests-several \
+ --run="4-" &&
+ check_sub_test_lib_test git-skip-tests-several <<-\EOF
> ok 1 # skip passing test #1 (--run)
> ok 2 # skip passing test #2 (--run)
> ok 3 # skip passing test #3 (--run)
@@ -551,16 +397,10 @@ test_expect_success '--run with a right open range' '
EOF
'
-test_expect_success '--run with basic negation' '
- run_sub_test_lib_test run-basic-neg \
- "--run with basic negation" --run="!3" <<-\EOF &&
- for i in 1 2 3 4 5 6
- do
- test_expect_success "passing test #$i" "true"
- done
- test_done
- EOF
- check_sub_test_lib_test run-basic-neg <<-\EOF
+test_expect_success 'subtest: --run with basic negation' '
+ run_sub_test_lib_test git-skip-tests-several \
+ --run="!3" &&
+ check_sub_test_lib_test git-skip-tests-several <<-\EOF
> ok 1 - passing test #1
> ok 2 - passing test #2
> ok 3 # skip passing test #3 (--run)
@@ -572,16 +412,10 @@ test_expect_success '--run with basic negation' '
EOF
'
-test_expect_success '--run with two negations' '
- run_sub_test_lib_test run-two-neg \
- "--run with two negations" --run="!3,!6" <<-\EOF &&
- for i in 1 2 3 4 5 6
- do
- test_expect_success "passing test #$i" "true"
- done
- test_done
- EOF
- check_sub_test_lib_test run-two-neg <<-\EOF
+test_expect_success 'subtest: --run with two negations' '
+ run_sub_test_lib_test git-skip-tests-several \
+ --run="!3,!6" &&
+ check_sub_test_lib_test git-skip-tests-several <<-\EOF
> ok 1 - passing test #1
> ok 2 - passing test #2
> ok 3 # skip passing test #3 (--run)
@@ -593,16 +427,10 @@ test_expect_success '--run with two negations' '
EOF
'
-test_expect_success '--run a range and negation' '
- run_sub_test_lib_test run-range-and-neg \
- "--run a range and negation" --run="-4,!2" <<-\EOF &&
- for i in 1 2 3 4 5 6
- do
- test_expect_success "passing test #$i" "true"
- done
- test_done
- EOF
- check_sub_test_lib_test run-range-and-neg <<-\EOF
+test_expect_success 'subtest: --run a range and negation' '
+ run_sub_test_lib_test git-skip-tests-several \
+ --run="-4,!2" &&
+ check_sub_test_lib_test git-skip-tests-several <<-\EOF
> ok 1 - passing test #1
> ok 2 # skip passing test #2 (--run)
> ok 3 - passing test #3
@@ -614,16 +442,10 @@ test_expect_success '--run a range and negation' '
EOF
'
-test_expect_success '--run range negation' '
- run_sub_test_lib_test run-range-neg \
- "--run range negation" --run="!1-3" <<-\EOF &&
- for i in 1 2 3 4 5 6
- do
- test_expect_success "passing test #$i" "true"
- done
- test_done
- EOF
- check_sub_test_lib_test run-range-neg <<-\EOF
+test_expect_success 'subtest: --run range negation' '
+ run_sub_test_lib_test git-skip-tests-several \
+ --run="!1-3" &&
+ check_sub_test_lib_test git-skip-tests-several <<-\EOF
> ok 1 # skip passing test #1 (--run)
> ok 2 # skip passing test #2 (--run)
> ok 3 # skip passing test #3 (--run)
@@ -635,17 +457,10 @@ test_expect_success '--run range negation' '
EOF
'
-test_expect_success '--run include, exclude and include' '
- run_sub_test_lib_test run-inc-neg-inc \
- "--run include, exclude and include" \
- --run="1-5,!1-3,2" <<-\EOF &&
- for i in 1 2 3 4 5 6
- do
- test_expect_success "passing test #$i" "true"
- done
- test_done
- EOF
- check_sub_test_lib_test run-inc-neg-inc <<-\EOF
+test_expect_success 'subtest: --run include, exclude and include' '
+ run_sub_test_lib_test git-skip-tests-several \
+ --run="1-5,!1-3,2" &&
+ check_sub_test_lib_test git-skip-tests-several <<-\EOF
> ok 1 # skip passing test #1 (--run)
> ok 2 - passing test #2
> ok 3 # skip passing test #3 (--run)
@@ -657,17 +472,10 @@ test_expect_success '--run include, exclude and include' '
EOF
'
-test_expect_success '--run include, exclude and include, comma separated' '
- run_sub_test_lib_test run-inc-neg-inc-comma \
- "--run include, exclude and include, comma separated" \
- --run=1-5,!1-3,2 <<-\EOF &&
- for i in 1 2 3 4 5 6
- do
- test_expect_success "passing test #$i" "true"
- done
- test_done
- EOF
- check_sub_test_lib_test run-inc-neg-inc-comma <<-\EOF
+test_expect_success 'subtest: --run include, exclude and include, comma separated' '
+ run_sub_test_lib_test git-skip-tests-several \
+ --run=1-5,!1-3,2 &&
+ check_sub_test_lib_test git-skip-tests-several <<-\EOF
> ok 1 # skip passing test #1 (--run)
> ok 2 - passing test #2
> ok 3 # skip passing test #3 (--run)
@@ -679,17 +487,10 @@ test_expect_success '--run include, exclude and include, comma separated' '
EOF
'
-test_expect_success '--run exclude and include' '
- run_sub_test_lib_test run-neg-inc \
- "--run exclude and include" \
- --run="!3-,5" <<-\EOF &&
- for i in 1 2 3 4 5 6
- do
- test_expect_success "passing test #$i" "true"
- done
- test_done
- EOF
- check_sub_test_lib_test run-neg-inc <<-\EOF
+test_expect_success 'subtest: --run exclude and include' '
+ run_sub_test_lib_test git-skip-tests-several \
+ --run="!3-,5" &&
+ check_sub_test_lib_test git-skip-tests-several <<-\EOF
> ok 1 - passing test #1
> ok 2 - passing test #2
> ok 3 # skip passing test #3 (--run)
@@ -701,17 +502,10 @@ test_expect_success '--run exclude and include' '
EOF
'
-test_expect_success '--run empty selectors' '
- run_sub_test_lib_test run-empty-sel \
- "--run empty selectors" \
- --run="1,,3,,,5" <<-\EOF &&
- for i in 1 2 3 4 5 6
- do
- test_expect_success "passing test #$i" "true"
- done
- test_done
- EOF
- check_sub_test_lib_test run-empty-sel <<-\EOF
+test_expect_success 'subtest: --run empty selectors' '
+ run_sub_test_lib_test git-skip-tests-several \
+ --run="1,,3,,,5" &&
+ check_sub_test_lib_test git-skip-tests-several <<-\EOF
> ok 1 - passing test #1
> ok 2 # skip passing test #2 (--run)
> ok 3 - passing test #3
@@ -723,9 +517,8 @@ test_expect_success '--run empty selectors' '
EOF
'
-test_expect_success '--run substring selector' '
- run_sub_test_lib_test run-substring-selector \
- "--run empty selectors" \
+test_expect_success 'subtest: --run substring selector' '
+ write_and_run_sub_test_lib_test run-substring-selector \
--run="relevant" <<-\EOF &&
test_expect_success "relevant test" "true"
for i in 1 2 3 4 5 6
@@ -747,9 +540,8 @@ test_expect_success '--run substring selector' '
EOF
'
-test_expect_success '--run keyword selection' '
- run_sub_test_lib_test_err run-inv-range-start \
- "--run invalid range start" \
+test_expect_success 'subtest: --run keyword selection' '
+ write_and_run_sub_test_lib_test_err run-inv-range-start \
--run="a-5" <<-\EOF &&
test_expect_success "passing test #1" "true"
test_done
@@ -762,14 +554,10 @@ test_expect_success '--run keyword selection' '
EOF_ERR
'
-test_expect_success '--run invalid range end' '
- run_sub_test_lib_test_err run-inv-range-end \
- "--run invalid range end" \
- --run="1-z" <<-\EOF &&
- test_expect_success "passing test #1" "true"
- test_done
- EOF
- check_sub_test_lib_test_err run-inv-range-end \
+test_expect_success 'subtest: --run invalid range end' '
+ run_sub_test_lib_test_err run-inv-range-start \
+ --run="1-z" &&
+ check_sub_test_lib_test_err run-inv-range-start \
<<-\EOF_OUT 3<<-EOF_ERR
> FATAL: Unexpected exit with code 1
EOF_OUT
@@ -777,8 +565,8 @@ test_expect_success '--run invalid range end' '
EOF_ERR
'
-test_expect_success 'tests respect prerequisites' '
- run_sub_test_lib_test prereqs "tests respect prereqs" <<-\EOF &&
+test_expect_success 'subtest: tests respect prerequisites' '
+ write_and_run_sub_test_lib_test prereqs <<-\EOF &&
test_set_prereq HAVEIT
test_expect_success HAVEIT "prereq is satisfied" "true"
@@ -807,8 +595,8 @@ test_expect_success 'tests respect prerequisites' '
EOF
'
-test_expect_success 'tests respect lazy prerequisites' '
- run_sub_test_lib_test lazy-prereqs "respect lazy prereqs" <<-\EOF &&
+test_expect_success 'subtest: tests respect lazy prerequisites' '
+ write_and_run_sub_test_lib_test lazy-prereqs <<-\EOF &&
test_lazy_prereq LAZY_TRUE true
test_expect_success LAZY_TRUE "lazy prereq is satisifed" "true"
@@ -831,8 +619,8 @@ test_expect_success 'tests respect lazy prerequisites' '
EOF
'
-test_expect_success 'nested lazy prerequisites' '
- run_sub_test_lib_test nested-lazy "nested lazy prereqs" <<-\EOF &&
+test_expect_success 'subtest: nested lazy prerequisites' '
+ write_and_run_sub_test_lib_test nested-lazy <<-\EOF &&
test_lazy_prereq NESTED_INNER "
>inner &&
@@ -857,9 +645,9 @@ test_expect_success 'nested lazy prerequisites' '
EOF
'
-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_expect_success 'subtest: lazy prereqs do not turn off tracing' '
+ write_and_run_sub_test_lib_test lazy-prereq-and-tracing \
+ -v -x <<-\EOF &&
test_lazy_prereq LAZY true
test_expect_success lazy "test_have_prereq LAZY && echo trace"
@@ -870,8 +658,8 @@ test_expect_success 'lazy prereqs do not turn off tracing' '
grep "echo trace" lazy-prereq-and-tracing/err
'
-test_expect_success 'tests clean up after themselves' '
- run_sub_test_lib_test cleanup "test with cleanup" <<-\EOF &&
+test_expect_success 'subtest: tests clean up after themselves' '
+ write_and_run_sub_test_lib_test cleanup <<-\EOF &&
clean=no
test_expect_success "do cleanup" "
test_when_finished clean=yes
@@ -890,9 +678,9 @@ test_expect_success 'tests clean up after themselves' '
EOF
'
-test_expect_success 'tests clean up even on failures' '
- run_sub_test_lib_test_err \
- failing-cleanup "Failing tests with cleanup commands" <<-\EOF &&
+test_expect_success 'subtest: tests clean up even on failures' '
+ write_and_run_sub_test_lib_test_err \
+ failing-cleanup <<-\EOF &&
test_expect_success "tests clean up even after a failure" "
touch clean-after-failure &&
test_when_finished rm clean-after-failure &&
@@ -919,9 +707,9 @@ test_expect_success 'tests clean up even on failures' '
EOF
'
-test_expect_success 'test_atexit is run' '
- run_sub_test_lib_test_err \
- atexit-cleanup "Run atexit commands" -i <<-\EOF &&
+test_expect_success 'subtest: test_atexit is run' '
+ write_and_run_sub_test_lib_test_err \
+ atexit-cleanup -i <<-\EOF &&
test_expect_success "tests clean up even after a failure" "
> ../../clean-atexit &&
test_atexit rm ../../clean-atexit &&
@@ -1271,28 +1059,29 @@ P=$(test_oid root)
test_expect_success 'git commit-tree records the correct tree in a commit' '
commit0=$(echo NO | git commit-tree $P) &&
- tree=$(git show --pretty=raw $commit0 |
- sed -n -e "s/^tree //p" -e "/^author /q") &&
+ git show --pretty=raw $commit0 >out &&
+ tree=$(sed -n -e "s/^tree //p" -e "/^author /q" out) &&
test "z$tree" = "z$P"
'
test_expect_success 'git commit-tree records the correct parent in a commit' '
commit1=$(echo NO | git commit-tree $P -p $commit0) &&
- parent=$(git show --pretty=raw $commit1 |
- sed -n -e "s/^parent //p" -e "/^author /q") &&
+ git show --pretty=raw $commit1 >out &&
+ parent=$(sed -n -e "s/^parent //p" -e "/^author /q" out) &&
test "z$commit0" = "z$parent"
'
test_expect_success 'git commit-tree omits duplicated parent in a commit' '
commit2=$(echo NO | git commit-tree $P -p $commit0 -p $commit0) &&
- parent=$(git show --pretty=raw $commit2 |
- sed -n -e "s/^parent //p" -e "/^author /q" |
- sort -u) &&
+ git show --pretty=raw $commit2 >out &&
+ cat >match.sed <<-\EOF &&
+ s/^parent //p
+ /^author /q
+ EOF
+ parent=$(sed -n -f match.sed out | sort -u) &&
test "z$commit0" = "z$parent" &&
- numparent=$(git show --pretty=raw $commit2 |
- sed -n -e "s/^parent //p" -e "/^author /q" |
- wc -l) &&
- test $numparent = 1
+ git show --pretty=raw $commit2 >out &&
+ test_stdout_line_count = 1 sed -n -f match.sed out
'
test_expect_success 'update-index D/F conflict' '
diff --git a/t/t0004-unwritable.sh b/t/t0004-unwritable.sh
index e3137d638e..37d68ef03b 100755
--- a/t/t0004-unwritable.sh
+++ b/t/t0004-unwritable.sh
@@ -2,6 +2,7 @@
test_description='detect unwritable repository and fail correctly'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success setup '
@@ -21,7 +22,7 @@ test_expect_success POSIXPERM,SANITY 'write-tree should notice unwritable reposi
test_must_fail git write-tree
'
-test_expect_success POSIXPERM,SANITY 'commit should notice unwritable repository' '
+test_expect_success POSIXPERM,SANITY,!SANITIZE_LEAK 'commit should notice unwritable repository' '
test_when_finished "chmod 775 .git/objects .git/objects/??" &&
chmod a-w .git/objects .git/objects/?? &&
test_must_fail git commit -m second
diff --git a/t/t0011-hashmap.sh b/t/t0011-hashmap.sh
index 5343ffd3f9..e094975b13 100755
--- a/t/t0011-hashmap.sh
+++ b/t/t0011-hashmap.sh
@@ -1,6 +1,8 @@
#!/bin/sh
test_description='test hashmap and string hash functions'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_hashmap() {
diff --git a/t/t0012-help.sh b/t/t0012-help.sh
index 5679e29c62..91b68c74a1 100755
--- a/t/t0012-help.sh
+++ b/t/t0012-help.sh
@@ -34,6 +34,18 @@ test_expect_success 'basic help commands' '
git help -a >/dev/null
'
+test_expect_success 'invalid usage' '
+ test_expect_code 129 git help -g add &&
+ test_expect_code 129 git help -a -c &&
+
+ test_expect_code 129 git help -g add &&
+ test_expect_code 129 git help -a -g &&
+
+ test_expect_code 129 git help -g -c &&
+ test_expect_code 129 git help --config-for-completion add &&
+ test_expect_code 129 git help --config-sections-for-completion add
+'
+
test_expect_success "works for commands and guides by default" '
configure_help &&
git help status &&
@@ -73,6 +85,59 @@ test_expect_success 'git help -g' '
test_i18ngrep "^ tutorial " help.output
'
+test_expect_success 'git help fails for non-existing html pages' '
+ configure_help &&
+ mkdir html-empty &&
+ test_must_fail git -c help.htmlpath=html-empty help status &&
+ test_must_be_empty test-browser.log
+'
+
+test_expect_success 'git help succeeds without git.html' '
+ configure_help &&
+ mkdir html-with-docs &&
+ touch html-with-docs/git-status.html &&
+ git -c help.htmlpath=html-with-docs help status &&
+ echo "html-with-docs/git-status.html" >expect &&
+ test_cmp expect test-browser.log
+'
+
+test_expect_success 'git help -c' '
+ git help -c >help.output &&
+ cat >expect <<-\EOF &&
+
+ '\''git help config'\'' for more information
+ EOF
+ grep -v -E \
+ -e "^[^.]+\.[^.]+$" \
+ -e "^[^.]+\.[^.]+\.[^.]+$" \
+ help.output >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'git help --config-for-completion' '
+ git help -c >human &&
+ grep -E \
+ -e "^[^.]+\.[^.]+$" \
+ -e "^[^.]+\.[^.]+\.[^.]+$" human |
+ sed -e "s/\*.*//" -e "s/<.*//" |
+ sort -u >human.munged &&
+
+ git help --config-for-completion >vars &&
+ test_cmp human.munged vars
+'
+
+test_expect_success 'git help --config-sections-for-completion' '
+ git help -c >human &&
+ grep -E \
+ -e "^[^.]+\.[^.]+$" \
+ -e "^[^.]+\.[^.]+\.[^.]+$" human |
+ sed -e "s/\..*//" |
+ sort -u >human.munged &&
+
+ git help --config-sections-for-completion >sections &&
+ test_cmp human.munged sections
+'
+
test_expect_success 'generate builtin list' '
git --list-cmds=builtins >builtins
'
diff --git a/t/t0016-oidmap.sh b/t/t0016-oidmap.sh
index 31f8276ba8..0faef1f4f1 100755
--- a/t/t0016-oidmap.sh
+++ b/t/t0016-oidmap.sh
@@ -1,6 +1,8 @@
#!/bin/sh
test_description='test oidmap'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
# This purposefully is very similar to t0011-hashmap.sh
diff --git a/t/t0017-env-helper.sh b/t/t0017-env-helper.sh
index 4a159f99e4..2e42fba956 100755
--- a/t/t0017-env-helper.sh
+++ b/t/t0017-env-helper.sh
@@ -2,6 +2,7 @@
test_description='test env--helper'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
diff --git a/t/t0018-advice.sh b/t/t0018-advice.sh
index 39e5e4b34f..c13057a4ca 100755
--- a/t/t0018-advice.sh
+++ b/t/t0018-advice.sh
@@ -2,6 +2,7 @@
test_description='Test advise_if_enabled functionality'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'advice should be printed when config variable is unset' '
diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh
index b5749f327d..33dfc9cd56 100755
--- a/t/t0021-conversion.sh
+++ b/t/t0021-conversion.sh
@@ -6,6 +6,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
TEST_ROOT="$PWD"
PATH=$TEST_ROOT:$PATH
@@ -1061,4 +1062,74 @@ test_expect_success PERL,SYMLINKS,CASE_INSENSITIVE_FS \
)
'
+test_expect_success PERL 'setup for progress tests' '
+ git init progress &&
+ (
+ cd progress &&
+ git config filter.delay.process "rot13-filter.pl delay-progress.log clean smudge delay" &&
+ git config filter.delay.required true &&
+
+ echo "*.a filter=delay" >.gitattributes &&
+ touch test-delay10.a &&
+ git add . &&
+ git commit -m files
+ )
+'
+
+test_delayed_checkout_progress () {
+ if test "$1" = "!"
+ then
+ local expect_progress=N &&
+ shift
+ else
+ local expect_progress=
+ fi &&
+
+ if test $# -lt 1
+ then
+ BUG "no command given to test_delayed_checkout_progress"
+ fi &&
+
+ (
+ cd progress &&
+ GIT_PROGRESS_DELAY=0 &&
+ export GIT_PROGRESS_DELAY &&
+ rm -f *.a delay-progress.log &&
+
+ "$@" 2>err &&
+ grep "IN: smudge test-delay10.a .* \\[DELAYED\\]" delay-progress.log &&
+ if test "$expect_progress" = N
+ then
+ ! grep "Filtering content" err
+ else
+ grep "Filtering content" err
+ fi
+ )
+}
+
+for mode in pathspec branch
+do
+ case "$mode" in
+ pathspec) opt='.' ;;
+ branch) opt='-f HEAD' ;;
+ esac
+
+ test_expect_success PERL,TTY "delayed checkout shows progress by default on tty ($mode checkout)" '
+ test_delayed_checkout_progress test_terminal git checkout $opt
+ '
+
+ test_expect_success PERL "delayed checkout ommits progress on non-tty ($mode checkout)" '
+ test_delayed_checkout_progress ! git checkout $opt
+ '
+
+ test_expect_success PERL,TTY "delayed checkout ommits progress with --quiet ($mode checkout)" '
+ test_delayed_checkout_progress ! test_terminal git checkout --quiet $opt
+ '
+
+ test_expect_success PERL,TTY "delayed checkout honors --[no]-progress ($mode checkout)" '
+ test_delayed_checkout_progress ! test_terminal git checkout --no-progress $opt &&
+ test_delayed_checkout_progress test_terminal git checkout --quiet --progress $opt
+ '
+done
+
test_done
diff --git a/t/t0030-stripspace.sh b/t/t0030-stripspace.sh
index 0c24a0f9a3..ae1ca380c1 100755
--- a/t/t0030-stripspace.sh
+++ b/t/t0030-stripspace.sh
@@ -5,6 +5,7 @@
test_description='git stripspace'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
t40='A quick brown fox jumps over the lazy do'
diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh
index ad4746d899..da310ed29b 100755
--- a/t/t0040-parse-options.sh
+++ b/t/t0040-parse-options.sh
@@ -37,7 +37,6 @@ String options
--list <str> add str to list
Magic arguments
- --quux means --quux
-NUM set integer to NUM
+ same as -b
--ambiguous positive ambiguity
@@ -263,10 +262,6 @@ test_expect_success 'detect possible typos' '
test_cmp typo.err output.err
'
-test_expect_success 'keep some options as arguments' '
- test-tool parse-options --expect="arg 00: --quux" --quux
-'
-
cat >expect <<\EOF
Callback: "four", 0
boolean: 5
diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
index de4960783f..34d1061f32 100755
--- a/t/t0060-path-utils.sh
+++ b/t/t0060-path-utils.sh
@@ -525,4 +525,30 @@ test_expect_success MINGW 'is_valid_path() on Windows' '
"PRN./abc"
'
+test_lazy_prereq RUNTIME_PREFIX '
+ test true = "$RUNTIME_PREFIX"
+'
+
+test_lazy_prereq CAN_EXEC_IN_PWD '
+ cp "$GIT_EXEC_PATH"/git$X ./ &&
+ ./git rev-parse
+'
+
+test_expect_success RUNTIME_PREFIX,CAN_EXEC_IN_PWD 'RUNTIME_PREFIX works' '
+ mkdir -p pretend/bin pretend/libexec/git-core &&
+ echo "echo HERE" | write_script pretend/libexec/git-core/git-here &&
+ cp "$GIT_EXEC_PATH"/git$X pretend/bin/ &&
+ GIT_EXEC_PATH= ./pretend/bin/git here >actual &&
+ echo HERE >expect &&
+ test_cmp expect actual'
+
+test_expect_success RUNTIME_PREFIX,CAN_EXEC_IN_PWD '%(prefix)/ works' '
+ mkdir -p pretend/bin &&
+ cp "$GIT_EXEC_PATH"/git$X pretend/bin/ &&
+ git config yes.path "%(prefix)/yes" &&
+ GIT_EXEC_PATH= ./pretend/bin/git config --path yes.path >actual &&
+ echo "$(pwd)/pretend/yes" >expect &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t0063-string-list.sh b/t/t0063-string-list.sh
index c6ee9f66b1..46d4839194 100755
--- a/t/t0063-string-list.sh
+++ b/t/t0063-string-list.sh
@@ -5,6 +5,7 @@
test_description='Test string list functionality'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_split () {
diff --git a/t/t0071-sort.sh b/t/t0071-sort.sh
new file mode 100755
index 0000000000..a8ab174879
--- /dev/null
+++ b/t/t0071-sort.sh
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+test_description='verify sort functions'
+
+. ./test-lib.sh
+
+test_expect_success 'llist_mergesort()' '
+ test-tool mergesort test
+'
+
+test_done
diff --git a/t/t0090-cache-tree.sh b/t/t0090-cache-tree.sh
index 9bf66c9e68..9067572648 100755
--- a/t/t0090-cache-tree.sh
+++ b/t/t0090-cache-tree.sh
@@ -195,6 +195,7 @@ test_expect_success 'reset --hard gives cache-tree' '
test_expect_success 'reset --hard without index gives cache-tree' '
rm -f .git/index &&
+ git clean -fd &&
git reset --hard &&
test_cache_tree
'
diff --git a/t/t0091-bugreport.sh b/t/t0091-bugreport.sh
index 526304ff95..eeedbfa919 100755
--- a/t/t0091-bugreport.sh
+++ b/t/t0091-bugreport.sh
@@ -2,6 +2,7 @@
test_description='git bugreport'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
# Headers "[System Info]" will be followed by a non-empty line if we put some
diff --git a/t/t0210/scrub_normal.perl b/t/t0210/scrub_normal.perl
index c65d1a815e..7cc4de392a 100644
--- a/t/t0210/scrub_normal.perl
+++ b/t/t0210/scrub_normal.perl
@@ -42,6 +42,12 @@ while (<>) {
# so just omit it for testing purposes.
# print "cmd_path _EXE_\n";
}
+ elsif ($line =~ m/^cmd_ancestry/) {
+ # 'cmd_ancestry' is not implemented everywhere, so for portability's
+ # sake, skip it when parsing normal.
+ #
+ # print "$line";
+ }
else {
print "$line";
}
diff --git a/t/t0211/scrub_perf.perl b/t/t0211/scrub_perf.perl
index 351af7844e..d164b750ff 100644
--- a/t/t0211/scrub_perf.perl
+++ b/t/t0211/scrub_perf.perl
@@ -44,6 +44,11 @@ while (<>) {
# $tokens[$col_rest] = "_EXE_";
goto SKIP_LINE;
}
+ elsif ($tokens[$col_event] =~ m/cmd_ancestry/) {
+ # 'cmd_ancestry' is platform-specific and not implemented everywhere,
+ # so skip it.
+ goto SKIP_LINE;
+ }
elsif ($tokens[$col_event] =~ m/child_exit/) {
$tokens[$col_rest] =~ s/ pid:\d* / pid:_PID_ /;
}
diff --git a/t/t0212/parse_events.perl b/t/t0212/parse_events.perl
index 6584bb5634..b6408560c0 100644
--- a/t/t0212/parse_events.perl
+++ b/t/t0212/parse_events.perl
@@ -132,7 +132,10 @@ while (<>) {
# just omit it for testing purposes.
# $processes->{$sid}->{'path'} = "_EXE_";
}
-
+ elsif ($event eq 'cmd_ancestry') {
+ # 'cmd_ancestry' is platform-specific and not implemented everywhere, so
+ # just skip it for testing purposes.
+ }
elsif ($event eq 'cmd_name') {
$processes->{$sid}->{'name'} = $line->{'name'};
$processes->{$sid}->{'hierarchy'} = $line->{'hierarchy'};
diff --git a/t/t0301-credential-cache.sh b/t/t0301-credential-cache.sh
index ebd5fa5249..698b7159f0 100755
--- a/t/t0301-credential-cache.sh
+++ b/t/t0301-credential-cache.sh
@@ -9,6 +9,21 @@ test -z "$NO_UNIX_SOCKETS" || {
test_done
}
+uname_s=$(uname -s)
+case $uname_s in
+*MINGW*)
+ test_path_is_socket () {
+ # `test -S` cannot detect Win10's Unix sockets
+ test_path_exists "$1"
+ }
+ ;;
+*)
+ test_path_is_socket () {
+ test -S "$1"
+ }
+ ;;
+esac
+
# don't leave a stale daemon running
test_atexit 'git credential-cache exit'
@@ -21,7 +36,7 @@ test_expect_success 'socket defaults to ~/.cache/git/credential/socket' '
rmdir -p .cache/git/credential/
" &&
test_path_is_missing "$HOME/.git-credential-cache" &&
- test -S "$HOME/.cache/git/credential/socket"
+ test_path_is_socket "$HOME/.cache/git/credential/socket"
'
XDG_CACHE_HOME="$HOME/xdg"
@@ -31,7 +46,7 @@ helper_test cache
test_expect_success "use custom XDG_CACHE_HOME if set and default sockets are not created" '
test_when_finished "git credential-cache exit" &&
- test -S "$XDG_CACHE_HOME/git/credential/socket" &&
+ test_path_is_socket "$XDG_CACHE_HOME/git/credential/socket" &&
test_path_is_missing "$HOME/.git-credential-cache/socket" &&
test_path_is_missing "$HOME/.cache/git/credential/socket"
'
@@ -48,7 +63,7 @@ test_expect_success 'credential-cache --socket option overrides default location
username=store-user
password=store-pass
EOF
- test -S "$HOME/dir/socket"
+ test_path_is_socket "$HOME/dir/socket"
'
test_expect_success "use custom XDG_CACHE_HOME even if xdg socket exists" '
@@ -62,7 +77,7 @@ test_expect_success "use custom XDG_CACHE_HOME even if xdg socket exists" '
username=store-user
password=store-pass
EOF
- test -S "$HOME/.cache/git/credential/socket" &&
+ test_path_is_socket "$HOME/.cache/git/credential/socket" &&
XDG_CACHE_HOME="$HOME/xdg" &&
export XDG_CACHE_HOME &&
check approve cache <<-\EOF &&
@@ -71,7 +86,7 @@ test_expect_success "use custom XDG_CACHE_HOME even if xdg socket exists" '
username=store-user
password=store-pass
EOF
- test -S "$XDG_CACHE_HOME/git/credential/socket"
+ test_path_is_socket "$XDG_CACHE_HOME/git/credential/socket"
'
test_expect_success 'use user socket if user directory exists' '
@@ -79,14 +94,15 @@ test_expect_success 'use user socket if user directory exists' '
git credential-cache exit &&
rmdir \"\$HOME/.git-credential-cache/\"
" &&
- mkdir -p -m 700 "$HOME/.git-credential-cache/" &&
+ mkdir -p "$HOME/.git-credential-cache/" &&
+ chmod 700 "$HOME/.git-credential-cache/" &&
check approve cache <<-\EOF &&
protocol=https
host=example.com
username=store-user
password=store-pass
EOF
- test -S "$HOME/.git-credential-cache/socket"
+ test_path_is_socket "$HOME/.git-credential-cache/socket"
'
test_expect_success SYMLINKS 'use user socket if user directory is a symlink to a directory' '
@@ -103,7 +119,7 @@ test_expect_success SYMLINKS 'use user socket if user directory is a symlink to
username=store-user
password=store-pass
EOF
- test -S "$HOME/.git-credential-cache/socket"
+ test_path_is_socket "$HOME/.git-credential-cache/socket"
'
helper_test_timeout cache --timeout=1
diff --git a/t/t1013-read-tree-submodule.sh b/t/t1013-read-tree-submodule.sh
index b6df7444c0..bfc90d4cf2 100755
--- a/t/t1013-read-tree-submodule.sh
+++ b/t/t1013-read-tree-submodule.sh
@@ -6,7 +6,6 @@ test_description='read-tree can handle submodules'
. "$TEST_DIRECTORY"/lib-submodule-update.sh
KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1
-KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED=1
test_submodule_switch_recursing_with_args "read-tree -u -m"
diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh
index 38fc8340f5..272ba1b566 100755
--- a/t/t1091-sparse-checkout-builtin.sh
+++ b/t/t1091-sparse-checkout-builtin.sh
@@ -206,16 +206,21 @@ test_expect_success 'sparse-checkout disable' '
'
test_expect_success 'sparse-index enabled and disabled' '
- git -C repo sparse-checkout init --cone --sparse-index &&
- test_cmp_config -C repo true index.sparse &&
- test-tool -C repo read-cache --table >cache &&
- grep " tree " cache &&
-
- git -C repo sparse-checkout disable &&
- test-tool -C repo read-cache --table >cache &&
- ! grep " tree " cache &&
- git -C repo config --list >config &&
- ! grep index.sparse config
+ (
+ sane_unset GIT_TEST_SPLIT_INDEX &&
+ git -C repo update-index --no-split-index &&
+
+ git -C repo sparse-checkout init --cone --sparse-index &&
+ test_cmp_config -C repo true index.sparse &&
+ test-tool -C repo read-cache --table >cache &&
+ grep " tree " cache &&
+
+ git -C repo sparse-checkout disable &&
+ test-tool -C repo read-cache --table >cache &&
+ ! grep " tree " cache &&
+ git -C repo config --list >config &&
+ ! grep index.sparse config
+ )
'
test_expect_success 'cone mode: init and set' '
@@ -406,7 +411,7 @@ test_expect_success 'sparse-checkout (init|set|disable) warns with unmerged stat
git -C unmerged sparse-checkout disable
'
-test_expect_success 'sparse-checkout reapply' '
+test_expect_failure 'sparse-checkout reapply' '
git clone repo tweak &&
echo dirty >tweak/deep/deeper2/a &&
@@ -438,6 +443,8 @@ test_expect_success 'sparse-checkout reapply' '
test_i18ngrep "warning.*The following paths are unmerged" err &&
test_path_is_file tweak/folder1/a &&
+ # NEEDSWORK: We are asking to update a file outside of the
+ # sparse-checkout cone, but this is no longer allowed.
git -C tweak add folder1/a &&
git -C tweak sparse-checkout reapply 2>err &&
test_must_be_empty err &&
@@ -642,4 +649,63 @@ test_expect_success MINGW 'cone mode replaces backslashes with slashes' '
check_files repo/deep a deeper1
'
+test_expect_success 'cone mode clears ignored subdirectories' '
+ rm repo/.git/info/sparse-checkout &&
+
+ git -C repo sparse-checkout init --cone &&
+ git -C repo sparse-checkout set deep/deeper1 &&
+
+ cat >repo/.gitignore <<-\EOF &&
+ obj/
+ *.o
+ EOF
+
+ git -C repo add .gitignore &&
+ git -C repo commit -m ".gitignore" &&
+
+ mkdir -p repo/obj repo/folder1/obj repo/deep/deeper2/obj &&
+ for file in folder1/obj/a obj/a folder1/file.o folder1.o \
+ deep/deeper2/obj/a deep/deeper2/file.o file.o
+ do
+ echo ignored >repo/$file || return 1
+ done &&
+
+ git -C repo status --porcelain=v2 >out &&
+ test_must_be_empty out &&
+
+ git -C repo sparse-checkout reapply &&
+ test_path_is_missing repo/folder1 &&
+ test_path_is_missing repo/deep/deeper2 &&
+ test_path_is_dir repo/obj &&
+ test_path_is_file repo/file.o &&
+
+ git -C repo status --porcelain=v2 >out &&
+ test_must_be_empty out &&
+
+ git -C repo sparse-checkout set deep/deeper2 &&
+ test_path_is_missing repo/deep/deeper1 &&
+ test_path_is_dir repo/deep/deeper2 &&
+ test_path_is_dir repo/obj &&
+ test_path_is_file repo/file.o &&
+
+ >repo/deep/deeper2/ignored.o &&
+ >repo/deep/deeper2/untracked &&
+
+ # When an untracked file is in the way, all untracked files
+ # (even ignored files) are preserved.
+ git -C repo sparse-checkout set folder1 2>err &&
+ grep "contains untracked files" err &&
+ test_path_is_file repo/deep/deeper2/ignored.o &&
+ test_path_is_file repo/deep/deeper2/untracked &&
+
+ # The rest of the cone matches expectation
+ test_path_is_missing repo/deep/deeper1 &&
+ test_path_is_dir repo/obj &&
+ test_path_is_file repo/file.o &&
+
+ git -C repo status --porcelain=v2 >out &&
+ echo "? deep/deeper2/untracked" >expect &&
+ test_cmp expect out
+'
+
test_done
diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh
index 91e30d6ec2..ca91c6a67f 100755
--- a/t/t1092-sparse-checkout-compatibility.sh
+++ b/t/t1092-sparse-checkout-compatibility.sh
@@ -47,7 +47,7 @@ test_expect_success 'setup' '
git checkout -b base &&
for dir in folder1 folder2 deep
do
- git checkout -b update-$dir &&
+ git checkout -b update-$dir base &&
echo "updated $dir" >$dir/a &&
git commit -a -m "update $dir" || return 1
done &&
@@ -114,6 +114,16 @@ test_expect_success 'setup' '
git add . &&
git commit -m "file to dir" &&
+ for side in left right
+ do
+ git checkout -b merge-$side base &&
+ echo $side >>deep/deeper2/a &&
+ echo $side >>folder1/a &&
+ echo $side >>folder2/a &&
+ git add . &&
+ git commit -m "$side" || return 1
+ done &&
+
git checkout -b deepest base &&
echo "updated deepest" >deep/deeper1/deepest/a &&
git commit -a -m "update deepest" &&
@@ -177,6 +187,16 @@ test_sparse_match () {
test_cmp sparse-checkout-err sparse-index-err
}
+test_sparse_unstaged () {
+ file=$1 &&
+ for repo in sparse-checkout sparse-index
+ do
+ # Skip "unmerged" paths
+ git -C $repo diff --staged --diff-filter=u -- "$file" >diff &&
+ test_must_be_empty diff || return 1
+ done
+}
+
test_expect_success 'sparse-index contents' '
init_repos &&
@@ -281,6 +301,20 @@ test_expect_success 'add, commit, checkout' '
test_all_match git checkout -
'
+test_expect_success 'add outside sparse cone' '
+ init_repos &&
+
+ run_on_sparse mkdir folder1 &&
+ run_on_sparse ../edit-contents folder1/a &&
+ run_on_sparse ../edit-contents folder1/newfile &&
+ test_sparse_match test_must_fail git add folder1/a &&
+ grep "Disable or modify the sparsity rules" sparse-checkout-err &&
+ test_sparse_unstaged folder1/a &&
+ test_sparse_match test_must_fail git add folder1/newfile &&
+ grep "Disable or modify the sparsity rules" sparse-checkout-err &&
+ test_sparse_unstaged folder1/newfile
+'
+
test_expect_success 'commit including unstaged changes' '
init_repos &&
@@ -312,9 +346,6 @@ test_expect_success 'commit including unstaged changes' '
test_expect_success 'status/add: outside sparse cone' '
init_repos &&
- # adding a "missing" file outside the cone should fail
- test_sparse_match test_must_fail git add folder1/a &&
-
# folder1 is at HEAD, but outside the sparse cone
run_on_sparse mkdir folder1 &&
cp initial-repo/folder1/a sparse-checkout/folder1/a &&
@@ -330,21 +361,29 @@ test_expect_success 'status/add: outside sparse cone' '
test_sparse_match git status --porcelain=v2 &&
- # This "git add folder1/a" fails with a warning
- # in the sparse repos, differing from the full
- # repo. This is intentional.
+ # Adding the path outside of the sparse-checkout cone should fail.
test_sparse_match test_must_fail git add folder1/a &&
+ grep "Disable or modify the sparsity rules" sparse-checkout-err &&
+ test_sparse_unstaged folder1/a &&
test_sparse_match test_must_fail git add --refresh folder1/a &&
- test_all_match git status --porcelain=v2 &&
-
- test_all_match git add . &&
+ grep "Disable or modify the sparsity rules" sparse-checkout-err &&
+ test_sparse_unstaged folder1/a &&
+ test_sparse_match test_must_fail git add folder1/new &&
+ grep "Disable or modify the sparsity rules" sparse-checkout-err &&
+ test_sparse_unstaged folder1/new &&
+ test_sparse_match git add --sparse folder1/a &&
+ test_sparse_match git add --sparse folder1/new &&
+
+ test_all_match git add --sparse . &&
test_all_match git status --porcelain=v2 &&
test_all_match git commit -m folder1/new &&
+ test_all_match git rev-parse HEAD^{tree} &&
run_on_all ../edit-contents folder1/newer &&
- test_all_match git add folder1/ &&
+ test_all_match git add --sparse folder1/ &&
test_all_match git status --porcelain=v2 &&
- test_all_match git commit -m folder1/newer
+ test_all_match git commit -m folder1/newer &&
+ test_all_match git rev-parse HEAD^{tree}
'
test_expect_success 'checkout and reset --hard' '
@@ -472,16 +511,97 @@ test_expect_success 'checkout and reset (mixed) [sparse]' '
test_sparse_match git reset update-folder2
'
-test_expect_success 'merge' '
+test_expect_success 'merge, cherry-pick, and rebase' '
init_repos &&
- test_all_match git checkout -b merge update-deep &&
- test_all_match git merge -m "folder1" update-folder1 &&
- test_all_match git rev-parse HEAD^{tree} &&
- test_all_match git merge -m "folder2" update-folder2 &&
+ for OPERATION in "merge -m merge" cherry-pick rebase
+ do
+ test_all_match git checkout -B temp update-deep &&
+ test_all_match git $OPERATION update-folder1 &&
+ test_all_match git rev-parse HEAD^{tree} &&
+ test_all_match git $OPERATION update-folder2 &&
+ test_all_match git rev-parse HEAD^{tree} || return 1
+ done
+'
+
+test_expect_success 'merge with conflict outside cone' '
+ init_repos &&
+
+ test_all_match git checkout -b merge-tip merge-left &&
+ test_all_match git status --porcelain=v2 &&
+ test_all_match test_must_fail git merge -m merge merge-right &&
+ test_all_match git status --porcelain=v2 &&
+
+ # Resolve the conflict in different ways:
+ # 1. Revert to the base
+ test_all_match git checkout base -- deep/deeper2/a &&
+ test_all_match git status --porcelain=v2 &&
+
+ # 2. Add the file with conflict markers
+ test_sparse_match test_must_fail git add folder1/a &&
+ grep "Disable or modify the sparsity rules" sparse-checkout-err &&
+ test_sparse_unstaged folder1/a &&
+ test_all_match git add --sparse folder1/a &&
+ test_all_match git status --porcelain=v2 &&
+
+ # 3. Rename the file to another sparse filename and
+ # accept conflict markers as resolved content.
+ run_on_all mv folder2/a folder2/z &&
+ test_sparse_match test_must_fail git add folder2 &&
+ grep "Disable or modify the sparsity rules" sparse-checkout-err &&
+ test_sparse_unstaged folder2/z &&
+ test_all_match git add --sparse folder2 &&
+ test_all_match git status --porcelain=v2 &&
+
+ test_all_match git merge --continue &&
+ test_all_match git status --porcelain=v2 &&
test_all_match git rev-parse HEAD^{tree}
'
+test_expect_success 'cherry-pick/rebase with conflict outside cone' '
+ init_repos &&
+
+ for OPERATION in cherry-pick rebase
+ do
+ test_all_match git checkout -B tip &&
+ test_all_match git reset --hard merge-left &&
+ test_all_match git status --porcelain=v2 &&
+ test_all_match test_must_fail git $OPERATION merge-right &&
+ test_all_match git status --porcelain=v2 &&
+
+ # Resolve the conflict in different ways:
+ # 1. Revert to the base
+ test_all_match git checkout base -- deep/deeper2/a &&
+ test_all_match git status --porcelain=v2 &&
+
+ # 2. Add the file with conflict markers
+ # NEEDSWORK: Even though the merge conflict removed the
+ # SKIP_WORKTREE bit from the index entry for folder1/a, we should
+ # warn that this is a problematic add.
+ test_sparse_match test_must_fail git add folder1/a &&
+ grep "Disable or modify the sparsity rules" sparse-checkout-err &&
+ test_sparse_unstaged folder1/a &&
+ test_all_match git add --sparse folder1/a &&
+ test_all_match git status --porcelain=v2 &&
+
+ # 3. Rename the file to another sparse filename and
+ # accept conflict markers as resolved content.
+ # NEEDSWORK: This mode now fails, because folder2/z is
+ # outside of the sparse-checkout cone and does not match an
+ # existing index entry with the SKIP_WORKTREE bit cleared.
+ run_on_all mv folder2/a folder2/z &&
+ test_sparse_match test_must_fail git add folder2 &&
+ grep "Disable or modify the sparsity rules" sparse-checkout-err &&
+ test_sparse_unstaged folder2/z &&
+ test_all_match git add --sparse folder2 &&
+ test_all_match git status --porcelain=v2 &&
+
+ test_all_match git $OPERATION --continue &&
+ test_all_match git status --porcelain=v2 &&
+ test_all_match git rev-parse HEAD^{tree} || return 1
+ done
+'
+
test_expect_success 'merge with outside renames' '
init_repos &&
@@ -549,6 +669,7 @@ test_expect_success 'clean' '
test_expect_success 'submodule handling' '
init_repos &&
+ test_sparse_match git sparse-checkout add modules &&
test_all_match mkdir modules &&
test_all_match touch modules/a &&
test_all_match git add modules &&
@@ -558,6 +679,7 @@ test_expect_success 'submodule handling' '
test_all_match git commit -m "add submodule" &&
# having a submodule prevents "modules" from collapse
+ test_sparse_match git sparse-checkout set deep/deeper1 &&
test-tool -C sparse-index read-cache --table >cache &&
grep "100644 blob .* modules/a" cache &&
grep "160000 commit $(git -C initial-repo rev-parse HEAD) modules/sub" cache
@@ -575,8 +697,17 @@ test_expect_success 'sparse-index is expanded and converted back' '
ensure_not_expanded () {
rm -f trace2.txt &&
echo >>sparse-index/untracked.txt &&
- GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
- git -C sparse-index "$@" &&
+
+ if test "$1" = "!"
+ then
+ shift &&
+ test_must_fail env \
+ GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
+ git -C sparse-index "$@" || return 1
+ else
+ GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
+ git -C sparse-index "$@" || return 1
+ fi &&
test_region ! index ensure_full_index trace2.txt
}
@@ -598,7 +729,42 @@ test_expect_success 'sparse-index is not expanded' '
git -C sparse-index reset --hard &&
ensure_not_expanded checkout rename-out-to-out -- deep/deeper1 &&
git -C sparse-index reset --hard &&
- ensure_not_expanded restore -s rename-out-to-out -- deep/deeper1
+ ensure_not_expanded restore -s rename-out-to-out -- deep/deeper1 &&
+
+ echo >>sparse-index/README.md &&
+ ensure_not_expanded add -A &&
+ echo >>sparse-index/extra.txt &&
+ ensure_not_expanded add extra.txt &&
+ echo >>sparse-index/untracked.txt &&
+ ensure_not_expanded add . &&
+
+ ensure_not_expanded checkout -f update-deep &&
+ test_config -C sparse-index pull.twohead ort &&
+ (
+ sane_unset GIT_TEST_MERGE_ALGORITHM &&
+ for OPERATION in "merge -m merge" cherry-pick rebase
+ do
+ ensure_not_expanded merge -m merge update-folder1 &&
+ ensure_not_expanded merge -m merge update-folder2 || return 1
+ done
+ )
+'
+
+test_expect_success 'sparse-index is not expanded: merge conflict in cone' '
+ init_repos &&
+
+ for side in right left
+ do
+ git -C sparse-index checkout -b expand-$side base &&
+ echo $side >sparse-index/deep/a &&
+ git -C sparse-index commit -a -m "$side" || return 1
+ done &&
+
+ (
+ sane_unset GIT_TEST_MERGE_ALGORITHM &&
+ git -C sparse-index config pull.twohead ort &&
+ ensure_not_expanded ! merge -m merged expand-right
+ )
'
# NEEDSWORK: a sparse-checkout behaves differently from a full checkout
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index 4506cd435b..0d4f73acaa 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -1598,6 +1598,40 @@ test_expect_success 'transaction cannot restart ongoing transaction' '
test_must_fail git show-ref --verify refs/heads/restart
'
+test_expect_success PIPE 'transaction flushes status updates' '
+ mkfifo in out &&
+ (git update-ref --stdin <in >out &) &&
+
+ exec 9>in &&
+ exec 8<out &&
+ test_when_finished "exec 9>&-" &&
+ test_when_finished "exec 8<&-" &&
+
+ echo "start" >&9 &&
+ echo "start: ok" >expected &&
+ read line <&8 &&
+ echo "$line" >actual &&
+ test_cmp expected actual &&
+
+ echo "create refs/heads/flush $A" >&9 &&
+
+ echo prepare >&9 &&
+ echo "prepare: ok" >expected &&
+ read line <&8 &&
+ echo "$line" >actual &&
+ test_cmp expected actual &&
+
+ # This must now fail given that we have locked the ref.
+ test_must_fail git update-ref refs/heads/flush $B 2>stderr &&
+ grep "fatal: update_ref failed for ref ${SQ}refs/heads/flush${SQ}: cannot lock ref" stderr &&
+
+ echo commit >&9 &&
+ echo "commit: ok" >expected &&
+ read line <&8 &&
+ echo "$line" >actual &&
+ test_cmp expected actual
+'
+
test_expect_success 'directory not created deleting packed ref' '
git branch d1/d2/r1 HEAD &&
git pack-refs --all &&
diff --git a/t/t1405-main-ref-store.sh b/t/t1405-main-ref-store.sh
index a237d9880e..49718b7ea7 100755
--- a/t/t1405-main-ref-store.sh
+++ b/t/t1405-main-ref-store.sh
@@ -9,12 +9,18 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
RUN="test-tool ref-store main"
-test_expect_success 'pack_refs(PACK_REFS_ALL | PACK_REFS_PRUNE)' '
- test_commit one &&
+
+test_expect_success 'setup' '
+ test_commit one
+'
+
+test_expect_success REFFILES 'pack_refs(PACK_REFS_ALL | PACK_REFS_PRUNE)' '
N=`find .git/refs -type f | wc -l` &&
test "$N" != 0 &&
- $RUN pack-refs 3 &&
- N=`find .git/refs -type f | wc -l`
+ ALL_OR_PRUNE_FLAG=3 &&
+ $RUN pack-refs ${ALL_OR_PRUNE_FLAG} &&
+ N=`find .git/refs -type f` &&
+ test -z "$N"
'
test_expect_success 'create_symref(FOO, refs/heads/main)' '
@@ -98,12 +104,12 @@ test_expect_success 'reflog_exists(HEAD)' '
test_expect_success 'delete_reflog(HEAD)' '
$RUN delete-reflog HEAD &&
- ! test -f .git/logs/HEAD
+ test_must_fail git reflog exists HEAD
'
test_expect_success 'create-reflog(HEAD)' '
$RUN create-reflog HEAD 1 &&
- test -f .git/logs/HEAD
+ git reflog exists HEAD
'
test_expect_success 'delete_ref(refs/heads/foo)' '
diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh
index 27b9080251..d42f067ff8 100755
--- a/t/t1410-reflog.sh
+++ b/t/t1410-reflog.sh
@@ -374,7 +374,9 @@ test_expect_failure 'reflog with non-commit entries displays all entries' '
test_line_count = 3 actual
'
-test_expect_success 'reflog expire operates on symref not referrent' '
+# This test takes a lock on an individual ref; this is not supported in
+# reftable.
+test_expect_success REFFILES 'reflog expire operates on symref not referrent' '
git branch --create-reflog the_symref &&
git branch --create-reflog referrent &&
git update-ref referrent HEAD &&
diff --git a/t/t1430-bad-ref-name.sh b/t/t1430-bad-ref-name.sh
index b1839e0877..fa3aeb80f2 100755
--- a/t/t1430-bad-ref-name.sh
+++ b/t/t1430-bad-ref-name.sh
@@ -170,7 +170,7 @@ test_expect_success 'for-each-ref emits warnings for broken names' '
! grep -e "badname" output &&
! grep -e "broken\.\.\.symref" output &&
test_i18ngrep "ignoring ref with broken name refs/heads/broken\.\.\.ref" error &&
- test_i18ngrep "ignoring broken ref refs/heads/badname" error &&
+ test_i18ngrep ! "ignoring broken ref refs/heads/badname" error &&
test_i18ngrep "ignoring ref with broken name refs/heads/broken\.\.\.symref" error
'
diff --git a/t/t1502-rev-parse-parseopt.sh b/t/t1502-rev-parse-parseopt.sh
index b29563fc99..284fe18e72 100755
--- a/t/t1502-rev-parse-parseopt.sh
+++ b/t/t1502-rev-parse-parseopt.sh
@@ -282,4 +282,58 @@ test_expect_success 'test --parseopt --stuck-long and short option with unset op
test_cmp expect output
'
+test_expect_success 'test --parseopt help output: "wrapped" options normal "or:" lines' '
+ sed -e "s/^|//" >spec <<-\EOF &&
+ |cmd [--some-option]
+ | [--another-option]
+ |cmd [--yet-another-option]
+ |--
+ |h,help show the help
+ EOF
+
+ sed -e "s/^|//" >expect <<-\END_EXPECT &&
+ |cat <<\EOF
+ |usage: cmd [--some-option]
+ | or: [--another-option]
+ | or: cmd [--yet-another-option]
+ |
+ | -h, --help show the help
+ |
+ |EOF
+ END_EXPECT
+
+ test_must_fail git rev-parse --parseopt -- -h >out <spec >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'test --parseopt help output: multi-line blurb after empty line' '
+ sed -e "s/^|//" >spec <<-\EOF &&
+ |cmd [--some-option]
+ | [--another-option]
+ |
+ |multi
+ |line
+ |blurb
+ |--
+ |h,help show the help
+ EOF
+
+ sed -e "s/^|//" >expect <<-\END_EXPECT &&
+ |cat <<\EOF
+ |usage: cmd [--some-option]
+ | or: [--another-option]
+ |
+ | multi
+ | line
+ | blurb
+ |
+ | -h, --help show the help
+ |
+ |EOF
+ END_EXPECT
+
+ test_must_fail git rev-parse --parseopt -- -h >out <spec >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t1503-rev-parse-verify.sh b/t/t1503-rev-parse-verify.sh
index bf08102391..40958615eb 100755
--- a/t/t1503-rev-parse-verify.sh
+++ b/t/t1503-rev-parse-verify.sh
@@ -142,7 +142,7 @@ test_expect_success 'main@{n} for various n' '
test_must_fail git rev-parse --verify main@{$Np1}
'
-test_expect_success SYMLINKS 'ref resolution not confused by broken symlinks' '
+test_expect_success SYMLINKS,REFFILES 'ref resolution not confused by broken symlinks' '
ln -s does-not-exist .git/refs/heads/broken &&
test_must_fail git rev-parse --verify broken
'
diff --git a/t/t1600-index.sh b/t/t1600-index.sh
index c9b9e718b8..46329c488b 100755
--- a/t/t1600-index.sh
+++ b/t/t1600-index.sh
@@ -4,6 +4,8 @@ test_description='index file specific tests'
. ./test-lib.sh
+sane_unset GIT_TEST_SPLIT_INDEX
+
test_expect_success 'setup' '
echo 1 >a
'
@@ -13,7 +15,8 @@ test_expect_success 'bogus GIT_INDEX_VERSION issues warning' '
rm -f .git/index &&
GIT_INDEX_VERSION=2bogus &&
export GIT_INDEX_VERSION &&
- git add a 2>&1 | sed "s/[0-9]//" >actual.err &&
+ git add a 2>err &&
+ sed "s/[0-9]//" err >actual.err &&
sed -e "s/ Z$/ /" <<-\EOF >expect.err &&
warning: GIT_INDEX_VERSION set, but the value is invalid.
Using version Z
@@ -27,7 +30,8 @@ test_expect_success 'out of bounds GIT_INDEX_VERSION issues warning' '
rm -f .git/index &&
GIT_INDEX_VERSION=1 &&
export GIT_INDEX_VERSION &&
- git add a 2>&1 | sed "s/[0-9]//" >actual.err &&
+ git add a 2>err &&
+ sed "s/[0-9]//" err >actual.err &&
sed -e "s/ Z$/ /" <<-\EOF >expect.err &&
warning: GIT_INDEX_VERSION set, but the value is invalid.
Using version Z
@@ -50,7 +54,8 @@ test_expect_success 'out of bounds index.version issues warning' '
sane_unset GIT_INDEX_VERSION &&
rm -f .git/index &&
git config --add index.version 1 &&
- git add a 2>&1 | sed "s/[0-9]//" >actual.err &&
+ git add a 2>err &&
+ sed "s/[0-9]//" err >actual.err &&
sed -e "s/ Z$/ /" <<-\EOF >expect.err &&
warning: index.version set, but the value is invalid.
Using version Z
@@ -79,7 +84,7 @@ test_index_version () {
else
unset GIT_INDEX_VERSION
fi &&
- git add a 2>&1 &&
+ git add a &&
echo $EXPECTED_OUTPUT_VERSION >expect &&
test-tool index-version <.git/index >actual &&
test_cmp expect actual
diff --git a/t/t1700-split-index.sh b/t/t1700-split-index.sh
index 986baa612e..decd2527ed 100755
--- a/t/t1700-split-index.sh
+++ b/t/t1700-split-index.sh
@@ -510,4 +510,38 @@ test_expect_success 'do not refresh null base index' '
)
'
+test_expect_success 'reading split index at alternate location' '
+ git init reading-alternate-location &&
+ (
+ cd reading-alternate-location &&
+ >file-in-alternate &&
+ git update-index --split-index --add file-in-alternate
+ ) &&
+ echo file-in-alternate >expect &&
+
+ # Should be able to find the shared index both right next to
+ # the specified split index file ...
+ GIT_INDEX_FILE=./reading-alternate-location/.git/index \
+ git ls-files --cached >actual &&
+ test_cmp expect actual &&
+
+ # ... and, for backwards compatibility, in the current GIT_DIR
+ # as well.
+ mv -v ./reading-alternate-location/.git/sharedindex.* .git &&
+ GIT_INDEX_FILE=./reading-alternate-location/.git/index \
+ git ls-files --cached >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'GIT_TEST_SPLIT_INDEX works' '
+ git init git-test-split-index &&
+ (
+ cd git-test-split-index &&
+ >file &&
+ GIT_TEST_SPLIT_INDEX=1 git update-index --add file &&
+ ls -l .git/sharedindex.* >actual &&
+ test_line_count = 1 actual
+ )
+'
+
test_done
diff --git a/t/t2021-checkout-overwrite.sh b/t/t2021-checkout-overwrite.sh
index 70d69263e6..660132ff8d 100755
--- a/t/t2021-checkout-overwrite.sh
+++ b/t/t2021-checkout-overwrite.sh
@@ -48,6 +48,7 @@ test_expect_success 'checkout commit with dir must not remove untracked a/b' '
test_expect_success SYMLINKS 'the symlink remained' '
+ test_when_finished "rm a/b" &&
test -h a/b
'
diff --git a/t/t2402-worktree-list.sh b/t/t2402-worktree-list.sh
index fedcefe8de..4012bd67b0 100755
--- a/t/t2402-worktree-list.sh
+++ b/t/t2402-worktree-list.sh
@@ -230,7 +230,7 @@ test_expect_success 'broken main worktree still at the top' '
EOF
cd linked &&
echo "worktree $(pwd)" >expected &&
- echo "ref: .broken" >../.git/HEAD &&
+ (cd ../ && test-tool ref-store main create-symref HEAD .broken ) &&
git worktree list --porcelain >out &&
head -n 3 out >actual &&
test_cmp ../expected actual &&
diff --git a/t/t2500-untracked-overwriting.sh b/t/t2500-untracked-overwriting.sh
new file mode 100755
index 0000000000..5c0bf4d21f
--- /dev/null
+++ b/t/t2500-untracked-overwriting.sh
@@ -0,0 +1,244 @@
+#!/bin/sh
+
+test_description='Test handling of overwriting untracked files'
+
+. ./test-lib.sh
+
+test_setup_reset () {
+ git init reset_$1 &&
+ (
+ cd reset_$1 &&
+ test_commit init &&
+
+ git branch stable &&
+ git branch work &&
+
+ git checkout work &&
+ test_commit foo &&
+
+ git checkout stable
+ )
+}
+
+test_expect_success 'reset --hard will nuke untracked files/dirs' '
+ test_setup_reset hard &&
+ (
+ cd reset_hard &&
+ git ls-tree -r stable &&
+ git log --all --name-status --oneline &&
+ git ls-tree -r work &&
+
+ mkdir foo.t &&
+ echo precious >foo.t/file &&
+ echo foo >expect &&
+
+ git reset --hard work &&
+
+ # check that untracked directory foo.t/ was nuked
+ test_path_is_file foo.t &&
+ test_cmp expect foo.t
+ )
+'
+
+test_expect_success 'reset --merge will preserve untracked files/dirs' '
+ test_setup_reset merge &&
+ (
+ cd reset_merge &&
+
+ mkdir foo.t &&
+ echo precious >foo.t/file &&
+ cp foo.t/file expect &&
+
+ test_must_fail git reset --merge work 2>error &&
+ test_cmp expect foo.t/file &&
+ grep "Updating .foo.t. would lose untracked files" error
+ )
+'
+
+test_expect_success 'reset --keep will preserve untracked files/dirs' '
+ test_setup_reset keep &&
+ (
+ cd reset_keep &&
+
+ mkdir foo.t &&
+ echo precious >foo.t/file &&
+ cp foo.t/file expect &&
+
+ test_must_fail git reset --merge work 2>error &&
+ test_cmp expect foo.t/file &&
+ grep "Updating.*foo.t.*would lose untracked files" error
+ )
+'
+
+test_setup_checkout_m () {
+ git init checkout &&
+ (
+ cd checkout &&
+ test_commit init &&
+
+ test_write_lines file has some >filler &&
+ git add filler &&
+ git commit -m filler &&
+
+ git branch stable &&
+
+ git switch -c work &&
+ echo stuff >notes.txt &&
+ test_write_lines file has some words >filler &&
+ git add notes.txt filler &&
+ git commit -m filler &&
+
+ git checkout stable
+ )
+}
+
+test_expect_success 'checkout -m does not nuke untracked file' '
+ test_setup_checkout_m &&
+ (
+ cd checkout &&
+
+ # Tweak filler
+ test_write_lines this file has some >filler &&
+ # Make an untracked file, save its contents in "expect"
+ echo precious >notes.txt &&
+ cp notes.txt expect &&
+
+ test_must_fail git checkout -m work &&
+ test_cmp expect notes.txt
+ )
+'
+
+test_setup_sequencing () {
+ git init sequencing_$1 &&
+ (
+ cd sequencing_$1 &&
+ test_commit init &&
+
+ test_write_lines this file has some words >filler &&
+ git add filler &&
+ git commit -m filler &&
+
+ mkdir -p foo/bar &&
+ test_commit foo/bar/baz &&
+
+ git branch simple &&
+ git branch fooey &&
+
+ git checkout fooey &&
+ git rm foo/bar/baz.t &&
+ echo stuff >>filler &&
+ git add -u &&
+ git commit -m "changes" &&
+
+ git checkout simple &&
+ echo items >>filler &&
+ echo newstuff >>newfile &&
+ git add filler newfile &&
+ git commit -m another
+ )
+}
+
+test_expect_success 'git rebase --abort and untracked files' '
+ test_setup_sequencing rebase_abort_and_untracked &&
+ (
+ cd sequencing_rebase_abort_and_untracked &&
+ git checkout fooey &&
+ test_must_fail git rebase simple &&
+
+ cat init.t &&
+ git rm init.t &&
+ echo precious >init.t &&
+ cp init.t expect &&
+ git status --porcelain &&
+ test_must_fail git rebase --abort &&
+ test_cmp expect init.t
+ )
+'
+
+test_expect_success 'git rebase fast forwarding and untracked files' '
+ test_setup_sequencing rebase_fast_forward_and_untracked &&
+ (
+ cd sequencing_rebase_fast_forward_and_untracked &&
+ git checkout init &&
+ echo precious >filler &&
+ cp filler expect &&
+ test_must_fail git rebase init simple &&
+ test_cmp expect filler
+ )
+'
+
+test_expect_failure 'git rebase --autostash and untracked files' '
+ test_setup_sequencing rebase_autostash_and_untracked &&
+ (
+ cd sequencing_rebase_autostash_and_untracked &&
+ git checkout simple &&
+ git rm filler &&
+ mkdir filler &&
+ echo precious >filler/file &&
+ cp filler/file expect &&
+ git rebase --autostash init &&
+ test_path_is_file filler/file
+ )
+'
+
+test_expect_failure 'git stash and untracked files' '
+ test_setup_sequencing stash_and_untracked_files &&
+ (
+ cd sequencing_stash_and_untracked_files &&
+ git checkout simple &&
+ git rm filler &&
+ mkdir filler &&
+ echo precious >filler/file &&
+ cp filler/file expect &&
+ git status --porcelain &&
+ git stash push &&
+ git status --porcelain &&
+ test_path_is_file filler/file
+ )
+'
+
+test_expect_success 'git am --abort and untracked dir vs. unmerged file' '
+ test_setup_sequencing am_abort_and_untracked &&
+ (
+ cd sequencing_am_abort_and_untracked &&
+ git format-patch -1 --stdout fooey >changes.mbox &&
+ test_must_fail git am --3way changes.mbox &&
+
+ # Delete the conflicted file; we will stage and commit it later
+ rm filler &&
+
+ # Put an unrelated untracked directory there
+ mkdir filler &&
+ echo foo >filler/file1 &&
+ echo bar >filler/file2 &&
+
+ test_must_fail git am --abort 2>errors &&
+ test_path_is_dir filler &&
+ grep "Updating .filler. would lose untracked files in it" errors
+ )
+'
+
+test_expect_success 'git am --skip and untracked dir vs deleted file' '
+ test_setup_sequencing am_skip_and_untracked &&
+ (
+ cd sequencing_am_skip_and_untracked &&
+ git checkout fooey &&
+ git format-patch -1 --stdout simple >changes.mbox &&
+ test_must_fail git am --3way changes.mbox &&
+
+ # Delete newfile
+ rm newfile &&
+
+ # Put an unrelated untracked directory there
+ mkdir newfile &&
+ echo foo >newfile/file1 &&
+ echo bar >newfile/file2 &&
+
+ # Change our mind about resolutions, just skip this patch
+ test_must_fail git am --skip 2>errors &&
+ test_path_is_dir newfile &&
+ grep "Updating .newfile. would lose untracked files in it" errors
+ )
+'
+
+test_done
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index cc4b10236e..e575ffb4ff 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -1272,6 +1272,19 @@ test_expect_success 'attempt to delete a branch merged to its base' '
test_must_fail git branch -d my10
'
+test_expect_success 'branch --delete --force removes dangling branch' '
+ git checkout main &&
+ test_commit unstable &&
+ hash=$(git rev-parse HEAD) &&
+ objpath=$(echo $hash | sed -e "s|^..|.git/objects/&/|") &&
+ git branch --no-track dangling &&
+ mv $objpath $objpath.x &&
+ test_when_finished "mv $objpath.x $objpath" &&
+ git branch --delete --force dangling &&
+ git for-each-ref refs/heads/dangling >actual &&
+ test_must_be_empty actual
+'
+
test_expect_success 'use --edit-description' '
write_script editor <<-\EOF &&
echo "New contents" >"$1"
diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh
index 5325b9f67a..6e94c6db7b 100755
--- a/t/t3203-branch-output.sh
+++ b/t/t3203-branch-output.sh
@@ -340,6 +340,10 @@ test_expect_success 'git branch --format option' '
test_cmp expect actual
'
+test_expect_success 'git branch with --format=%(rest) must fail' '
+ test_must_fail git branch --format="%(rest)" >actual
+'
+
test_expect_success 'worktree colors correct' '
cat >expect <<-EOF &&
* <GREEN>(HEAD detached from fromtag)<RESET>
diff --git a/t/t3320-notes-merge-worktrees.sh b/t/t3320-notes-merge-worktrees.sh
index 052516e6c6..6b2d507f3e 100755
--- a/t/t3320-notes-merge-worktrees.sh
+++ b/t/t3320-notes-merge-worktrees.sh
@@ -46,8 +46,9 @@ test_expect_success 'create some new worktrees' '
test_expect_success 'merge z into y fails and sets NOTES_MERGE_REF' '
git config core.notesRef refs/notes/y &&
test_must_fail git notes merge z &&
- echo "ref: refs/notes/y" >expect &&
- test_cmp expect .git/NOTES_MERGE_REF
+ echo "refs/notes/y" >expect &&
+ git symbolic-ref NOTES_MERGE_REF >actual &&
+ test_cmp expect actual
'
test_expect_success 'merge z into y while mid-merge in another workdir fails' '
@@ -57,7 +58,7 @@ test_expect_success 'merge z into y while mid-merge in another workdir fails' '
test_must_fail git notes merge z 2>err &&
test_i18ngrep "a notes merge into refs/notes/y is already in-progress at" err
) &&
- test_path_is_missing .git/worktrees/worktree/NOTES_MERGE_REF
+ test_must_fail git -C worktree symbolic-ref NOTES_MERGE_REF
'
test_expect_success 'merge z into x while mid-merge on y succeeds' '
@@ -68,8 +69,9 @@ test_expect_success 'merge z into x while mid-merge on y succeeds' '
test_i18ngrep "Automatic notes merge failed" out &&
grep -v "A notes merge into refs/notes/x is already in-progress in" out
) &&
- echo "ref: refs/notes/x" >expect &&
- test_cmp expect .git/worktrees/worktree2/NOTES_MERGE_REF
+ echo "refs/notes/x" >expect &&
+ git -C worktree2 symbolic-ref NOTES_MERGE_REF >actual &&
+ test_cmp expect actual
'
test_done
diff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh
index e26762d0b2..f6e4864497 100755
--- a/t/t3403-rebase-skip.sh
+++ b/t/t3403-rebase-skip.sh
@@ -20,6 +20,7 @@ test_expect_success setup '
git add hello &&
git commit -m "hello" &&
git branch skip-reference &&
+ git tag hello &&
echo world >> hello &&
git commit -a -m "hello world" &&
@@ -36,7 +37,8 @@ test_expect_success setup '
test_tick &&
GIT_AUTHOR_NAME="Another Author" \
GIT_AUTHOR_EMAIL="another.author@example.com" \
- git commit --amend --no-edit -m amended-goodbye &&
+ git commit --amend --no-edit -m amended-goodbye \
+ --reset-author &&
test_tick &&
git tag amended-goodbye &&
@@ -51,7 +53,7 @@ test_expect_success setup '
'
test_expect_success 'rebase with git am -3 (default)' '
- test_must_fail git rebase main
+ test_must_fail git rebase --apply main
'
test_expect_success 'rebase --skip can not be used with other options' '
@@ -95,6 +97,13 @@ test_expect_success 'moved back to branch correctly' '
test_debug 'gitk --all & sleep 1'
+test_expect_success 'skipping final pick removes .git/MERGE_MSG' '
+ test_must_fail git rebase --onto hello reverted-goodbye^ \
+ reverted-goodbye &&
+ git rebase --skip &&
+ test_path_is_missing .git/MERGE_MSG
+'
+
test_expect_success 'correct advice upon picking empty commit' '
test_when_finished "git rebase --abort" &&
test_must_fail git rebase -i --onto goodbye \
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 66bcbbf952..12eb226957 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -297,6 +297,7 @@ test_expect_success 'abort with error when new base cannot be checked out' '
output &&
test_i18ngrep "file1" output &&
test_path_is_missing .git/rebase-merge &&
+ rm file1 &&
git reset --hard HEAD^
'
@@ -351,82 +352,6 @@ test_expect_success 'retain authorship when squashing' '
git show HEAD | grep "^Author: Twerp Snog"
'
-test_expect_success REBASE_P '-p handles "no changes" gracefully' '
- HEAD=$(git rev-parse HEAD) &&
- git rebase -i -p HEAD^ &&
- git update-index --refresh &&
- git diff-files --quiet &&
- git diff-index --quiet --cached HEAD -- &&
- test $HEAD = $(git rev-parse HEAD)
-'
-
-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
- ) &&
- test H = $(git cat-file commit HEAD^ | sed -ne \$p) &&
- test G = $(git cat-file commit HEAD | sed -ne \$p)
-'
-
-test_expect_success REBASE_P 'preserve merges with -p' '
- git checkout -b to-be-preserved primary^ &&
- : > unrelated-file &&
- git add unrelated-file &&
- test_tick &&
- git commit -m "unrelated" &&
- git checkout -b another-branch primary &&
- echo B > file1 &&
- test_tick &&
- git commit -m J file1 &&
- test_tick &&
- git merge to-be-preserved &&
- echo C > file1 &&
- test_tick &&
- git commit -m K file1 &&
- echo D > file1 &&
- test_tick &&
- git commit -m L1 file1 &&
- git checkout HEAD^ &&
- echo 1 > unrelated-file &&
- test_tick &&
- git commit -m L2 unrelated-file &&
- test_tick &&
- git merge another-branch &&
- echo E > file1 &&
- test_tick &&
- git commit -m M file1 &&
- git checkout -b to-be-rebased &&
- test_tick &&
- git rebase -i -p --onto branch1 primary &&
- git update-index --refresh &&
- git diff-files --quiet &&
- git diff-index --quiet --cached 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 &&
- test $(git show HEAD:unrelated-file) = 1
-'
-
-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
- ) &&
- echo 2 > unrelated-file &&
- test_tick &&
- git commit -m L2-modified --amend unrelated-file &&
- git rebase --continue &&
- git update-index --refresh &&
- git diff-files --quiet &&
- git diff-index --quiet --cached HEAD -- &&
- test $(git show HEAD:unrelated-file) = 2
-'
-
test_expect_success '--continue tries to commit' '
git reset --hard D &&
test_tick &&
@@ -839,6 +764,19 @@ test_expect_success 'reword' '
git show HEAD~2 | grep "C changed"
'
+test_expect_success 'no uncommited changes when rewording the todo list is reloaded' '
+ git checkout E &&
+ test_when_finished "git checkout @{-1}" &&
+ (
+ set_fake_editor &&
+ GIT_SEQUENCE_EDITOR="\"$PWD/fake-editor.sh\"" &&
+ export GIT_SEQUENCE_EDITOR &&
+ set_reword_editor &&
+ FAKE_LINES="reword 1 reword 2" git rebase -i C
+ ) &&
+ check_reworded_commits D E
+'
+
test_expect_success 'rebase -i can copy notes' '
git config notes.rewrite.rebase true &&
git config notes.rewriteRef "refs/notes/*" &&
diff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh
index 7c381fbc89..ebbaed147a 100755
--- a/t/t3407-rebase-abort.sh
+++ b/t/t3407-rebase-abort.sh
@@ -7,77 +7,77 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
. ./test-lib.sh
-### Test that we handle space characters properly
-work_dir="$(pwd)/test dir"
-
test_expect_success setup '
- mkdir -p "$work_dir" &&
- cd "$work_dir" &&
- git init &&
- echo a > a &&
- git add a &&
- git commit -m a &&
+ test_commit a a a &&
git branch to-rebase &&
- echo b > a &&
- git commit -a -m b &&
- echo c > a &&
- git commit -a -m c &&
+ test_commit --annotate b a b &&
+ test_commit --annotate c a c &&
git checkout to-rebase &&
- echo d > a &&
- git commit -a -m "merge should fail on this" &&
- echo e > a &&
- git commit -a -m "merge should fail on this, too" &&
- git branch pre-rebase
+ test_commit "merge should fail on this" a d d &&
+ test_commit --annotate "merge should fail on this, too" a e pre-rebase
'
+# Check that HEAD is equal to "pre-rebase" and the current branch is
+# "to-rebase"
+check_head() {
+ test_cmp_rev HEAD pre-rebase^{commit} &&
+ test "$(git symbolic-ref HEAD)" = refs/heads/to-rebase
+}
+
testrebase() {
type=$1
- dotest=$2
+ state_dir=$2
test_expect_success "rebase$type --abort" '
- cd "$work_dir" &&
# Clean up the state from the previous one
git reset --hard pre-rebase &&
test_must_fail git rebase$type main &&
- test_path_is_dir "$dotest" &&
+ test_path_is_dir "$state_dir" &&
git rebase --abort &&
- test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) &&
- test ! -d "$dotest"
+ check_head &&
+ test_path_is_missing "$state_dir"
'
test_expect_success "rebase$type --abort after --skip" '
- cd "$work_dir" &&
# Clean up the state from the previous one
git reset --hard pre-rebase &&
test_must_fail git rebase$type main &&
- test_path_is_dir "$dotest" &&
+ test_path_is_dir "$state_dir" &&
test_must_fail git rebase --skip &&
- test $(git rev-parse HEAD) = $(git rev-parse main) &&
+ test_cmp_rev HEAD main &&
git rebase --abort &&
- test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) &&
- test ! -d "$dotest"
+ check_head &&
+ test_path_is_missing "$state_dir"
'
test_expect_success "rebase$type --abort after --continue" '
- cd "$work_dir" &&
# Clean up the state from the previous one
git reset --hard pre-rebase &&
test_must_fail git rebase$type main &&
- test_path_is_dir "$dotest" &&
+ test_path_is_dir "$state_dir" &&
echo c > a &&
echo d >> a &&
git add a &&
test_must_fail git rebase --continue &&
- test $(git rev-parse HEAD) != $(git rev-parse main) &&
+ test_cmp_rev ! HEAD main &&
+ git rebase --abort &&
+ check_head &&
+ test_path_is_missing "$state_dir"
+ '
+
+ test_expect_success "rebase$type --abort when checking out a tag" '
+ test_when_finished "git symbolic-ref HEAD refs/heads/to-rebase" &&
+ git reset --hard a -- &&
+ test_must_fail git rebase$type --onto b c pre-rebase &&
+ test_cmp_rev HEAD b^{commit} &&
git rebase --abort &&
- test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) &&
- test ! -d "$dotest"
+ test_cmp_rev HEAD pre-rebase^{commit} &&
+ ! git symbolic-ref HEAD
'
test_expect_success "rebase$type --abort does not update reflog" '
- cd "$work_dir" &&
# Clean up the state from the previous one
git reset --hard pre-rebase &&
git reflog show to-rebase > reflog_before &&
@@ -89,7 +89,6 @@ testrebase() {
'
test_expect_success 'rebase --abort can not be used with other options' '
- cd "$work_dir" &&
# Clean up the state from the previous one
git reset --hard pre-rebase &&
test_must_fail git rebase$type main &&
@@ -97,33 +96,21 @@ testrebase() {
test_must_fail git rebase --abort -v &&
git rebase --abort
'
+
+ test_expect_success "rebase$type --quit" '
+ test_when_finished "git symbolic-ref HEAD refs/heads/to-rebase" &&
+ # Clean up the state from the previous one
+ git reset --hard pre-rebase &&
+ test_must_fail git rebase$type main &&
+ test_path_is_dir $state_dir &&
+ head_before=$(git rev-parse HEAD) &&
+ git rebase --quit &&
+ test_cmp_rev HEAD $head_before &&
+ test_path_is_missing .git/rebase-apply
+ '
}
testrebase " --apply" .git/rebase-apply
testrebase " --merge" .git/rebase-merge
-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 --apply main &&
- test_path_is_dir .git/rebase-apply &&
- head_before=$(git rev-parse HEAD) &&
- git rebase --quit &&
- test $(git rev-parse HEAD) = $head_before &&
- test ! -d .git/rebase-apply
-'
-
-test_expect_success 'rebase --merge --quit' '
- cd "$work_dir" &&
- # Clean up the state from the previous one
- git reset --hard pre-rebase &&
- test_must_fail git rebase --merge main &&
- test_path_is_dir .git/rebase-merge &&
- head_before=$(git rev-parse HEAD) &&
- git rebase --quit &&
- test $(git rev-parse HEAD) = $head_before &&
- test ! -d .git/rebase-merge
-'
-
test_done
diff --git a/t/t3408-rebase-multi-line.sh b/t/t3408-rebase-multi-line.sh
index ab0960e6d9..cde3562e3a 100755
--- a/t/t3408-rebase-multi-line.sh
+++ b/t/t3408-rebase-multi-line.sh
@@ -55,14 +55,4 @@ test_expect_success rebase '
test_cmp expect actual
'
-test_expect_success REBASE_P rebasep '
-
- git checkout side-merge &&
- git rebase -p side &&
- git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
- git cat-file commit side-merge-original | sed -e "1,/^\$/d" >expect &&
- test_cmp expect actual
-
-'
-
test_done
diff --git a/t/t3409-rebase-preserve-merges.sh b/t/t3409-rebase-preserve-merges.sh
deleted file mode 100755
index ec8062a66a..0000000000
--- a/t/t3409-rebase-preserve-merges.sh
+++ /dev/null
@@ -1,130 +0,0 @@
-#!/bin/sh
-#
-# Copyright(C) 2008 Stephen Habermann & Andreas Ericsson
-#
-test_description='git rebase -p should preserve merges
-
-Run "git rebase -p" and check that merges are properly carried along
-'
-GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
-export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
-
-. ./test-lib.sh
-
-if ! test_have_prereq REBASE_P; then
- skip_all='skipping git rebase -p tests, as asked for'
- test_done
-fi
-
-GIT_AUTHOR_EMAIL=bogus_email_address
-export GIT_AUTHOR_EMAIL
-
-# Clone 2 (conflicting merge):
-#
-# A1--A2--B3 <-- origin/main
-# \ \
-# B1------M <-- topic
-# \
-# B2 <-- origin/topic
-#
-# Clone 3 (no-ff merge):
-#
-# A1--A2--B3 <-- origin/main
-# \
-# B1------M <-- topic
-# \ /
-# \--A3 <-- topic2
-# \
-# B2 <-- origin/topic
-#
-# Clone 4 (same as Clone 3)
-
-test_expect_success 'setup for merge-preserving rebase' \
- 'echo First > A &&
- git add A &&
- git commit -m "Add A1" &&
- git checkout -b topic &&
- echo Second > B &&
- git add B &&
- git commit -m "Add B1" &&
- git checkout -f main &&
- echo Third >> A &&
- git commit -a -m "Modify A2" &&
- echo Fifth > B &&
- git add B &&
- git commit -m "Add different B" &&
-
- git clone ./. clone2 &&
- (
- cd clone2 &&
- git checkout -b topic origin/topic &&
- test_must_fail git merge origin/main &&
- echo Resolved >B &&
- git add B &&
- git commit -m "Merge origin/main into topic"
- ) &&
-
- git clone ./. clone3 &&
- (
- cd clone3 &&
- git checkout -b topic2 origin/topic &&
- echo Sixth > A &&
- git commit -a -m "Modify A3" &&
- git checkout -b topic origin/topic &&
- git merge --no-ff topic2
- ) &&
-
- git clone ./. clone4 &&
- (
- cd clone4 &&
- git checkout -b topic2 origin/topic &&
- echo Sixth > A &&
- git commit -a -m "Modify A3" &&
- git checkout -b topic origin/topic &&
- git merge --no-ff topic2
- ) &&
-
- git checkout topic &&
- echo Fourth >> B &&
- git commit -a -m "Modify B2"
-'
-
-test_expect_success '--continue works after a conflict' '
- (
- cd clone2 &&
- git fetch &&
- test_must_fail git rebase -p origin/topic &&
- test 2 = $(git ls-files B | wc -l) &&
- echo Resolved again > B &&
- test_must_fail git rebase --continue &&
- grep "^@@@ " .git/rebase-merge/patch &&
- git add B &&
- git rebase --continue &&
- test 1 = $(git rev-list --all --pretty=oneline | grep "Modify A" | wc -l) &&
- test 1 = $(git rev-list --all --pretty=oneline | grep "Add different" | wc -l) &&
- test 1 = $(git rev-list --all --pretty=oneline | grep "Merge origin" | wc -l)
- )
-'
-
-test_expect_success 'rebase -p preserves no-ff merges' '
- (
- cd clone3 &&
- git fetch &&
- git rebase -p origin/topic &&
- test 3 = $(git rev-list --all --pretty=oneline | grep "Modify A" | wc -l) &&
- test 1 = $(git rev-list --all --pretty=oneline | grep "Merge branch" | wc -l)
- )
-'
-
-test_expect_success 'rebase -p ignores merge.log config' '
- (
- cd clone4 &&
- git fetch &&
- git -c merge.log=1 rebase -p origin/topic &&
- echo >expected &&
- git log --format="%b" -1 >current &&
- test_cmp expected current
- )
-'
-
-test_done
diff --git a/t/t3410-rebase-preserve-dropped-merges.sh b/t/t3410-rebase-preserve-dropped-merges.sh
deleted file mode 100755
index 2e29866993..0000000000
--- a/t/t3410-rebase-preserve-dropped-merges.sh
+++ /dev/null
@@ -1,90 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2008 Stephen Haberman
-#
-
-test_description='git rebase preserve merges
-
-This test runs git rebase with preserve merges and ensures commits
-dropped by the --cherry-pick flag have their childrens parents
-rewritten.
-'
-. ./test-lib.sh
-
-if ! test_have_prereq REBASE_P; then
- skip_all='skipping git rebase -p tests, as asked for'
- test_done
-fi
-
-# set up two branches like this:
-#
-# A - B - C - D - E
-# \
-# F - G - H
-# \
-# I
-#
-# where B, D and G touch the same file.
-
-test_expect_success 'setup' '
- test_commit A file1 &&
- test_commit B file1 1 &&
- test_commit C file2 &&
- test_commit D file1 2 &&
- test_commit E file3 &&
- git checkout A &&
- test_commit F file4 &&
- test_commit G file1 3 &&
- test_commit H file5 &&
- git checkout F &&
- test_commit I file6
-'
-
-# A - B - C - D - E
-# \ \ \
-# F - G - H -- L \ --> L
-# \ | \
-# I -- G2 -- J -- K I -- K
-# G2 = same changes as G
-test_expect_success 'skip same-resolution merges with -p' '
- git checkout H &&
- test_must_fail git merge E &&
- test_commit L file1 23 &&
- git checkout I &&
- test_commit G2 file1 3 &&
- test_must_fail git merge E &&
- test_commit J file1 23 &&
- test_commit K file7 file7 &&
- git rebase -i -p L &&
- test $(git rev-parse HEAD^^) = $(git rev-parse L) &&
- test "23" = "$(cat file1)" &&
- test "I" = "$(cat file6)" &&
- test "file7" = "$(cat file7)"
-'
-
-# A - B - C - D - E
-# \ \ \
-# F - G - H -- L2 \ --> L2
-# \ | \
-# I -- G3 --- J2 -- K2 I -- G3 -- K2
-# G2 = different changes as G
-test_expect_success 'keep different-resolution merges with -p' '
- git checkout H &&
- test_must_fail git merge E &&
- test_commit L2 file1 23 &&
- git checkout I &&
- test_commit G3 file1 4 &&
- test_must_fail git merge E &&
- test_commit J2 file1 24 &&
- test_commit K2 file7 file7 &&
- test_must_fail git rebase -i -p L2 &&
- echo 234 > file1 &&
- git add file1 &&
- git rebase --continue &&
- test $(git rev-parse HEAD^^^) = $(git rev-parse L2) &&
- test "234" = "$(cat file1)" &&
- test "I" = "$(cat file6)" &&
- test "file7" = "$(cat file7)"
-'
-
-test_done
diff --git a/t/t3411-rebase-preserve-around-merges.sh b/t/t3411-rebase-preserve-around-merges.sh
deleted file mode 100755
index fb45e7bf7b..0000000000
--- a/t/t3411-rebase-preserve-around-merges.sh
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2008 Stephen Haberman
-#
-
-test_description='git rebase preserve merges
-
-This test runs git rebase with -p and tries to squash a commit from after
-a merge to before the merge.
-'
-. ./test-lib.sh
-
-if ! test_have_prereq REBASE_P; then
- skip_all='skipping git rebase -p tests, as asked for'
- test_done
-fi
-
-. "$TEST_DIRECTORY"/lib-rebase.sh
-
-set_fake_editor
-
-# set up two branches like this:
-#
-# A1 - B1 - D1 - E1 - F1
-# \ /
-# -- C1 --
-
-test_expect_success 'setup' '
- test_commit A1 &&
- test_commit B1 &&
- test_commit C1 &&
- git reset --hard B1 &&
- test_commit D1 &&
- test_merge E1 C1 &&
- test_commit F1
-'
-
-# Should result in:
-#
-# A1 - B1 - D2 - E2
-# \ /
-# -- C1 --
-#
-test_expect_success 'squash F1 into D1' '
- FAKE_LINES="1 squash 4 2 3" git rebase -i -p B1 &&
- test "$(git rev-parse HEAD^2)" = "$(git rev-parse C1)" &&
- test "$(git rev-parse HEAD~2)" = "$(git rev-parse B1)" &&
- git tag E2
-'
-
-# Start with:
-#
-# A1 - B1 - D2 - E2
-# \
-# G1 ---- L1 ---- M1
-# \ /
-# H1 -- J1 -- K1
-# \ /
-# -- I1 --
-#
-# And rebase G1..M1 onto E2
-
-test_expect_success 'rebase two levels of merge' '
- git checkout A1 &&
- test_commit G1 &&
- test_commit H1 &&
- test_commit I1 &&
- git checkout -b branch3 H1 &&
- test_commit J1 &&
- test_merge K1 I1 &&
- git checkout -b branch2 G1 &&
- test_commit L1 &&
- test_merge M1 K1 &&
- GIT_EDITOR=: git rebase -i -p E2 &&
- test "$(git rev-parse HEAD~3)" = "$(git rev-parse E2)" &&
- test "$(git rev-parse HEAD~2)" = "$(git rev-parse HEAD^2^2~2)" &&
- test "$(git rev-parse HEAD^2^1^1)" = "$(git rev-parse HEAD^2^2^1)"
-'
-
-test_done
diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh
index fda62c65bd..19c6f4acbf 100755
--- a/t/t3412-rebase-root.sh
+++ b/t/t3412-rebase-root.sh
@@ -89,17 +89,6 @@ test_expect_success 'pre-rebase got correct input (4)' '
test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work4
'
-test_expect_success REBASE_P 'rebase -i -p with linear history' '
- git checkout -b work5 other &&
- git rebase -i -p --root --onto main &&
- git log --pretty=tformat:"%s" > rebased5 &&
- test_cmp expect rebased5
-'
-
-test_expect_success REBASE_P 'pre-rebase got correct input (5)' '
- test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,
-'
-
test_expect_success 'set up merge history' '
git checkout other^ &&
git checkout -b side &&
@@ -123,13 +112,6 @@ commit work6~4
1
EOF
-test_expect_success REBASE_P 'rebase -i -p with merge' '
- git checkout -b work6 other &&
- git rebase -i -p --root --onto main &&
- log_with_names work6 > rebased6 &&
- test_cmp expect-side rebased6
-'
-
test_expect_success 'set up second root and merge' '
git symbolic-ref HEAD refs/heads/third &&
rm .git/index &&
@@ -158,13 +140,6 @@ commit work7~5
1
EOF
-test_expect_success REBASE_P 'rebase -i -p with two roots' '
- git checkout -b work7 other &&
- git rebase -i -p --root --onto main &&
- log_with_names work7 > rebased7 &&
- test_cmp expect-third rebased7
-'
-
test_expect_success 'setup pre-rebase hook that fails' '
mkdir -p .git/hooks &&
cat >.git/hooks/pre-rebase <<EOF &&
@@ -264,21 +239,9 @@ commit conflict3~6
1
EOF
-test_expect_success REBASE_P 'rebase -i -p --root with conflict (first part)' '
- git checkout -b conflict3 other &&
- test_must_fail git rebase -i -p --root --onto main &&
- git ls-files -u | grep "B$"
-'
-
test_expect_success 'fix the conflict' '
echo 3 > B &&
git add B
'
-test_expect_success REBASE_P 'rebase -i -p --root with conflict (second part)' '
- git rebase --continue &&
- log_with_names conflict3 >out &&
- test_cmp expect-conflict-p out
-'
-
test_done
diff --git a/t/t3414-rebase-preserve-onto.sh b/t/t3414-rebase-preserve-onto.sh
deleted file mode 100755
index 72e04b5386..0000000000
--- a/t/t3414-rebase-preserve-onto.sh
+++ /dev/null
@@ -1,85 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2009 Greg Price
-#
-
-test_description='git rebase -p should respect --onto
-
-In a rebase with --onto, we should rewrite all the commits that
-aren'"'"'t on top of $ONTO, even if they are on top of $UPSTREAM.
-'
-. ./test-lib.sh
-
-if ! test_have_prereq REBASE_P; then
- skip_all='skipping git rebase -p tests, as asked for'
- test_done
-fi
-
-. "$TEST_DIRECTORY"/lib-rebase.sh
-
-# Set up branches like this:
-# A1---B1---E1---F1---G1
-# \ \ /
-# \ \--C1---D1--/
-# H1
-
-test_expect_success 'setup' '
- test_commit A1 &&
- test_commit B1 &&
- test_commit C1 &&
- test_commit D1 &&
- git reset --hard B1 &&
- test_commit E1 &&
- test_commit F1 &&
- test_merge G1 D1 &&
- git reset --hard A1 &&
- test_commit H1
-'
-
-# Now rebase merge G1 from both branches' base B1, both should move:
-# A1---B1---E1---F1---G1
-# \ \ /
-# \ \--C1---D1--/
-# \
-# H1---E2---F2---G2
-# \ /
-# \--C2---D2--/
-
-test_expect_success 'rebase from B1 onto H1' '
- git checkout G1 &&
- git rebase -p --onto H1 B1 &&
- test "$(git rev-parse HEAD^1^1^1)" = "$(git rev-parse H1)" &&
- test "$(git rev-parse HEAD^2^1^1)" = "$(git rev-parse H1)"
-'
-
-# On the other hand if rebase from E1 which is within one branch,
-# then the other branch stays:
-# A1---B1---E1---F1---G1
-# \ \ /
-# \ \--C1---D1--/
-# \ \
-# H1-----F3-----G3
-
-test_expect_success 'rebase from E1 onto H1' '
- git checkout G1 &&
- git rebase -p --onto H1 E1 &&
- test "$(git rev-parse HEAD^1^1)" = "$(git rev-parse H1)" &&
- test "$(git rev-parse HEAD^2)" = "$(git rev-parse D1)"
-'
-
-# And the same if we rebase from a commit in the second-parent branch.
-# A1---B1---E1---F1----G1
-# \ \ \ /
-# \ \--C1---D1-\-/
-# \ \
-# H1------D3------G4
-
-test_expect_success 'rebase from C1 onto H1' '
- git checkout G1 &&
- git rev-list --first-parent --pretty=oneline C1..G1 &&
- git rebase -p --onto H1 C1 &&
- test "$(git rev-parse HEAD^2^1)" = "$(git rev-parse H1)" &&
- test "$(git rev-parse HEAD^1)" = "$(git rev-parse F1)"
-'
-
-test_done
diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh
index f4c2ee02bc..22eca73aa3 100755
--- a/t/t3418-rebase-continue.sh
+++ b/t/t3418-rebase-continue.sh
@@ -21,7 +21,7 @@ test_expect_success 'setup' '
git checkout main
'
-test_expect_success 'interactive rebase --continue works with touched file' '
+test_expect_success 'merge based rebase --continue with works with touched file' '
rm -fr .git/rebase-* &&
git reset --hard &&
git checkout main &&
@@ -31,12 +31,22 @@ test_expect_success 'interactive rebase --continue works with touched file' '
git rebase --continue
'
-test_expect_success 'non-interactive rebase --continue works with touched file' '
+test_expect_success 'merge based rebase --continue removes .git/MERGE_MSG' '
+ git checkout -f --detach topic &&
+
+ test_must_fail git rebase --onto main HEAD^ &&
+ git read-tree --reset -u HEAD &&
+ test_path_is_file .git/MERGE_MSG &&
+ git rebase --continue &&
+ test_path_is_missing .git/MERGE_MSG
+'
+
+test_expect_success 'apply based rebase --continue works with touched file' '
rm -fr .git/rebase-* &&
git reset --hard &&
git checkout main &&
- test_must_fail git rebase --onto main main topic &&
+ test_must_fail git rebase --apply --onto main main topic &&
echo "Resolved" >F2 &&
git add F2 &&
test-tool chmtime =-60 F1 &&
@@ -109,20 +119,6 @@ test_expect_success 'rebase -i --continue handles merge strategy and options' '
test -f funny.was.run
'
-test_expect_success REBASE_P 'rebase passes merge strategy options correctly' '
- rm -fr .git/rebase-* &&
- git reset --hard commit-new-file-F3-on-topic-branch &&
- test_commit theirs-to-merge &&
- git reset --hard HEAD^ &&
- test_commit some-commit &&
- test_tick &&
- git merge --no-ff theirs-to-merge &&
- FAKE_LINES="1 edit 2 3" git rebase -i -f -p -m \
- -s recursive --strategy-option=theirs HEAD~2 &&
- test_commit force-change &&
- 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 &&
@@ -254,11 +250,10 @@ test_rerere_autoupdate () {
'
}
-test_rerere_autoupdate
+test_rerere_autoupdate --apply
test_rerere_autoupdate -m
GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR
test_rerere_autoupdate -i
-test_have_prereq !REBASE_P || test_rerere_autoupdate --preserve-merges
unset GIT_SEQUENCE_EDITOR
test_expect_success 'the todo command "break" works' '
diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh
index 4a9204b4b6..62d86d557d 100755
--- a/t/t3421-rebase-topology-linear.sh
+++ b/t/t3421-rebase-topology-linear.sh
@@ -29,7 +29,6 @@ test_run_rebase () {
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 &&
@@ -53,7 +52,6 @@ test_run_rebase () {
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_run_rebase () {
result=$1
@@ -70,7 +68,6 @@ 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
test_run_rebase () {
result=$1
@@ -87,7 +84,6 @@ 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
@@ -102,7 +98,6 @@ 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
# f
# /
@@ -142,7 +137,6 @@ test_run_rebase () {
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_run_rebase () {
result=$1
@@ -157,7 +151,6 @@ test_run_rebase () {
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_run_rebase () {
result=$1
@@ -172,7 +165,6 @@ test_run_rebase () {
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_run_rebase () {
result=$1
@@ -187,7 +179,6 @@ test_run_rebase () {
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
# a---b---c---j!
# \
@@ -215,7 +206,6 @@ test_run_rebase () {
test_run_rebase failure --apply
test_run_rebase success -m
test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase failure -p
test_run_rebase () {
result=$1
@@ -229,7 +219,6 @@ test_run_rebase () {
}
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
@@ -243,7 +232,6 @@ test_run_rebase () {
}
test_run_rebase success -m
test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase success -p
test_run_rebase success --rebase-merges
# m
@@ -283,7 +271,6 @@ test_run_rebase () {
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_run_rebase () {
result=$1
@@ -298,7 +285,6 @@ test_run_rebase () {
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
test_run_rebase () {
result=$1
@@ -313,7 +299,6 @@ test_run_rebase () {
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_run_rebase () {
result=$1
@@ -329,7 +314,6 @@ test_run_rebase () {
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
test_run_rebase () {
result=$1
@@ -344,7 +328,6 @@ test_run_rebase () {
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
test_run_rebase () {
result=$1
@@ -358,7 +341,6 @@ test_run_rebase () {
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_run_rebase () {
result=$1
@@ -373,6 +355,5 @@ test_run_rebase () {
test_run_rebase success ''
test_run_rebase success -m
test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase success -p
test_done
diff --git a/t/t3422-rebase-incompatible-options.sh b/t/t3422-rebase-incompatible-options.sh
index c8234062c6..eb0a3d9d48 100755
--- a/t/t3422-rebase-incompatible-options.sh
+++ b/t/t3422-rebase-incompatible-options.sh
@@ -63,15 +63,4 @@ test_rebase_am_only () {
test_rebase_am_only --whitespace=fix
test_rebase_am_only -C4
-test_expect_success REBASE_P '--preserve-merges incompatible with --signoff' '
- git checkout B^0 &&
- test_must_fail git rebase --preserve-merges --signoff A
-'
-
-test_expect_success REBASE_P \
- '--preserve-merges incompatible with --rebase-merges' '
- git checkout B^0 &&
- test_must_fail git rebase --preserve-merges --rebase-merges A
-'
-
test_done
diff --git a/t/t3425-rebase-topology-merges.sh b/t/t3425-rebase-topology-merges.sh
index e42faa44e7..63acc1ea4d 100755
--- a/t/t3425-rebase-topology-merges.sh
+++ b/t/t3425-rebase-topology-merges.sh
@@ -106,155 +106,4 @@ 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
-if ! test_have_prereq REBASE_P; then
- skip_all='skipping git rebase -p tests, as asked for'
- test_done
-fi
-
-test_expect_success "rebase -p is no-op in non-linear history" "
- reset_rebase &&
- git rebase -p d w &&
- test_cmp_rev w HEAD
-"
-
-test_expect_success "rebase -p is no-op when base inside second parent" "
- reset_rebase &&
- git rebase -p e w &&
- test_cmp_rev w HEAD
-"
-
-test_expect_failure "rebase -p --root on non-linear history is a no-op" "
- reset_rebase &&
- git rebase -p --root w &&
- test_cmp_rev w HEAD
-"
-
-test_expect_success "rebase -p re-creates merge from side branch" "
- reset_rebase &&
- git rebase -p z w &&
- test_cmp_rev z HEAD^ &&
- test_cmp_rev w^2 HEAD^2
-"
-
-test_expect_success "rebase -p re-creates internal merge" "
- reset_rebase &&
- git rebase -p c w &&
- test_cmp_rev c HEAD~4 &&
- test_cmp_rev HEAD^2^ HEAD~3 &&
- test_revision_subjects 'd n e o w' HEAD~3 HEAD~2 HEAD^2 HEAD^ HEAD
-"
-
-test_expect_success "rebase -p can re-create two branches on onto" "
- reset_rebase &&
- git rebase -p --onto c d w &&
- test_cmp_rev c HEAD~3 &&
- test_cmp_rev c HEAD^2^ &&
- test_revision_subjects 'n e o w' HEAD~2 HEAD^2 HEAD^ HEAD
-"
-
-# f
-# /
-# a---b---c---g---h
-# \
-# d---gp--i
-# \ \
-# e-------u
-#
-# gp = cherry-picked g
-# h = reverted g
-test_expect_success 'setup of non-linear-history for patch-equivalence tests' '
- git checkout e &&
- test_merge u i
-'
-
-test_expect_success "rebase -p re-creates history around dropped commit matching upstream" "
- reset_rebase &&
- git rebase -p h u &&
- test_cmp_rev h HEAD~3 &&
- test_cmp_rev HEAD^2^ HEAD~2 &&
- test_revision_subjects 'd i e u' HEAD~2 HEAD^2 HEAD^ HEAD
-"
-
-test_expect_success "rebase -p --onto in merged history drops patches in upstream" "
- reset_rebase &&
- git rebase -p --onto f h u &&
- test_cmp_rev f HEAD~3 &&
- test_cmp_rev HEAD^2^ HEAD~2 &&
- test_revision_subjects 'd i e u' HEAD~2 HEAD^2 HEAD^ HEAD
-"
-
-test_expect_success "rebase -p --onto in merged history does not drop patches in onto" "
- reset_rebase &&
- git rebase -p --onto h f u &&
- test_cmp_rev h HEAD~3 &&
- test_cmp_rev HEAD^2~2 HEAD~2 &&
- test_revision_subjects 'd gp i e u' HEAD~2 HEAD^2^ HEAD^2 HEAD^ HEAD
-"
-
-# a---b---c---g---h
-# \
-# d---gp--s
-# \ \ /
-# \ X
-# \ / \
-# e---t
-#
-# gp = cherry-picked g
-# h = reverted g
-test_expect_success 'setup of non-linear-history for dropping whole side' '
- git checkout gp &&
- test_merge s e &&
- git checkout e &&
- test_merge t gp
-'
-
-test_expect_failure "rebase -p drops merge commit when entire first-parent side is dropped" "
- reset_rebase &&
- git rebase -p h s &&
- test_cmp_rev h HEAD~2 &&
- test_linear_range 'd e' h..
-"
-
-test_expect_success "rebase -p drops merge commit when entire second-parent side is dropped" "
- reset_rebase &&
- git rebase -p h t &&
- test_cmp_rev h HEAD~2 &&
- test_linear_range 'd e' h..
-"
-
-# a---b---c
-# \
-# d---e
-# \ \
-# n---r
-# \
-# o
-#
-# r = tree-same with n
-test_expect_success 'setup of non-linear-history for empty commits' '
- git checkout n &&
- git merge --no-commit e &&
- git reset n . &&
- git commit -m r &&
- git reset --hard &&
- git clean -f &&
- git tag r
-'
-
-test_expect_success "rebase -p re-creates empty internal merge commit" "
- reset_rebase &&
- git rebase -p c r &&
- test_cmp_rev c HEAD~3 &&
- test_cmp_rev HEAD^2^ HEAD~2 &&
- test_revision_subjects 'd e n r' HEAD~2 HEAD^2 HEAD^ HEAD
-"
-
-test_expect_success "rebase -p re-creates empty merge commit" "
- reset_rebase &&
- git rebase -p o r &&
- test_cmp_rev e HEAD^2 &&
- test_cmp_rev o HEAD^ &&
- test_revision_subjects 'r' HEAD
-"
-
test_done
diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh
index e78c7e3796..48b76f8232 100755
--- a/t/t3427-rebase-subtree.sh
+++ b/t/t3427-rebase-subtree.sh
@@ -36,11 +36,10 @@ commit_message() {
# 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:
+# `-Xsubtree=files_subtree` option in two different ways:
#
-# 1. using `--preserve-merges`
-# 2. using `--preserve-merges` and --keep-empty
-# 3. without specifying a rebase backend
+# 1. without specifying a rebase backend
+# 2. using the `--rebase-merges` backend
test_expect_success 'setup' '
test_commit README &&
@@ -69,25 +68,6 @@ test_expect_success 'setup' '
git commit -m "Empty commit" --allow-empty
'
-# FAILURE: Does not preserve topic_4.
-test_expect_failure REBASE_P 'Rebase -Xsubtree --preserve-merges --onto commit' '
- reset_rebase &&
- git checkout -b rebase-preserve-merges to-rebase &&
- git rebase -Xsubtree=files_subtree --preserve-merges --onto files-main main &&
- verbose test "$(commit_message HEAD~)" = "topic_4" &&
- verbose test "$(commit_message HEAD)" = "files_subtree/topic_5"
-'
-
-# 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 to-rebase &&
- git rebase -Xsubtree=files_subtree --keep-empty --preserve-merges --onto files-main main &&
- verbose test "$(commit_message HEAD~2)" = "topic_4" &&
- verbose test "$(commit_message HEAD~)" = "files_subtree/topic_5" &&
- verbose test "$(commit_message HEAD)" = "Empty commit"
-'
-
test_expect_success 'Rebase -Xsubtree --empty=ask --onto commit' '
reset_rebase &&
git checkout -b rebase-onto to-rebase &&
diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
index 6748070df5..43c82d9a33 100755
--- a/t/t3430-rebase-merges.sh
+++ b/t/t3430-rebase-merges.sh
@@ -172,19 +172,39 @@ test_expect_success 'failed `merge <branch>` does not crash' '
grep "^Merge branch ${SQ}G${SQ}$" .git/rebase-merge/message
'
-test_expect_success 'fast-forward merge -c still rewords' '
- git checkout -b fast-forward-merge-c H &&
+test_expect_success 'merge -c commits before rewording and reloads todo-list' '
+ cat >script-from-scratch <<-\EOF &&
+ merge -c E B
+ merge -c H G
+ EOF
+
+ git checkout -b merge-c H &&
(
- set_fake_editor &&
- FAKE_COMMIT_MESSAGE=edited \
- GIT_SEQUENCE_EDITOR="echo merge -c H G >" \
- git rebase -ir @^
+ set_reword_editor &&
+ GIT_SEQUENCE_EDITOR="\"$PWD/replace-editor.sh\"" \
+ git rebase -i -r D
) &&
- echo edited >expected &&
- git log --pretty=format:%B -1 >actual &&
- test_cmp expected actual
+ check_reworded_commits E H
'
+test_expect_success 'merge -c rewords when a strategy is given' '
+ git checkout -b merge-c-with-strategy H &&
+ write_script git-merge-override <<-\EOF &&
+ echo overridden$1 >G.t
+ git add G.t
+ EOF
+
+ PATH="$PWD:$PATH" \
+ GIT_SEQUENCE_EDITOR="echo merge -c H G >" \
+ GIT_EDITOR="echo edited >>" \
+ git rebase --no-ff -ir -s override -Xxopt E &&
+ test_write_lines overridden--xopt >expect &&
+ test_cmp expect G.t &&
+ test_write_lines H "" edited "" >expect &&
+ git log --format=%B -1 >actual &&
+ test_cmp expect actual
+
+'
test_expect_success 'with a branch tip that was cherry-picked already' '
git checkout -b already-upstream main &&
base="$(git rev-parse --verify HEAD)" &&
diff --git a/t/t3435-rebase-gpg-sign.sh b/t/t3435-rebase-gpg-sign.sh
index ec10766858..5f8ba2c739 100755
--- a/t/t3435-rebase-gpg-sign.sh
+++ b/t/t3435-rebase-gpg-sign.sh
@@ -65,6 +65,7 @@ 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' '
+ test_when_finished "git clean -f" &&
git reset --hard merged &&
git config commit.gpgsign true &&
git rebase -p --no-gpg-sign --onto=one fork-point main &&
diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh
index 9d100cd188..4b5b607673 100755
--- a/t/t3501-revert-cherry-pick.sh
+++ b/t/t3501-revert-cherry-pick.sh
@@ -158,4 +158,20 @@ test_expect_success 'cherry-pick works with dirty renamed file' '
grep -q "^modified$" renamed
'
+test_expect_success 'advice from failed revert' '
+ test_commit --no-tag "add dream" dream dream &&
+ dream_oid=$(git rev-parse --short HEAD) &&
+ cat <<-EOF >expected &&
+ error: could not revert $dream_oid... add dream
+ hint: After resolving the conflicts, mark them with
+ hint: "git add/rm <pathspec>", then run
+ hint: "git revert --continue".
+ hint: You can instead skip this commit with "git revert --skip".
+ hint: To abort and get back to the state before "git revert",
+ hint: run "git revert --abort".
+ EOF
+ test_commit --append --no-tag "double-add dream" dream dream &&
+ test_must_fail git revert HEAD^ 2>actual &&
+ test_cmp expected actual
+'
test_done
diff --git a/t/t3507-cherry-pick-conflict.sh b/t/t3507-cherry-pick-conflict.sh
index 014001b8f3..979e843c65 100755
--- a/t/t3507-cherry-pick-conflict.sh
+++ b/t/t3507-cherry-pick-conflict.sh
@@ -47,20 +47,23 @@ test_expect_success 'failed cherry-pick does not advance HEAD' '
test "$head" = "$newhead"
'
-test_expect_success 'advice from failed cherry-pick' "
+test_expect_success 'advice from failed cherry-pick' '
pristine_detach initial &&
- picked=\$(git rev-parse --short picked) &&
+ picked=$(git rev-parse --short picked) &&
cat <<-EOF >expected &&
- error: could not apply \$picked... picked
- hint: after resolving the conflicts, mark the corrected paths
- hint: with 'git add <paths>' or 'git rm <paths>'
- hint: and commit the result with 'git commit'
+ error: could not apply $picked... picked
+ hint: After resolving the conflicts, mark them with
+ hint: "git add/rm <pathspec>", then run
+ hint: "git cherry-pick --continue".
+ hint: You can instead skip this commit with "git cherry-pick --skip".
+ hint: To abort and get back to the state before "git cherry-pick",
+ hint: run "git cherry-pick --abort".
EOF
test_must_fail git cherry-pick picked 2>actual &&
test_cmp expected actual
-"
+'
test_expect_success 'advice from failed cherry-pick --no-commit' "
pristine_detach initial &&
diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh
index 49010aa946..3b0fa66c33 100755
--- a/t/t3510-cherry-pick-sequence.sh
+++ b/t/t3510-cherry-pick-sequence.sh
@@ -238,6 +238,7 @@ test_expect_success 'allow skipping commit but not abort for a new history' '
'
test_expect_success 'allow skipping stopped cherry-pick because of untracked file modifications' '
+ test_when_finished "rm unrelated" &&
pristine_detach initial &&
git rm --cached unrelated &&
git commit -m "untrack unrelated" &&
diff --git a/t/t3602-rm-sparse-checkout.sh b/t/t3602-rm-sparse-checkout.sh
index e9e9a15c74..ecce497a9c 100755
--- a/t/t3602-rm-sparse-checkout.sh
+++ b/t/t3602-rm-sparse-checkout.sh
@@ -11,12 +11,15 @@ test_expect_success 'setup' "
git commit -m files &&
cat >sparse_error_header <<-EOF &&
- The following pathspecs didn't match any eligible path, but they do match index
- entries outside the current sparse checkout:
+ The following paths and/or pathspecs matched paths that exist
+ outside of your sparse-checkout definition, so will not be
+ updated in the index:
EOF
cat >sparse_hint <<-EOF &&
- hint: Disable or modify the sparsity rules if you intend to update such entries.
+ hint: If you intend to update such entries, try one of the following:
+ hint: * Use the --sparse option.
+ hint: * Disable or modify the sparsity rules.
hint: Disable this message with \"git config advice.updateSparsePath false\"
EOF
@@ -37,9 +40,25 @@ done
test_expect_success 'recursive rm does not remove sparse entries' '
git reset --hard &&
git sparse-checkout set sub/dir &&
- git rm -r sub &&
+ test_must_fail git rm -r sub &&
+ git rm --sparse -r sub &&
git status --porcelain -uno >actual &&
- echo "D sub/dir/e" >expected &&
+ cat >expected <<-\EOF &&
+ D sub/d
+ D sub/dir/e
+ EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'recursive rm --sparse removes sparse entries' '
+ git reset --hard &&
+ git sparse-checkout set "sub/dir" &&
+ git rm --sparse -r sub &&
+ git status --porcelain -uno >actual &&
+ cat >expected <<-\EOF &&
+ D sub/d
+ D sub/dir/e
+ EOF
test_cmp expected actual
'
@@ -75,4 +94,15 @@ test_expect_success 'do not warn about sparse entries with --ignore-unmatch' '
git ls-files --error-unmatch b
'
+test_expect_success 'refuse to rm a non-skip-worktree path outside sparse cone' '
+ git reset --hard &&
+ git sparse-checkout set a &&
+ git update-index --no-skip-worktree b &&
+ test_must_fail git rm b 2>stderr &&
+ test_cmp b_error_and_hint stderr &&
+ git rm --sparse b 2>stderr &&
+ test_must_be_empty stderr &&
+ test_path_is_missing b
+'
+
test_done
diff --git a/t/t3705-add-sparse-checkout.sh b/t/t3705-add-sparse-checkout.sh
index 2b1fd0d0ee..5b904988d4 100755
--- a/t/t3705-add-sparse-checkout.sh
+++ b/t/t3705-add-sparse-checkout.sh
@@ -19,6 +19,7 @@ setup_sparse_entry () {
fi &&
git add sparse_entry &&
git update-index --skip-worktree sparse_entry &&
+ git commit --allow-empty -m "ensure sparse_entry exists at HEAD" &&
SPARSE_ENTRY_BLOB=$(git rev-parse :sparse_entry)
}
@@ -36,14 +37,22 @@ setup_gitignore () {
EOF
}
+test_sparse_entry_unstaged () {
+ git diff --staged -- sparse_entry >diff &&
+ test_must_be_empty diff
+}
+
test_expect_success 'setup' "
cat >sparse_error_header <<-EOF &&
- The following pathspecs didn't match any eligible path, but they do match index
- entries outside the current sparse checkout:
+ The following paths and/or pathspecs matched paths that exist
+ outside of your sparse-checkout definition, so will not be
+ updated in the index:
EOF
cat >sparse_hint <<-EOF &&
- hint: Disable or modify the sparsity rules if you intend to update such entries.
+ hint: If you intend to update such entries, try one of the following:
+ hint: * Use the --sparse option.
+ hint: * Disable or modify the sparsity rules.
hint: Disable this message with \"git config advice.updateSparsePath false\"
EOF
@@ -55,6 +64,7 @@ test_expect_success 'git add does not remove sparse entries' '
setup_sparse_entry &&
rm sparse_entry &&
test_must_fail git add sparse_entry 2>stderr &&
+ test_sparse_entry_unstaged &&
test_cmp error_and_hint stderr &&
test_sparse_entry_unchanged
'
@@ -73,6 +83,7 @@ test_expect_success 'git add . does not remove sparse entries' '
rm sparse_entry &&
setup_gitignore &&
test_must_fail git add . 2>stderr &&
+ test_sparse_entry_unstaged &&
cat sparse_error_header >expect &&
echo . >>expect &&
@@ -88,6 +99,7 @@ do
setup_sparse_entry &&
echo modified >sparse_entry &&
test_must_fail git add $opt sparse_entry 2>stderr &&
+ test_sparse_entry_unstaged &&
test_cmp error_and_hint stderr &&
test_sparse_entry_unchanged
'
@@ -98,6 +110,7 @@ test_expect_success 'git add --refresh does not update sparse entries' '
git ls-files --debug sparse_entry | grep mtime >before &&
test-tool chmtime -60 sparse_entry &&
test_must_fail git add --refresh sparse_entry 2>stderr &&
+ test_sparse_entry_unstaged &&
test_cmp error_and_hint stderr &&
git ls-files --debug sparse_entry | grep mtime >after &&
test_cmp before after
@@ -106,6 +119,7 @@ test_expect_success 'git add --refresh does not update sparse entries' '
test_expect_success 'git add --chmod does not update sparse entries' '
setup_sparse_entry &&
test_must_fail git add --chmod=+x sparse_entry 2>stderr &&
+ test_sparse_entry_unstaged &&
test_cmp error_and_hint stderr &&
test_sparse_entry_unchanged &&
! test -x sparse_entry
@@ -116,6 +130,7 @@ test_expect_success 'git add --renormalize does not update sparse entries' '
setup_sparse_entry "LINEONE\r\nLINETWO\r\n" &&
echo "sparse_entry text=auto" >.gitattributes &&
test_must_fail git add --renormalize sparse_entry 2>stderr &&
+ test_sparse_entry_unstaged &&
test_cmp error_and_hint stderr &&
test_sparse_entry_unchanged
'
@@ -124,6 +139,7 @@ test_expect_success 'git add --dry-run --ignore-missing warn on sparse path' '
setup_sparse_entry &&
rm sparse_entry &&
test_must_fail git add --dry-run --ignore-missing sparse_entry 2>stderr &&
+ test_sparse_entry_unstaged &&
test_cmp error_and_hint stderr &&
test_sparse_entry_unchanged
'
@@ -145,11 +161,57 @@ test_expect_success 'do not warn when pathspec matches dense entries' '
git ls-files --error-unmatch dense_entry
'
+test_expect_success 'git add fails outside of sparse-checkout definition' '
+ test_when_finished git sparse-checkout disable &&
+ test_commit a &&
+ git sparse-checkout init &&
+ git sparse-checkout set a &&
+ echo >>sparse_entry &&
+
+ git update-index --no-skip-worktree sparse_entry &&
+ test_must_fail git add sparse_entry &&
+ test_sparse_entry_unstaged &&
+
+ test_must_fail git add --chmod=+x sparse_entry &&
+ test_sparse_entry_unstaged &&
+
+ test_must_fail git add --renormalize sparse_entry &&
+ test_sparse_entry_unstaged &&
+
+ # Avoid munging CRLFs to avoid an error message
+ git -c core.autocrlf=input add --sparse sparse_entry 2>stderr &&
+ test_must_be_empty stderr &&
+ test-tool read-cache --table >actual &&
+ grep "^100644 blob.*sparse_entry\$" actual &&
+
+ git add --sparse --chmod=+x sparse_entry 2>stderr &&
+ test_must_be_empty stderr &&
+ test-tool read-cache --table >actual &&
+ grep "^100755 blob.*sparse_entry\$" actual &&
+
+ git reset &&
+
+ # This will print a message over stderr on Windows.
+ git add --sparse --renormalize sparse_entry &&
+ git status --porcelain >actual &&
+ grep "^M sparse_entry\$" actual
+'
+
test_expect_success 'add obeys advice.updateSparsePath' '
setup_sparse_entry &&
test_must_fail git -c advice.updateSparsePath=false add sparse_entry 2>stderr &&
+ test_sparse_entry_unstaged &&
test_cmp sparse_entry_error stderr
'
+test_expect_success 'add allows sparse entries with --sparse' '
+ git sparse-checkout set a &&
+ echo modified >sparse_entry &&
+ test_must_fail git add sparse_entry &&
+ test_sparse_entry_unchanged &&
+ git add --sparse sparse_entry 2>stderr &&
+ test_must_be_empty stderr
+'
+
test_done
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 873aa56e35..f0a82be9de 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -1307,4 +1307,62 @@ test_expect_success 'stash -c stash.useBuiltin=false warning ' '
test_must_be_empty err
'
+test_expect_success 'git stash succeeds despite directory/file change' '
+ test_create_repo directory_file_switch_v1 &&
+ (
+ cd directory_file_switch_v1 &&
+ test_commit init &&
+
+ test_write_lines this file has some words >filler &&
+ git add filler &&
+ git commit -m filler &&
+
+ git rm filler &&
+ mkdir filler &&
+ echo contents >filler/file &&
+ git stash push
+ )
+'
+
+test_expect_success 'git stash can pop file -> directory saved changes' '
+ test_create_repo directory_file_switch_v2 &&
+ (
+ cd directory_file_switch_v2 &&
+ test_commit init &&
+
+ test_write_lines this file has some words >filler &&
+ git add filler &&
+ git commit -m filler &&
+
+ git rm filler &&
+ mkdir filler &&
+ echo contents >filler/file &&
+ cp filler/file expect &&
+ git stash push --include-untracked &&
+ git stash apply --index &&
+ test_cmp expect filler/file
+ )
+'
+
+test_expect_success 'git stash can pop directory -> file saved changes' '
+ test_create_repo directory_file_switch_v3 &&
+ (
+ cd directory_file_switch_v3 &&
+ test_commit init &&
+
+ mkdir filler &&
+ test_write_lines some words >filler/file1 &&
+ test_write_lines and stuff >filler/file2 &&
+ git add filler &&
+ git commit -m filler &&
+
+ git rm -rf filler &&
+ echo contents >filler &&
+ cp filler expect &&
+ git stash push --include-untracked &&
+ git stash apply --index &&
+ test_cmp expect filler
+ )
+'
+
test_done
diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh
index e561a8e485..28683d059d 100755
--- a/t/t4013-diff-various.sh
+++ b/t/t4013-diff-various.sh
@@ -65,7 +65,7 @@ test_expect_success setup '
export GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&
git checkout master &&
- git pull -s ours . side &&
+ git pull -s ours --no-rebase . side &&
GIT_AUTHOR_DATE="2006-06-26 00:05:00 +0000" &&
GIT_COMMITTER_DATE="2006-06-26 00:05:00 +0000" &&
diff --git a/t/t4018/java-class-member-function b/t/t4018/java-class-member-function
index 298bc7a71b..3b95f68b3b 100644
--- a/t/t4018/java-class-member-function
+++ b/t/t4018/java-class-member-function
@@ -3,6 +3,10 @@ public class Beer
int special;
public static void main(String RIGHT[])
{
+ someMethodCall();
+ someOtherMethod("17")
+ .doThat();
+ // Whatever
System.out.print("ChangeMe");
}
}
diff --git a/t/t4018/java-enum-constant b/t/t4018/java-enum-constant
new file mode 100644
index 0000000000..a1931c8379
--- /dev/null
+++ b/t/t4018/java-enum-constant
@@ -0,0 +1,6 @@
+private enum RIGHT {
+ ONE,
+ TWO,
+ THREE,
+ ChangeMe
+}
diff --git a/t/t4018/java-method-return-generic-bounded b/t/t4018/java-method-return-generic-bounded
new file mode 100644
index 0000000000..66dd78c379
--- /dev/null
+++ b/t/t4018/java-method-return-generic-bounded
@@ -0,0 +1,9 @@
+class MyExample {
+ public <T extends Bar & Foo<T>, R> Map<T, R[]> foo(String[] RIGHT) {
+ someMethodCall();
+ someOtherMethod()
+ .doThat();
+ // Whatever...
+ return (List<T>) Arrays.asList("ChangeMe");
+ }
+}
diff --git a/t/t4018/java-method-return-generic-wildcard b/t/t4018/java-method-return-generic-wildcard
new file mode 100644
index 0000000000..96e9e5f2c1
--- /dev/null
+++ b/t/t4018/java-method-return-generic-wildcard
@@ -0,0 +1,9 @@
+class MyExample {
+ public List<? extends Comparable> foo(String[] RIGHT) {
+ someMethodCall();
+ someOtherMethod()
+ .doThat();
+ // Whatever...
+ return Arrays.asList("ChangeMe");
+ }
+}
diff --git a/t/t4018/java-nested-field b/t/t4018/java-nested-field
new file mode 100644
index 0000000000..d92d3ec688
--- /dev/null
+++ b/t/t4018/java-nested-field
@@ -0,0 +1,6 @@
+class MyExample {
+ private static class RIGHT {
+ // change an inner class field
+ String inner = "ChangeMe";
+ }
+}
diff --git a/t/t4018/php-enum b/t/t4018/php-enum
new file mode 100644
index 0000000000..91a69c1a2b
--- /dev/null
+++ b/t/t4018/php-enum
@@ -0,0 +1,4 @@
+enum RIGHT: string
+{
+ case Foo = 'ChangeMe';
+}
diff --git a/t/t4045-diff-relative.sh b/t/t4045-diff-relative.sh
index 61ba5f707f..fab351b48a 100755
--- a/t/t4045-diff-relative.sh
+++ b/t/t4045-diff-relative.sh
@@ -162,4 +162,57 @@ 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_expect_success 'setup diff --relative unmerged' '
+ test_commit zero file0 &&
+ test_commit base subdir/file0 &&
+ git switch -c br1 &&
+ test_commit one file0 &&
+ test_commit sub1 subdir/file0 &&
+ git switch -c br2 base &&
+ test_commit two file0 &&
+ git switch -c br3 &&
+ test_commit sub3 subdir/file0
+'
+
+test_expect_success 'diff --relative without change in subdir' '
+ git switch br2 &&
+ test_when_finished "git merge --abort" &&
+ test_must_fail git merge one &&
+ git -C subdir diff --relative >out &&
+ test_must_be_empty out &&
+ git -C subdir diff --relative --name-only >out &&
+ test_must_be_empty out
+'
+
+test_expect_success 'diff --relative --name-only with change in subdir' '
+ git switch br3 &&
+ test_when_finished "git merge --abort" &&
+ test_must_fail git merge sub1 &&
+ test_write_lines file0 file0 >expected &&
+ git -C subdir diff --relative --name-only >out &&
+ test_cmp expected out
+'
+
+test_expect_failure 'diff --relative with change in subdir' '
+ git switch br3 &&
+ br1_blob=$(git rev-parse --short --verify br1:subdir/file0) &&
+ br3_blob=$(git rev-parse --short --verify br3:subdir/file0) &&
+ test_when_finished "git merge --abort" &&
+ test_must_fail git merge br1 &&
+ cat >expected <<-EOF &&
+ diff --cc file0
+ index $br3_blob,$br1_blob..0000000
+ --- a/file0
+ +++ b/file0
+ @@@ -1,1 -1,1 +1,5 @@@
+ ++<<<<<<< HEAD
+ +sub3
+ ++=======
+ + sub1
+ ++>>>>>>> br1
+ EOF
+ git -C subdir diff --relative >out &&
+ test_cmp expected out
+'
+
test_done
diff --git a/t/t4060-diff-submodule-option-diff-format.sh b/t/t4060-diff-submodule-option-diff-format.sh
index dc7b242697..d86e38abd8 100755
--- a/t/t4060-diff-submodule-option-diff-format.sh
+++ b/t/t4060-diff-submodule-option-diff-format.sh
@@ -361,7 +361,6 @@ test_expect_success 'typechanged submodule(submodule->blob)' '
rm -f sm1 &&
test_create_repo sm1 &&
head6=$(add_file sm1 foo6 foo7)
-fullhead6=$(cd sm1; git rev-parse --verify HEAD)
test_expect_success 'nonexistent commit' '
git diff-index -p --submodule=diff HEAD >actual &&
cat >expected <<-EOF &&
@@ -704,10 +703,26 @@ test_expect_success 'path filter' '
diff_cmp expected actual
'
-commit_file sm2
+cat >.gitmodules <<-EOF
+[submodule "sm2"]
+ path = sm2
+ url = bogus_url
+EOF
+git add .gitmodules
+commit_file sm2 .gitmodules
+
test_expect_success 'given commit' '
git diff-index -p --submodule=diff HEAD^ >actual &&
cat >expected <<-EOF &&
+ diff --git a/.gitmodules b/.gitmodules
+ new file mode 100644
+ index 1234567..89abcde
+ --- /dev/null
+ +++ b/.gitmodules
+ @@ -0,0 +1,3 @@
+ +[submodule "sm2"]
+ +path = sm2
+ +url = bogus_url
Submodule sm1 $head7...0000000 (submodule deleted)
Submodule sm2 0000000...$head9 (new submodule)
diff --git a/sm2/foo8 b/sm2/foo8
@@ -729,15 +744,21 @@ test_expect_success 'given commit' '
'
test_expect_success 'setup .git file for sm2' '
- (cd sm2 &&
- REAL="$(pwd)/../.real" &&
- mv .git "$REAL" &&
- echo "gitdir: $REAL" >.git)
+ git submodule absorbgitdirs sm2
'
test_expect_success 'diff --submodule=diff with .git file' '
git diff --submodule=diff HEAD^ >actual &&
cat >expected <<-EOF &&
+ diff --git a/.gitmodules b/.gitmodules
+ new file mode 100644
+ index 1234567..89abcde
+ --- /dev/null
+ +++ b/.gitmodules
+ @@ -0,0 +1,3 @@
+ +[submodule "sm2"]
+ +path = sm2
+ +url = bogus_url
Submodule sm1 $head7...0000000 (submodule deleted)
Submodule sm2 0000000...$head9 (new submodule)
diff --git a/sm2/foo8 b/sm2/foo8
@@ -758,9 +779,67 @@ test_expect_success 'diff --submodule=diff with .git file' '
diff_cmp expected actual
'
+mv sm2 sm2-bak
+
+test_expect_success 'deleted submodule with .git file' '
+ git diff-index -p --submodule=diff HEAD >actual &&
+ cat >expected <<-EOF &&
+ Submodule sm1 $head7...0000000 (submodule deleted)
+ Submodule sm2 $head9...0000000 (submodule deleted)
+ diff --git a/sm2/foo8 b/sm2/foo8
+ deleted file mode 100644
+ index 1234567..89abcde
+ --- a/sm2/foo8
+ +++ /dev/null
+ @@ -1 +0,0 @@
+ -foo8
+ diff --git a/sm2/foo9 b/sm2/foo9
+ deleted file mode 100644
+ index 1234567..89abcde
+ --- a/sm2/foo9
+ +++ /dev/null
+ @@ -1 +0,0 @@
+ -foo9
+ EOF
+ diff_cmp expected actual
+'
+
+echo submodule-to-blob>sm2
+
+test_expect_success 'typechanged(submodule->blob) submodule with .git file' '
+ git diff-index -p --submodule=diff HEAD >actual &&
+ cat >expected <<-EOF &&
+ Submodule sm1 $head7...0000000 (submodule deleted)
+ Submodule sm2 $head9...0000000 (submodule deleted)
+ diff --git a/sm2/foo8 b/sm2/foo8
+ deleted file mode 100644
+ index 1234567..89abcde
+ --- a/sm2/foo8
+ +++ /dev/null
+ @@ -1 +0,0 @@
+ -foo8
+ diff --git a/sm2/foo9 b/sm2/foo9
+ deleted file mode 100644
+ index 1234567..89abcde
+ --- a/sm2/foo9
+ +++ /dev/null
+ @@ -1 +0,0 @@
+ -foo9
+ diff --git a/sm2 b/sm2
+ new file mode 100644
+ index 1234567..89abcde
+ --- /dev/null
+ +++ b/sm2
+ @@ -0,0 +1 @@
+ +submodule-to-blob
+ EOF
+ diff_cmp expected actual
+'
+
+rm sm2
+mv sm2-bak sm2
+
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" &&
head10=$(git -C sm2 rev-parse --short --verify HEAD)
@@ -791,6 +870,7 @@ test_expect_success 'diff --submodule=diff with moved nested submodule HEAD' '
test_expect_success 'diff --submodule=diff recurses into nested submodules' '
cat >expected <<-EOF &&
+ Submodule sm1 $head7...0000000 (submodule deleted)
Submodule sm2 contains modified content
Submodule sm2 $head9..$head10:
diff --git a/sm2/.gitmodules b/sm2/.gitmodules
@@ -830,4 +910,67 @@ test_expect_success 'diff --submodule=diff recurses into nested submodules' '
diff_cmp expected actual
'
+(cd sm2; commit_file nested)
+commit_file sm2
+head12=$(cd sm2; git rev-parse --short --verify HEAD)
+
+mv sm2 sm2-bak
+
+test_expect_success 'diff --submodule=diff recurses into deleted nested submodules' '
+ cat >expected <<-EOF &&
+ Submodule sm1 $head7...0000000 (submodule deleted)
+ Submodule sm2 $head12...0000000 (submodule deleted)
+ diff --git a/sm2/.gitmodules b/sm2/.gitmodules
+ deleted file mode 100644
+ index 3a816b8..0000000
+ --- a/sm2/.gitmodules
+ +++ /dev/null
+ @@ -1,3 +0,0 @@
+ -[submodule "nested"]
+ - path = nested
+ - url = ../sm2
+ diff --git a/sm2/foo8 b/sm2/foo8
+ deleted file mode 100644
+ index db9916b..0000000
+ --- a/sm2/foo8
+ +++ /dev/null
+ @@ -1 +0,0 @@
+ -foo8
+ diff --git a/sm2/foo9 b/sm2/foo9
+ deleted file mode 100644
+ index 9c3b4f6..0000000
+ --- a/sm2/foo9
+ +++ /dev/null
+ @@ -1 +0,0 @@
+ -foo9
+ Submodule nested $head11...0000000 (submodule deleted)
+ diff --git a/sm2/nested/file b/sm2/nested/file
+ deleted file mode 100644
+ index ca281f5..0000000
+ --- a/sm2/nested/file
+ +++ /dev/null
+ @@ -1 +0,0 @@
+ -nested content
+ diff --git a/sm2/nested/foo8 b/sm2/nested/foo8
+ deleted file mode 100644
+ index db9916b..0000000
+ --- a/sm2/nested/foo8
+ +++ /dev/null
+ @@ -1 +0,0 @@
+ -foo8
+ diff --git a/sm2/nested/foo9 b/sm2/nested/foo9
+ deleted file mode 100644
+ index 9c3b4f6..0000000
+ --- a/sm2/nested/foo9
+ +++ /dev/null
+ @@ -1 +0,0 @@
+ -foo9
+ EOF
+ git diff --submodule=diff >actual 2>err &&
+ test_must_be_empty err &&
+ diff_cmp expected actual
+'
+
+mv sm2-bak sm2
+
test_done
diff --git a/t/t4103-apply-binary.sh b/t/t4103-apply-binary.sh
index fad6d3f542..d370ecfe0d 100755
--- a/t/t4103-apply-binary.sh
+++ b/t/t4103-apply-binary.sh
@@ -158,4 +158,27 @@ test_expect_success 'apply binary -p0 diff' '
test -z "$(git diff --name-status binary -- file3)"
'
+test_expect_success 'reject truncated binary diff' '
+ do_reset &&
+
+ # this length is calculated to get us very close to
+ # the 8192-byte strbuf we will use to read in the patch.
+ test-tool genrandom foo 6205 >file1 &&
+ git diff --binary >patch &&
+
+ # truncate the patch at the second "literal" line,
+ # but exclude the trailing newline. We must use perl
+ # for this, since tools like "sed" cannot reliably
+ # produce output without the trailing newline.
+ perl -pe "
+ if (/^literal/ && \$count++ >= 1) {
+ chomp;
+ print;
+ exit 0;
+ }
+ " <patch >patch.trunc &&
+
+ do_reset &&
+ test_must_fail git apply patch.trunc
+'
test_done
diff --git a/t/t4108-apply-threeway.sh b/t/t4108-apply-threeway.sh
index 65147efdea..cc3aa3314a 100755
--- a/t/t4108-apply-threeway.sh
+++ b/t/t4108-apply-threeway.sh
@@ -230,4 +230,49 @@ test_expect_success 'apply with --3way --cached and conflicts' '
test_cmp expect.diff actual.diff
'
+test_expect_success 'apply binary file patch' '
+ git reset --hard main &&
+ cp "$TEST_DIRECTORY/test-binary-1.png" bin.png &&
+ git add bin.png &&
+ git commit -m "add binary file" &&
+
+ cp "$TEST_DIRECTORY/test-binary-2.png" bin.png &&
+
+ git diff --binary >bin.diff &&
+ git reset --hard &&
+
+ # Apply must succeed.
+ git apply bin.diff
+'
+
+test_expect_success 'apply binary file patch with 3way' '
+ git reset --hard main &&
+ cp "$TEST_DIRECTORY/test-binary-1.png" bin.png &&
+ git add bin.png &&
+ git commit -m "add binary file" &&
+
+ cp "$TEST_DIRECTORY/test-binary-2.png" bin.png &&
+
+ git diff --binary >bin.diff &&
+ git reset --hard &&
+
+ # Apply must succeed.
+ git apply --3way --index bin.diff
+'
+
+test_expect_success 'apply full-index patch with 3way' '
+ git reset --hard main &&
+ cp "$TEST_DIRECTORY/test-binary-1.png" bin.png &&
+ git add bin.png &&
+ git commit -m "add binary file" &&
+
+ cp "$TEST_DIRECTORY/test-binary-2.png" bin.png &&
+
+ git diff --full-index >bin.diff &&
+ git reset --hard &&
+
+ # Apply must succeed.
+ git apply --3way --index bin.diff
+'
+
test_done
diff --git a/t/t4151-am-abort.sh b/t/t4151-am-abort.sh
index 9d8d3c72e7..2374151662 100755
--- a/t/t4151-am-abort.sh
+++ b/t/t4151-am-abort.sh
@@ -23,7 +23,13 @@ test_expect_success setup '
test_tick &&
git commit -a -m $i || return 1
done &&
+ git branch changes &&
git format-patch --no-numbered initial &&
+ git checkout -b conflicting initial &&
+ echo different >>file-1 &&
+ echo whatever >new-file &&
+ git add file-1 new-file &&
+ git commit -m different &&
git checkout -b side initial &&
echo local change >file-2-expect
'
@@ -191,4 +197,37 @@ test_expect_success 'am --abort leaves index stat info alone' '
git diff-files --exit-code --quiet
'
+test_expect_success 'git am --abort return failed exit status when it fails' '
+ test_when_finished "rm -rf file-2/ && git reset --hard && git am --abort" &&
+ git checkout changes &&
+ git format-patch -1 --stdout conflicting >changes.mbox &&
+ test_must_fail git am --3way changes.mbox &&
+
+ git rm file-2 &&
+ mkdir file-2 &&
+ echo precious >file-2/somefile &&
+ test_must_fail git am --abort &&
+ test_path_is_dir file-2/
+'
+
+test_expect_success 'git am --abort cleans relevant files' '
+ git checkout changes &&
+ git format-patch -1 --stdout conflicting >changes.mbox &&
+ test_must_fail git am --3way changes.mbox &&
+
+ test_path_is_file new-file &&
+ echo further changes >>file-1 &&
+ echo change other file >>file-2 &&
+
+ # Abort, and expect the files touched by am to be reverted
+ git am --abort &&
+
+ test_path_is_missing new-file &&
+
+ # Files not involved in am operation are left modified
+ git diff --name-only changes >actual &&
+ test_write_lines file-2 >expect &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t4210-log-i18n.sh b/t/t4210-log-i18n.sh
index d2dfcf164e..0141f36e33 100755
--- a/t/t4210-log-i18n.sh
+++ b/t/t4210-log-i18n.sh
@@ -131,4 +131,11 @@ do
fi
done
+test_expect_success 'log shows warning when conversion fails' '
+ enc=this-encoding-does-not-exist &&
+ git log -1 --encoding=$enc 2>err &&
+ echo "warning: unable to reencode commit to ${SQ}${enc}${SQ}" >expect &&
+ test_cmp expect err
+'
+
test_done
diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh
index 5c5e53f0be..e13a884207 100755
--- a/t/t5300-pack-object.sh
+++ b/t/t5300-pack-object.sh
@@ -34,6 +34,110 @@ test_expect_success 'setup' '
} >expect
'
+test_expect_success 'setup pack-object <stdin' '
+ git init pack-object-stdin &&
+ test_commit -C pack-object-stdin one &&
+ test_commit -C pack-object-stdin two
+
+'
+
+test_expect_success 'pack-object <stdin parsing: basic [|--revs]' '
+ cat >in <<-EOF &&
+ $(git -C pack-object-stdin rev-parse one)
+ EOF
+
+ git -C pack-object-stdin pack-objects basic-stdin <in &&
+ idx=$(echo pack-object-stdin/basic-stdin-*.idx) &&
+ git show-index <"$idx" >actual &&
+ test_line_count = 1 actual &&
+
+ git -C pack-object-stdin pack-objects --revs basic-stdin-revs <in &&
+ idx=$(echo pack-object-stdin/basic-stdin-revs-*.idx) &&
+ git show-index <"$idx" >actual &&
+ test_line_count = 3 actual
+'
+
+test_expect_success 'pack-object <stdin parsing: [|--revs] bad line' '
+ cat >in <<-EOF &&
+ $(git -C pack-object-stdin rev-parse one)
+ garbage
+ $(git -C pack-object-stdin rev-parse two)
+ EOF
+
+ sed "s/^> //g" >err.expect <<-EOF &&
+ fatal: expected object ID, got garbage:
+ > garbage
+
+ EOF
+ test_must_fail git -C pack-object-stdin pack-objects bad-line-stdin <in 2>err.actual &&
+ test_cmp err.expect err.actual &&
+
+ cat >err.expect <<-EOF &&
+ fatal: bad revision '"'"'garbage'"'"'
+ EOF
+ test_must_fail git -C pack-object-stdin pack-objects --revs bad-line-stdin-revs <in 2>err.actual &&
+ test_cmp err.expect err.actual
+'
+
+test_expect_success 'pack-object <stdin parsing: [|--revs] empty line' '
+ cat >in <<-EOF &&
+ $(git -C pack-object-stdin rev-parse one)
+
+ $(git -C pack-object-stdin rev-parse two)
+ EOF
+
+ sed -e "s/^> //g" -e "s/Z$//g" >err.expect <<-EOF &&
+ fatal: expected object ID, got garbage:
+ > Z
+
+ EOF
+ test_must_fail git -C pack-object-stdin pack-objects empty-line-stdin <in 2>err.actual &&
+ test_cmp err.expect err.actual &&
+
+ git -C pack-object-stdin pack-objects --revs empty-line-stdin-revs <in &&
+ idx=$(echo pack-object-stdin/empty-line-stdin-revs-*.idx) &&
+ git show-index <"$idx" >actual &&
+ test_line_count = 3 actual
+'
+
+test_expect_success 'pack-object <stdin parsing: [|--revs] with --stdin' '
+ cat >in <<-EOF &&
+ $(git -C pack-object-stdin rev-parse one)
+ $(git -C pack-object-stdin rev-parse two)
+ EOF
+
+ # There is the "--stdin-packs is incompatible with --revs"
+ # test below, but we should make sure that the revision.c
+ # --stdin is not picked up
+ cat >err.expect <<-EOF &&
+ fatal: disallowed abbreviated or ambiguous option '"'"'stdin'"'"'
+ EOF
+ test_must_fail git -C pack-object-stdin pack-objects stdin-with-stdin-option --stdin <in 2>err.actual &&
+ test_cmp err.expect err.actual &&
+
+ test_must_fail git -C pack-object-stdin pack-objects --stdin --revs stdin-with-stdin-option-revs 2>err.actual <in &&
+ test_cmp err.expect err.actual
+'
+
+test_expect_success 'pack-object <stdin parsing: --stdin-packs handles garbage' '
+ cat >in <<-EOF &&
+ $(git -C pack-object-stdin rev-parse one)
+ $(git -C pack-object-stdin rev-parse two)
+ EOF
+
+ # That we get "two" and not "one" has to do with OID
+ # ordering. It happens to be the same here under SHA-1 and
+ # SHA-256. See commentary in pack-objects.c
+ cat >err.expect <<-EOF &&
+ fatal: could not find pack '"'"'$(git -C pack-object-stdin rev-parse two)'"'"'
+ EOF
+ test_must_fail git \
+ -C pack-object-stdin \
+ pack-objects stdin-with-stdin-option --stdin-packs \
+ <in 2>err.actual &&
+ test_cmp err.expect err.actual
+'
+
# usage: check_deltas <stderr_from_pack_objects> <cmp_op> <nr_deltas>
# e.g.: check_deltas stderr -gt 0
check_deltas() {
diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh
index 7cabb85ca6..8ae314af58 100755
--- a/t/t5304-prune.sh
+++ b/t/t5304-prune.sh
@@ -291,6 +291,7 @@ test_expect_success 'prune: handle HEAD reflog in multiple worktrees' '
cat ../expected >blob &&
git add blob &&
git commit -m "second commit in third" &&
+ git clean -f && # Remove untracked left behind by deleting index
git reset --hard HEAD^
) &&
git prune --expire=now &&
diff --git a/t/t5312-prune-corruption.sh b/t/t5312-prune-corruption.sh
index 11423b3cb2..ea889c088a 100755
--- a/t/t5312-prune-corruption.sh
+++ b/t/t5312-prune-corruption.sh
@@ -7,6 +7,9 @@ if we see, for example, a ref with a bogus name, it is OK either to
bail out or to proceed using it as a reachable tip, but it is _not_
OK to proceed as if it did not exist. Otherwise we might silently
delete objects that cannot be recovered.
+
+Note that we do assert command failure in these cases, because that is
+what currently happens. If that changes, these tests should be revisited.
'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
@@ -18,39 +21,58 @@ test_expect_success 'disable reflogs' '
git reflog expire --expire=all --all
'
+create_bogus_ref () {
+ test_when_finished 'rm -f .git/refs/heads/bogus..name' &&
+ echo $bogus >.git/refs/heads/bogus..name
+}
+
test_expect_success 'create history reachable only from a bogus-named ref' '
test_tick && git commit --allow-empty -m main &&
base=$(git rev-parse HEAD) &&
test_tick && git commit --allow-empty -m bogus &&
bogus=$(git rev-parse HEAD) &&
git cat-file commit $bogus >saved &&
- echo $bogus >.git/refs/heads/bogus..name &&
git reset --hard HEAD^
'
test_expect_success 'pruning does not drop bogus object' '
test_when_finished "git hash-object -w -t commit saved" &&
- test_might_fail git prune --expire=now &&
- verbose git cat-file -e $bogus
+ create_bogus_ref &&
+ test_must_fail git prune --expire=now &&
+ git cat-file -e $bogus
'
test_expect_success 'put bogus object into pack' '
git tag reachable $bogus &&
git repack -ad &&
git tag -d reachable &&
- verbose git cat-file -e $bogus
+ git cat-file -e $bogus
+'
+
+test_expect_success 'non-destructive repack bails on bogus ref' '
+ create_bogus_ref &&
+ test_must_fail git repack -adk
'
+test_expect_success 'GIT_REF_PARANOIA=0 overrides safety' '
+ create_bogus_ref &&
+ GIT_REF_PARANOIA=0 git repack -adk
+'
+
+
test_expect_success 'destructive repack keeps packed object' '
- test_might_fail git repack -Ad --unpack-unreachable=now &&
- verbose git cat-file -e $bogus &&
- test_might_fail git repack -ad &&
- verbose git cat-file -e $bogus
+ create_bogus_ref &&
+ test_must_fail git repack -Ad --unpack-unreachable=now &&
+ git cat-file -e $bogus &&
+ test_must_fail git repack -ad &&
+ git cat-file -e $bogus
'
-# subsequent tests will have different corruptions
-test_expect_success 'clean up bogus ref' '
- rm .git/refs/heads/bogus..name
+test_expect_success 'destructive repack not confused by dangling symref' '
+ test_when_finished "git symbolic-ref -d refs/heads/dangling" &&
+ git symbolic-ref refs/heads/dangling refs/heads/does-not-exist &&
+ git repack -ad &&
+ test_must_fail git cat-file -e $bogus
'
# We create two new objects here, "one" and "two". Our
@@ -77,8 +99,8 @@ test_expect_success 'create history with missing tip commit' '
test_expect_success 'pruning with a corrupted tip does not drop history' '
test_when_finished "git hash-object -w -t commit saved" &&
- test_might_fail git prune --expire=now &&
- verbose git cat-file -e $recoverable
+ test_must_fail git prune --expire=now &&
+ git cat-file -e $recoverable
'
test_expect_success 'pack-refs does not silently delete broken loose ref' '
diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh
index af88f805aa..295c5bd94d 100755
--- a/t/t5318-commit-graph.sh
+++ b/t/t5318-commit-graph.sh
@@ -5,6 +5,25 @@ test_description='commit graph'
GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=0
+test_expect_success 'usage' '
+ test_expect_code 129 git commit-graph write blah 2>err &&
+ test_expect_code 129 git commit-graph write verify
+'
+
+test_expect_success 'usage shown without sub-command' '
+ test_expect_code 129 git commit-graph 2>err &&
+ ! grep error: err
+'
+
+test_expect_success 'usage shown with an error on unknown sub-command' '
+ cat >expect <<-\EOF &&
+ error: unrecognized subcommand: unknown
+ EOF
+ test_expect_code 129 git commit-graph unknown 2>stderr &&
+ grep error stderr >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'setup full repo' '
mkdir full &&
cd "$TRASH_DIRECTORY/full" &&
diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh
index 385f0a3efd..a3c72b68f7 100755
--- a/t/t5319-multi-pack-index.sh
+++ b/t/t5319-multi-pack-index.sh
@@ -189,12 +189,12 @@ test_expect_success 'write progress off for redirected stderr' '
'
test_expect_success 'write force progress on for stderr' '
- GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir --progress write 2>err &&
+ GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir write --progress 2>err &&
test_file_not_empty err
'
test_expect_success 'write with the --no-progress option' '
- GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir --no-progress write 2>err &&
+ GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir write --no-progress 2>err &&
test_line_count = 0 err
'
@@ -489,12 +489,12 @@ test_expect_success 'repack progress off for redirected stderr' '
'
test_expect_success 'repack force progress on for stderr' '
- GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir --progress repack 2>err &&
+ GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir repack --progress 2>err &&
test_file_not_empty err
'
test_expect_success 'repack with the --no-progress option' '
- GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir --no-progress repack 2>err &&
+ GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir repack --no-progress 2>err &&
test_line_count = 0 err
'
@@ -687,7 +687,7 @@ test_expect_success 'expire progress off for redirected stderr' '
test_expect_success 'expire force progress on for stderr' '
(
cd dup &&
- GIT_PROGRESS_DELAY=0 git multi-pack-index --progress expire 2>err &&
+ GIT_PROGRESS_DELAY=0 git multi-pack-index expire --progress 2>err &&
test_file_not_empty err
)
'
@@ -695,7 +695,7 @@ test_expect_success 'expire force progress on for stderr' '
test_expect_success 'expire with the --no-progress option' '
(
cd dup &&
- GIT_PROGRESS_DELAY=0 git multi-pack-index --no-progress expire 2>err &&
+ GIT_PROGRESS_DELAY=0 git multi-pack-index expire --no-progress 2>err &&
test_line_count = 0 err
)
'
diff --git a/t/t5323-pack-redundant.sh b/t/t5323-pack-redundant.sh
index 8b01793845..8dbbcc5e51 100755
--- a/t/t5323-pack-redundant.sh
+++ b/t/t5323-pack-redundant.sh
@@ -114,9 +114,9 @@ test_expect_success 'setup main repo' '
create_commits_in "$main_repo" A B C D E F G H I J K L M N O P Q R
'
-test_expect_success 'master: pack-redundant works with no packfile' '
+test_expect_success 'main: pack-redundant works with no packfile' '
(
- cd "$master_repo" &&
+ cd "$main_repo" &&
cat >expect <<-EOF &&
fatal: Zero packs found!
EOF
diff --git a/t/t5326-multi-pack-bitmaps.sh b/t/t5326-multi-pack-bitmaps.sh
index 069dab3e17..e187f90f29 100755
--- a/t/t5326-multi-pack-bitmaps.sh
+++ b/t/t5326-multi-pack-bitmaps.sh
@@ -365,4 +365,34 @@ test_expect_success 'write a bitmap with --refs-snapshot (preferred tips)' '
)
'
+test_expect_success 'hash-cache values are propagated from pack bitmaps' '
+ rm -fr repo &&
+ git init repo &&
+ test_when_finished "rm -fr repo" &&
+ (
+ cd repo &&
+
+ test_commit base &&
+ test_commit base2 &&
+ git repack -adb &&
+
+ test-tool bitmap dump-hashes >pack.raw &&
+ test_file_not_empty pack.raw &&
+ sort pack.raw >pack.hashes &&
+
+ test_commit new &&
+ git repack &&
+ git multi-pack-index write --bitmap &&
+
+ test-tool bitmap dump-hashes >midx.raw &&
+ sort midx.raw >midx.hashes &&
+
+ # ensure that every namehash in the pack bitmap can be found in
+ # the midx bitmap (i.e., that there are no oid-namehash pairs
+ # unique to the pack bitmap).
+ comm -23 pack.hashes midx.hashes >dropped.hashes &&
+ test_must_be_empty dropped.hashes
+ )
+'
+
test_done
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index e83b2a6506..a0faf0dd94 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -1214,6 +1214,19 @@ test_expect_success '--negotiation-tip understands abbreviated SHA-1' '
check_negotiation_tip
'
+test_expect_success '--negotiation-tip rejects missing OIDs' '
+ setup_negotiation_tip server server 0 &&
+ test_must_fail git -C client fetch \
+ --negotiation-tip=alpha_1 \
+ --negotiation-tip=$(test_oid zero) \
+ origin alpha_s beta_s 2>err &&
+ cat >fatal-expect <<-EOF &&
+ fatal: the object $(test_oid zero) does not exist
+EOF
+ grep fatal: err >fatal-actual &&
+ test_cmp fatal-expect fatal-actual
+'
+
. "$TEST_DIRECTORY"/lib-httpd.sh
start_httpd
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 0916f76302..8212ca56dc 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -201,6 +201,7 @@ test_expect_success 'push with negotiation' '
# Without negotiation
mk_empty testrepo &&
git push testrepo $the_first_commit:refs/remotes/origin/first_commit &&
+ test_commit -C testrepo unrelated_commit &&
git -C testrepo config receive.hideRefs refs/remotes/origin/first_commit &&
echo now pushing without negotiation &&
GIT_TRACE2_EVENT="$(pwd)/event" git -c protocol.version=2 push testrepo refs/heads/main:refs/remotes/origin/main &&
@@ -210,6 +211,7 @@ test_expect_success 'push with negotiation' '
rm event &&
mk_empty testrepo &&
git push testrepo $the_first_commit:refs/remotes/origin/first_commit &&
+ test_commit -C testrepo unrelated_commit &&
git -C testrepo config receive.hideRefs refs/remotes/origin/first_commit &&
GIT_TRACE2_EVENT="$(pwd)/event" git -c protocol.version=2 -c push.negotiate=1 push testrepo refs/heads/main:refs/remotes/origin/main &&
grep_wrote 2 event # 1 commit, 1 tree
@@ -219,6 +221,7 @@ test_expect_success 'push with negotiation proceeds anyway even if negotiation f
rm event &&
mk_empty testrepo &&
git push testrepo $the_first_commit:refs/remotes/origin/first_commit &&
+ test_commit -C testrepo unrelated_commit &&
git -C testrepo config receive.hideRefs refs/remotes/origin/first_commit &&
GIT_TEST_PROTOCOL_VERSION=0 GIT_TRACE2_EVENT="$(pwd)/event" \
git -c push.negotiate=1 push testrepo refs/heads/main:refs/remotes/origin/main 2>err &&
@@ -659,10 +662,10 @@ test_expect_success 'push does not update local refs on failure' '
test_expect_success 'allow deleting an invalid remote ref' '
- mk_test testrepo heads/main &&
+ mk_test testrepo heads/branch &&
rm -f testrepo/.git/objects/??/* &&
- git push testrepo :refs/heads/main &&
- (cd testrepo && test_must_fail git rev-parse --verify refs/heads/main)
+ git push testrepo :refs/heads/branch &&
+ (cd testrepo && test_must_fail git rev-parse --verify refs/heads/branch)
'
@@ -703,25 +706,26 @@ test_expect_success 'pushing valid refs triggers post-receive and post-update ho
'
test_expect_success 'deleting dangling ref triggers hooks with correct args' '
- mk_test_with_hooks testrepo heads/main &&
+ mk_test_with_hooks testrepo heads/branch &&
+ orig=$(git -C testrepo rev-parse refs/heads/branch) &&
rm -f testrepo/.git/objects/??/* &&
- git push testrepo :refs/heads/main &&
+ git push testrepo :refs/heads/branch &&
(
cd testrepo/.git &&
cat >pre-receive.expect <<-EOF &&
- $ZERO_OID $ZERO_OID refs/heads/main
+ $orig $ZERO_OID refs/heads/branch
EOF
cat >update.expect <<-EOF &&
- refs/heads/main $ZERO_OID $ZERO_OID
+ refs/heads/branch $orig $ZERO_OID
EOF
cat >post-receive.expect <<-EOF &&
- $ZERO_OID $ZERO_OID refs/heads/main
+ $orig $ZERO_OID refs/heads/branch
EOF
cat >post-update.expect <<-EOF &&
- refs/heads/main
+ refs/heads/branch
EOF
test_cmp pre-receive.expect pre-receive.actual &&
@@ -1767,5 +1771,4 @@ test_expect_success 'denyCurrentBranch and worktrees' '
git -C cloned push origin HEAD:new-wt &&
test_must_fail git -C cloned push --delete origin new-wt
'
-
test_done
diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index e2c0c51022..93ecfcdd24 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -136,12 +136,12 @@ test_expect_success 'the default remote . should not break explicit pull' '
git reset --hard HEAD^ &&
echo file >expect &&
test_cmp expect file &&
- git pull . second &&
+ git pull --no-rebase . second &&
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 &&
+ echo "OBJID HEAD@{0}: pull --no-rebase . second: Fast-forward" >reflog.expected &&
test_cmp reflog.expected reflog.fuzzy
'
@@ -226,7 +226,7 @@ test_expect_success 'fail if the index has unresolved entries' '
test_commit modified2 file &&
git ls-files -u >unmerged &&
test_must_be_empty unmerged &&
- test_must_fail git pull . second &&
+ test_must_fail git pull --no-rebase . second &&
git ls-files -u >unmerged &&
test_file_not_empty unmerged &&
cp file expected &&
@@ -409,37 +409,37 @@ test_expect_success 'pull --rebase --no-autostash & rebase.autostash unset' '
test_expect_success 'pull succeeds with dirty working directory and merge.autostash set' '
test_config merge.autostash true &&
- test_pull_autostash 2
+ test_pull_autostash 2 --no-rebase
'
test_expect_success 'pull --autostash & merge.autostash=true' '
test_config merge.autostash true &&
- test_pull_autostash 2 --autostash
+ test_pull_autostash 2 --autostash --no-rebase
'
test_expect_success 'pull --autostash & merge.autostash=false' '
test_config merge.autostash false &&
- test_pull_autostash 2 --autostash
+ test_pull_autostash 2 --autostash --no-rebase
'
test_expect_success 'pull --autostash & merge.autostash unset' '
test_unconfig merge.autostash &&
- test_pull_autostash 2 --autostash
+ test_pull_autostash 2 --autostash --no-rebase
'
test_expect_success 'pull --no-autostash & merge.autostash=true' '
test_config merge.autostash true &&
- test_pull_autostash_fail --no-autostash
+ test_pull_autostash_fail --no-autostash --no-rebase
'
test_expect_success 'pull --no-autostash & merge.autostash=false' '
test_config merge.autostash false &&
- test_pull_autostash_fail --no-autostash
+ test_pull_autostash_fail --no-autostash --no-rebase
'
test_expect_success 'pull --no-autostash & merge.autostash unset' '
test_unconfig merge.autostash &&
- test_pull_autostash_fail --no-autostash
+ test_pull_autostash_fail --no-autostash --no-rebase
'
test_expect_success 'pull.rebase' '
@@ -546,15 +546,6 @@ test_expect_success 'pull.rebase=1 is treated as true and flattens keep-merge' '
test_cmp expect actual
'
-test_expect_success REBASE_P \
- 'pull.rebase=preserve rebases and merges keep-merge' '
- git reset --hard before-preserve-rebase &&
- test_config pull.rebase preserve &&
- git pull . copy &&
- test_cmp_rev HEAD^^ copy &&
- test_cmp_rev HEAD^2 keep-merge
-'
-
test_expect_success 'pull.rebase=interactive' '
write_script "$TRASH_DIRECTORY/fake-editor" <<-\EOF &&
echo I was here >fake.out &&
@@ -598,7 +589,7 @@ test_expect_success '--rebase=false create a new merge commit' '
test_expect_success '--rebase=true rebases and flattens keep-merge' '
git reset --hard before-preserve-rebase &&
- test_config pull.rebase preserve &&
+ test_config pull.rebase merges &&
git pull --rebase=true . copy &&
test_cmp_rev HEAD^^ copy &&
echo file3 >expect &&
@@ -606,23 +597,14 @@ test_expect_success '--rebase=true rebases and flattens keep-merge' '
test_cmp expect actual
'
-test_expect_success REBASE_P \
- '--rebase=preserve rebases and merges keep-merge' '
- git reset --hard before-preserve-rebase &&
- test_config pull.rebase true &&
- git pull --rebase=preserve . copy &&
- test_cmp_rev HEAD^^ copy &&
- test_cmp_rev HEAD^2 keep-merge
-'
-
test_expect_success '--rebase=invalid fails' '
git reset --hard before-preserve-rebase &&
test_must_fail git pull --rebase=invalid . copy
'
-test_expect_success '--rebase overrides pull.rebase=preserve and flattens keep-merge' '
+test_expect_success '--rebase overrides pull.rebase=merges and flattens keep-merge' '
git reset --hard before-preserve-rebase &&
- test_config pull.rebase preserve &&
+ test_config pull.rebase merges &&
git pull --rebase . copy &&
test_cmp_rev HEAD^^ copy &&
echo file3 >expect &&
diff --git a/t/t5521-pull-options.sh b/t/t5521-pull-options.sh
index 63a688bdbf..7601c919fd 100755
--- a/t/t5521-pull-options.sh
+++ b/t/t5521-pull-options.sh
@@ -113,7 +113,7 @@ test_expect_success 'git pull --force' '
git pull two &&
test_commit A &&
git branch -f origin &&
- git pull --all --force
+ git pull --no-rebase --all --force
)
'
@@ -179,7 +179,7 @@ test_expect_success 'git pull --allow-unrelated-histories' '
(
cd dst &&
test_must_fail git pull ../src side &&
- git pull --allow-unrelated-histories ../src side
+ git pull --no-rebase --allow-unrelated-histories ../src side
)
'
diff --git a/t/t5524-pull-msg.sh b/t/t5524-pull-msg.sh
index c278adaa5a..b2be3605f5 100755
--- a/t/t5524-pull-msg.sh
+++ b/t/t5524-pull-msg.sh
@@ -28,7 +28,7 @@ test_expect_success setup '
test_expect_success pull '
(
cd cloned &&
- git pull --log &&
+ git pull --no-rebase --log &&
git log -2 &&
git cat-file commit HEAD >result &&
grep Dollar result
@@ -41,7 +41,7 @@ test_expect_success '--log=1 limits shortlog length' '
git reset --hard HEAD^ &&
test "$(cat afile)" = original &&
test "$(cat bfile)" = added &&
- git pull --log=1 &&
+ git pull --no-rebase --log=1 &&
git log -3 &&
git cat-file commit HEAD >result &&
grep Dollar result &&
diff --git a/t/t5549-fetch-push-http.sh b/t/t5549-fetch-push-http.sh
new file mode 100755
index 0000000000..2cdebcb735
--- /dev/null
+++ b/t/t5549-fetch-push-http.sh
@@ -0,0 +1,72 @@
+#!/bin/sh
+
+test_description='fetch/push functionality using the HTTP protocol'
+
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-httpd.sh
+start_httpd
+
+SERVER="$HTTPD_DOCUMENT_ROOT_PATH/server"
+URI="$HTTPD_URL/smart/server"
+
+grep_wrote () {
+ object_count=$1
+ file_name=$2
+ grep 'write_pack_file/wrote.*"value":"'$1'"' $2
+}
+
+setup_client_and_server () {
+ git init client &&
+ test_when_finished 'rm -rf client' &&
+ test_commit -C client first_commit &&
+ test_commit -C client second_commit &&
+
+ git init "$SERVER" &&
+ test_when_finished 'rm -rf "$SERVER"' &&
+ test_config -C "$SERVER" http.receivepack true &&
+ test_commit -C "$SERVER" unrelated_commit &&
+ git -C client push "$URI" first_commit:refs/remotes/origin/first_commit &&
+ git -C "$SERVER" config receive.hideRefs refs/remotes/origin/first_commit
+}
+
+test_expect_success 'push without negotiation (for comparing object counts with the next test)' '
+ setup_client_and_server &&
+
+ GIT_TRACE2_EVENT="$(pwd)/event" git -C client -c protocol.version=2 \
+ push "$URI" refs/heads/main:refs/remotes/origin/main &&
+ test_when_finished "rm -f event" &&
+ grep_wrote 6 event # 2 commits, 2 trees, 2 blobs
+'
+
+test_expect_success 'push with negotiation' '
+ setup_client_and_server &&
+
+ GIT_TRACE2_EVENT="$(pwd)/event" git -C client -c protocol.version=2 -c push.negotiate=1 \
+ push "$URI" refs/heads/main:refs/remotes/origin/main &&
+ test_when_finished "rm -f event" &&
+ grep_wrote 3 event # 1 commit, 1 tree, 1 blob
+'
+
+test_expect_success 'push with negotiation proceeds anyway even if negotiation fails' '
+ setup_client_and_server &&
+
+ # Use protocol v0 to make negotiation fail (because protocol v0 does
+ # not support the "wait-for-done" capability, which is required for
+ # push negotiation)
+ GIT_TEST_PROTOCOL_VERSION=0 GIT_TRACE2_EVENT="$(pwd)/event" git -C client -c push.negotiate=1 \
+ push "$URI" refs/heads/main:refs/remotes/origin/main 2>err &&
+ test_when_finished "rm -f event" &&
+ grep_wrote 6 event && # 2 commits, 2 trees, 2 blobs
+
+ cat >warning-expect <<-EOF &&
+ warning: --negotiate-only requires protocol v2
+ warning: push negotiation failed; proceeding anyway with push
+EOF
+ grep warning: err >warning-actual &&
+ test_cmp warning-expect warning-actual
+'
+
+test_done
diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh
index 4f87d90c5b..f92c79c132 100755
--- a/t/t5551-http-fetch-smart.sh
+++ b/t/t5551-http-fetch-smart.sh
@@ -196,8 +196,8 @@ test_expect_success 'GIT_TRACE_CURL redacts auth details' '
# 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
+ ! grep -i "Authorization: Basic [0-9a-zA-Z+/]" trace &&
+ grep -i "Authorization: Basic <redacted>" trace
'
test_expect_success 'GIT_CURL_VERBOSE redacts auth details' '
@@ -208,8 +208,8 @@ test_expect_success 'GIT_CURL_VERBOSE redacts auth details' '
# 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
+ ! grep -i "Authorization: Basic [0-9a-zA-Z+/]" trace &&
+ grep -i "Authorization: Basic <redacted>" trace
'
test_expect_success 'GIT_TRACE_CURL does not redact auth details if GIT_TRACE_REDACT=0' '
@@ -219,7 +219,7 @@ test_expect_success 'GIT_TRACE_CURL does not redact auth details if GIT_TRACE_RE
git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth &&
expect_askpass both user@host &&
- grep "Authorization: Basic [0-9a-zA-Z+/]" trace
+ grep -i "Authorization: Basic [0-9a-zA-Z+/]" trace
'
test_expect_success 'disable dumb http on server' '
@@ -474,10 +474,10 @@ test_expect_success 'cookies are redacted by default' '
GIT_TRACE_CURL=true \
git -c "http.cookieFile=$(pwd)/cookies" clone \
$HTTPD_URL/smart/repo.git clone 2>err &&
- grep "Cookie:.*Foo=<redacted>" err &&
- grep "Cookie:.*Bar=<redacted>" err &&
- ! grep "Cookie:.*Foo=1" err &&
- ! grep "Cookie:.*Bar=2" err
+ grep -i "Cookie:.*Foo=<redacted>" err &&
+ grep -i "Cookie:.*Bar=<redacted>" err &&
+ ! grep -i "Cookie:.*Foo=1" err &&
+ ! grep -i "Cookie:.*Bar=2" err
'
test_expect_success 'empty values of cookies are also redacted' '
@@ -486,7 +486,7 @@ test_expect_success 'empty values of cookies are also redacted' '
GIT_TRACE_CURL=true \
git -c "http.cookieFile=$(pwd)/cookies" clone \
$HTTPD_URL/smart/repo.git clone 2>err &&
- grep "Cookie:.*Foo=<redacted>" err
+ grep -i "Cookie:.*Foo=<redacted>" err
'
test_expect_success 'GIT_TRACE_REDACT=0 disables cookie redaction' '
@@ -496,8 +496,8 @@ test_expect_success 'GIT_TRACE_REDACT=0 disables cookie redaction' '
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
+ grep -i "Cookie:.*Foo=1" err &&
+ grep -i "Cookie:.*Bar=2" err
'
test_expect_success 'GIT_TRACE_CURL_NO_DATA prevents data from being traced' '
@@ -558,4 +558,13 @@ test_expect_success 'http auth forgets bogus credentials' '
expect_askpass both user@host
'
+test_expect_success 'client falls back from v2 to v0 to match server' '
+ GIT_TRACE_PACKET=$PWD/trace \
+ GIT_TEST_PROTOCOL_VERSION=2 \
+ git clone $HTTPD_URL/smart_v0/repo.git repo-v0 &&
+ # check for v0; there the HEAD symref is communicated in the capability
+ # line; v2 uses a different syntax on each ref advertisement line
+ grep symref=HEAD:refs/heads/ trace
+'
+
test_done
diff --git a/t/t5553-set-upstream.sh b/t/t5553-set-upstream.sh
index b1d614ce18..9c12c0f8c3 100755
--- a/t/t5553-set-upstream.sh
+++ b/t/t5553-set-upstream.sh
@@ -108,27 +108,27 @@ test_expect_success 'setup commit on main and other pull' '
test_expect_success 'pull --set-upstream upstream main sets branch main but not other' '
clear_config main other &&
- git pull --set-upstream upstream main &&
+ git pull --no-rebase --set-upstream upstream main &&
check_config main upstream refs/heads/main &&
check_config_missing other
'
test_expect_success 'pull --set-upstream main:other2 does not set the branch other2' '
clear_config other2 &&
- git pull --set-upstream upstream main:other2 &&
+ git pull --no-rebase --set-upstream upstream main:other2 &&
check_config_missing other2
'
test_expect_success 'pull --set-upstream upstream other sets branch main' '
clear_config main other &&
- git pull --set-upstream upstream other &&
+ git pull --no-rebase --set-upstream upstream other &&
check_config main 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 &&
+ git pull --no-rebase --tags --set-upstream upstream three &&
check_config_missing three
'
@@ -144,16 +144,16 @@ test_expect_success 'pull --set-upstream http://nosuchdomain.example.com fails w
test_expect_success 'pull --set-upstream upstream HEAD sets branch HEAD' '
clear_config main other &&
- git pull --set-upstream upstream HEAD &&
+ git pull --no-rebase --set-upstream upstream HEAD &&
check_config main upstream HEAD &&
git checkout other &&
- git pull --set-upstream upstream HEAD &&
+ git pull --no-rebase --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 main three &&
- git pull --set-upstream upstream main three &&
+ git pull --no-rebase --set-upstream upstream main three &&
check_config_missing main &&
check_config_missing three
'
diff --git a/t/t5555-http-smart-common.sh b/t/t5555-http-smart-common.sh
new file mode 100755
index 0000000000..49faf5e283
--- /dev/null
+++ b/t/t5555-http-smart-common.sh
@@ -0,0 +1,161 @@
+#!/bin/sh
+
+test_description='test functionality common to smart fetch & push'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ test_commit --no-tag initial
+'
+
+test_expect_success 'git upload-pack --http-backend-info-refs and --advertise-refs are aliased' '
+ git upload-pack --http-backend-info-refs . >expected 2>err.expected &&
+ git upload-pack --advertise-refs . >actual 2>err.actual &&
+ test_cmp err.expected err.actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'git receive-pack --http-backend-info-refs and --advertise-refs are aliased' '
+ git receive-pack --http-backend-info-refs . >expected 2>err.expected &&
+ git receive-pack --advertise-refs . >actual 2>err.actual &&
+ test_cmp err.expected err.actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'git upload-pack --advertise-refs' '
+ cat >expect <<-EOF &&
+ $(git rev-parse HEAD) HEAD
+ $(git rev-parse HEAD) $(git symbolic-ref HEAD)
+ 0000
+ EOF
+
+ # We only care about GIT_PROTOCOL, not GIT_TEST_PROTOCOL_VERSION
+ sane_unset GIT_PROTOCOL &&
+ GIT_TEST_PROTOCOL_VERSION=2 \
+ git upload-pack --advertise-refs . >out 2>err &&
+
+ test-tool pkt-line unpack <out >actual &&
+ test_must_be_empty err &&
+ test_cmp actual expect &&
+
+ # The --advertise-refs alias works
+ git upload-pack --advertise-refs . >out 2>err &&
+
+ test-tool pkt-line unpack <out >actual &&
+ test_must_be_empty err &&
+ test_cmp actual expect
+'
+
+test_expect_success 'git upload-pack --advertise-refs: v0' '
+ # With no specified protocol
+ cat >expect <<-EOF &&
+ $(git rev-parse HEAD) HEAD
+ $(git rev-parse HEAD) $(git symbolic-ref HEAD)
+ 0000
+ EOF
+
+ git upload-pack --advertise-refs . >out 2>err &&
+ test-tool pkt-line unpack <out >actual &&
+ test_must_be_empty err &&
+ test_cmp actual expect &&
+
+ # With explicit v0
+ GIT_PROTOCOL=version=0 \
+ git upload-pack --advertise-refs . >out 2>err &&
+ test-tool pkt-line unpack <out >actual 2>err &&
+ test_must_be_empty err &&
+ test_cmp actual expect
+
+'
+
+test_expect_success 'git receive-pack --advertise-refs: v0' '
+ # With no specified protocol
+ cat >expect <<-EOF &&
+ $(git rev-parse HEAD) $(git symbolic-ref HEAD)
+ 0000
+ EOF
+
+ git receive-pack --advertise-refs . >out 2>err &&
+ test-tool pkt-line unpack <out >actual &&
+ test_must_be_empty err &&
+ test_cmp actual expect &&
+
+ # With explicit v0
+ GIT_PROTOCOL=version=0 \
+ git receive-pack --advertise-refs . >out 2>err &&
+ test-tool pkt-line unpack <out >actual 2>err &&
+ test_must_be_empty err &&
+ test_cmp actual expect
+
+'
+
+test_expect_success 'git upload-pack --advertise-refs: v1' '
+ # With no specified protocol
+ cat >expect <<-EOF &&
+ version 1
+ $(git rev-parse HEAD) HEAD
+ $(git rev-parse HEAD) $(git symbolic-ref HEAD)
+ 0000
+ EOF
+
+ GIT_PROTOCOL=version=1 \
+ git upload-pack --advertise-refs . >out &&
+
+ test-tool pkt-line unpack <out >actual 2>err &&
+ test_must_be_empty err &&
+ test_cmp actual expect
+'
+
+test_expect_success 'git receive-pack --advertise-refs: v1' '
+ # With no specified protocol
+ cat >expect <<-EOF &&
+ version 1
+ $(git rev-parse HEAD) $(git symbolic-ref HEAD)
+ 0000
+ EOF
+
+ GIT_PROTOCOL=version=1 \
+ git receive-pack --advertise-refs . >out &&
+
+ test-tool pkt-line unpack <out >actual 2>err &&
+ test_must_be_empty err &&
+ test_cmp actual expect
+'
+
+test_expect_success 'git upload-pack --advertise-refs: v2' '
+ cat >expect <<-EOF &&
+ version 2
+ agent=FAKE
+ ls-refs=unborn
+ fetch=shallow wait-for-done
+ server-option
+ object-format=$(test_oid algo)
+ object-info
+ 0000
+ EOF
+
+ GIT_PROTOCOL=version=2 \
+ GIT_USER_AGENT=FAKE \
+ git upload-pack --advertise-refs . >out 2>err &&
+
+ test-tool pkt-line unpack <out >actual &&
+ test_must_be_empty err &&
+ test_cmp actual expect
+'
+
+test_expect_success 'git receive-pack --advertise-refs: v2' '
+ # There is no v2 yet for receive-pack, implicit v0
+ cat >expect <<-EOF &&
+ $(git rev-parse HEAD) $(git symbolic-ref HEAD)
+ 0000
+ EOF
+
+ GIT_PROTOCOL=version=2 \
+ git receive-pack --advertise-refs . >out 2>err &&
+
+ test-tool pkt-line unpack <out >actual &&
+ test_must_be_empty err &&
+ test_cmp actual expect
+'
+
+test_done
diff --git a/t/t5562/invoke-with-content-length.pl b/t/t5562/invoke-with-content-length.pl
index 0943474af2..718dd9b49d 100644
--- a/t/t5562/invoke-with-content-length.pl
+++ b/t/t5562/invoke-with-content-length.pl
@@ -13,11 +13,6 @@ my $body_data;
defined read($body_fh, $body_data, $body_size) or die "Cannot read $body_filename: $!";
close($body_fh);
-my $exited = 0;
-$SIG{"CHLD"} = sub {
- $exited = 1;
-};
-
# write data
my $pid = open(my $out, "|-", @command);
{
@@ -29,8 +24,13 @@ my $pid = open(my $out, "|-", @command);
}
print $out $body_data or die "Cannot write data: $!";
-sleep 60; # is interrupted by SIGCHLD
-if (!$exited) {
- close($out);
+$SIG{ALRM} = sub {
+ kill 'KILL', $pid;
die "Command did not exit after reading whole body";
+};
+alarm 60;
+
+my $ret = waitpid($pid, 0);
+if ($ret != $pid) {
+ die "confusing return from waitpid: $ret";
}
diff --git a/t/t5582-fetch-negative-refspec.sh b/t/t5582-fetch-negative-refspec.sh
index e5d2e79ad3..7a80e47c2b 100755
--- a/t/t5582-fetch-negative-refspec.sh
+++ b/t/t5582-fetch-negative-refspec.sh
@@ -105,7 +105,6 @@ test_expect_success "fetch with negative pattern refspec does not expand prefix"
'
test_expect_success "fetch with negative refspec avoids duplicate conflict" '
- cd "$D" &&
(
cd one &&
git branch dups/a &&
diff --git a/t/t5600-clone-fail-cleanup.sh b/t/t5600-clone-fail-cleanup.sh
index 5bf10261d3..34b3df4027 100755
--- a/t/t5600-clone-fail-cleanup.sh
+++ b/t/t5600-clone-fail-cleanup.sh
@@ -35,7 +35,9 @@ test_expect_success 'create a repo to clone' '
'
test_expect_success 'create objects in repo for later corruption' '
- test_commit -C foo file
+ test_commit -C foo file &&
+ git -C foo checkout --detach &&
+ test_commit -C foo detached
'
# source repository given to git clone should be relative to the
diff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh
index e845d621f6..24340e6d56 100755
--- a/t/t5604-clone-reference.sh
+++ b/t/t5604-clone-reference.sh
@@ -87,7 +87,7 @@ test_expect_success 'updating origin' '
'
test_expect_success 'pulling changes from origin' '
- git -C C pull origin
+ git -C C pull --no-rebase origin
'
# the 2 local objects are commit and tree from the merge
@@ -96,7 +96,7 @@ test_expect_success 'that alternate to origin gets used' '
'
test_expect_success 'pulling changes from origin' '
- git -C D pull origin
+ git -C D pull --no-rebase origin
'
# the 5 local objects are expected; file3 blob, commit in A to add it
diff --git a/t/t5606-clone-options.sh b/t/t5606-clone-options.sh
index 3a595c0f82..d822153e4d 100755
--- a/t/t5606-clone-options.sh
+++ b/t/t5606-clone-options.sh
@@ -16,6 +16,18 @@ test_expect_success 'setup' '
'
+test_expect_success 'submodule.stickyRecursiveClone flag manipulates submodule.recurse value' '
+
+ test_config_global submodule.stickyRecursiveClone true &&
+ git clone --recurse-submodules parent clone_recurse_true &&
+ test_cmp_config -C clone_recurse_true true submodule.recurse &&
+
+ test_config_global submodule.stickyRecursiveClone false &&
+ git clone --recurse-submodules parent clone_recurse_false &&
+ test_expect_code 1 git -C clone_recurse_false config --get submodule.recurse
+
+'
+
test_expect_success 'clone -o' '
git clone -o foo parent clone-o &&
diff --git a/t/t5607-clone-bundle.sh b/t/t5607-clone-bundle.sh
index ed0d911e95..51705aa86a 100755
--- a/t/t5607-clone-bundle.sh
+++ b/t/t5607-clone-bundle.sh
@@ -91,7 +91,8 @@ test_expect_success 'ridiculously long subject in boundary' '
git fetch long-subject-bundle.bdl &&
- if ! test_have_prereq SHA1
+ algo=$(test_oid algo) &&
+ if test "$algo" != sha1
then
echo "@object-format=sha256"
fi >expect &&
@@ -100,7 +101,7 @@ test_expect_success 'ridiculously long subject in boundary' '
$(git rev-parse HEAD) HEAD
EOF
- if test_have_prereq SHA1
+ if test "$algo" = sha1
then
head -n 3 long-subject-bundle.bdl
else
diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh
index 930721f053..aa1827d841 100755
--- a/t/t5701-git-serve.sh
+++ b/t/t5701-git-serve.sh
@@ -72,6 +72,37 @@ test_expect_success 'request invalid command' '
test_i18ngrep "invalid command" err
'
+test_expect_success 'request capability as command' '
+ test-tool pkt-line pack >in <<-EOF &&
+ command=agent
+ object-format=$(test_oid algo)
+ 0000
+ EOF
+ test_must_fail test-tool serve-v2 --stateless-rpc 2>err <in &&
+ grep invalid.command.*agent err
+'
+
+test_expect_success 'request command as capability' '
+ test-tool pkt-line pack >in <<-EOF &&
+ command=ls-refs
+ object-format=$(test_oid algo)
+ fetch
+ 0000
+ EOF
+ test_must_fail test-tool serve-v2 --stateless-rpc 2>err <in &&
+ grep unknown.capability err
+'
+
+test_expect_success 'requested command is command=value' '
+ test-tool pkt-line pack >in <<-EOF &&
+ command=ls-refs=whatever
+ object-format=$(test_oid algo)
+ 0000
+ EOF
+ test_must_fail test-tool serve-v2 --stateless-rpc 2>err <in &&
+ grep invalid.command.*ls-refs=whatever err
+'
+
test_expect_success 'wrong object-format' '
test-tool pkt-line pack >in <<-EOF &&
command=fetch
@@ -116,6 +147,19 @@ test_expect_success 'basics of ls-refs' '
test_cmp expect actual
'
+test_expect_success 'ls-refs complains about unknown options' '
+ test-tool pkt-line pack >in <<-EOF &&
+ command=ls-refs
+ object-format=$(test_oid algo)
+ 0001
+ no-such-arg
+ 0000
+ EOF
+
+ test_must_fail test-tool serve-v2 --stateless-rpc 2>err <in &&
+ grep unexpected.line.*no-such-arg err
+'
+
test_expect_success 'basic ref-prefixes' '
test-tool pkt-line pack >in <<-EOF &&
command=ls-refs
@@ -158,6 +202,37 @@ test_expect_success 'refs/heads prefix' '
test_cmp expect actual
'
+test_expect_success 'ignore very large set of prefixes' '
+ # generate a large number of ref-prefixes that we expect
+ # to match nothing; the value here exceeds TOO_MANY_PREFIXES
+ # from ls-refs.c.
+ {
+ echo command=ls-refs &&
+ echo object-format=$(test_oid algo) &&
+ echo 0001 &&
+ perl -le "print \"ref-prefix refs/heads/\$_\" for (1..65536)" &&
+ echo 0000
+ } |
+ test-tool pkt-line pack >in &&
+
+ # and then confirm that we see unmatched prefixes anyway (i.e.,
+ # that the prefix was not applied).
+ cat >expect <<-EOF &&
+ $(git rev-parse HEAD) HEAD
+ $(git rev-parse refs/heads/dev) refs/heads/dev
+ $(git rev-parse refs/heads/main) refs/heads/main
+ $(git rev-parse refs/heads/release) refs/heads/release
+ $(git rev-parse refs/tags/annotated-tag) refs/tags/annotated-tag
+ $(git rev-parse refs/tags/one) refs/tags/one
+ $(git rev-parse refs/tags/two) refs/tags/two
+ 0000
+ EOF
+
+ test-tool serve-v2 --stateless-rpc <in >out &&
+ test-tool pkt-line unpack <out >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'peel parameter' '
test-tool pkt-line pack >in <<-EOF &&
command=ls-refs
diff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh
index 78de1ff2ad..d527cf6c49 100755
--- a/t/t5702-protocol-v2.sh
+++ b/t/t5702-protocol-v2.sh
@@ -27,9 +27,9 @@ test_expect_success 'list refs with git:// using protocol v2' '
ls-remote --symref "$GIT_DAEMON_URL/parent" >actual &&
# Client requested to use protocol v2
- grep "git> .*\\\0\\\0version=2\\\0$" log &&
+ grep "ls-remote> .*\\\0\\\0version=2\\\0$" log &&
# Server responded using protocol v2
- grep "git< version 2" log &&
+ grep "ls-remote< version 2" log &&
git ls-remote --symref "$GIT_DAEMON_URL/parent" >expect &&
test_cmp expect actual
@@ -151,7 +151,7 @@ test_expect_success 'list refs with file:// using protocol v2' '
ls-remote --symref "file://$(pwd)/file_parent" >actual &&
# Server responded using protocol v2
- grep "git< version 2" log &&
+ grep "ls-remote< version 2" log &&
git ls-remote --symref "file://$(pwd)/file_parent" >expect &&
test_cmp expect actual
@@ -237,6 +237,19 @@ test_expect_success '...but not if explicitly forbidden by config' '
! grep "refs/heads/mydefaultbranch" file_empty_child/.git/HEAD
'
+test_expect_success 'bare clone propagates empty default branch' '
+ test_when_finished "rm -rf file_empty_parent file_empty_child.git" &&
+
+ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \
+ git -c init.defaultBranch=mydefaultbranch init file_empty_parent &&
+
+ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \
+ git -c init.defaultBranch=main -c protocol.version=2 \
+ clone --bare \
+ "file://$(pwd)/file_empty_parent" file_empty_child.git &&
+ grep "refs/heads/mydefaultbranch" file_empty_child.git/HEAD
+'
+
test_expect_success 'fetch with file:// using protocol v2' '
test_when_finished "rm -f log" &&
diff --git a/t/t5703-upload-pack-ref-in-want.sh b/t/t5703-upload-pack-ref-in-want.sh
index e9e471621d..220098523a 100755
--- a/t/t5703-upload-pack-ref-in-want.sh
+++ b/t/t5703-upload-pack-ref-in-want.sh
@@ -40,6 +40,30 @@ write_command () {
fi
}
+# Write a complete fetch command to stdout, suitable for use with `test-tool
+# pkt-line`. "want-ref", "want", and "have" lines are read from stdin.
+#
+# Examples:
+#
+# write_fetch_command <<-EOF
+# want-ref refs/heads/main
+# have $(git rev-parse a)
+# EOF
+#
+# write_fetch_command <<-EOF
+# want $(git rev-parse b)
+# have $(git rev-parse a)
+# EOF
+#
+write_fetch_command () {
+ write_command fetch &&
+ echo "0001" &&
+ echo "no-progress" &&
+ cat &&
+ echo "done" &&
+ echo "0000"
+}
+
# c(o/foo) d(o/bar)
# \ /
# b e(baz) f(main)
@@ -77,15 +101,11 @@ test_expect_success 'config controls ref-in-want advertisement' '
'
test_expect_success 'invalid want-ref line' '
- test-tool pkt-line pack >in <<-EOF &&
- $(write_command fetch)
- 0001
- no-progress
+ write_fetch_command >pkt <<-EOF &&
want-ref refs/heads/non-existent
- done
- 0000
EOF
+ test-tool pkt-line pack <pkt >in &&
test_must_fail test-tool serve-v2 --stateless-rpc 2>out <in &&
grep "unknown ref" out
'
@@ -97,16 +117,11 @@ test_expect_success 'basic want-ref' '
EOF
git rev-parse f >expected_commits &&
- oid=$(git rev-parse a) &&
- test-tool pkt-line pack >in <<-EOF &&
- $(write_command fetch)
- 0001
- no-progress
+ write_fetch_command >pkt <<-EOF &&
want-ref refs/heads/main
- have $oid
- done
- 0000
+ have $(git rev-parse a)
EOF
+ test-tool pkt-line pack <pkt >in &&
test-tool serve-v2 --stateless-rpc >out <in &&
check_output
@@ -121,17 +136,12 @@ test_expect_success 'multiple want-ref lines' '
EOF
git rev-parse c d >expected_commits &&
- oid=$(git rev-parse b) &&
- test-tool pkt-line pack >in <<-EOF &&
- $(write_command fetch)
- 0001
- no-progress
+ write_fetch_command >pkt <<-EOF &&
want-ref refs/heads/o/foo
want-ref refs/heads/o/bar
- have $oid
- done
- 0000
+ have $(git rev-parse b)
EOF
+ test-tool pkt-line pack <pkt >in &&
test-tool serve-v2 --stateless-rpc >out <in &&
check_output
@@ -144,16 +154,12 @@ test_expect_success 'mix want and want-ref' '
EOF
git rev-parse e f >expected_commits &&
- test-tool pkt-line pack >in <<-EOF &&
- $(write_command fetch)
- 0001
- no-progress
+ write_fetch_command >pkt <<-EOF &&
want-ref refs/heads/main
want $(git rev-parse e)
have $(git rev-parse a)
- done
- 0000
EOF
+ test-tool pkt-line pack <pkt >in &&
test-tool serve-v2 --stateless-rpc >out <in &&
check_output
@@ -166,16 +172,11 @@ test_expect_success 'want-ref with ref we already have commit for' '
EOF
>expected_commits &&
- oid=$(git rev-parse c) &&
- test-tool pkt-line pack >in <<-EOF &&
- $(write_command fetch)
- 0001
- no-progress
+ write_fetch_command >pkt <<-EOF &&
want-ref refs/heads/o/foo
- have $oid
- done
- 0000
+ have $(git rev-parse c)
EOF
+ test-tool pkt-line pack <pkt >in &&
test-tool serve-v2 --stateless-rpc >out <in &&
check_output
@@ -298,6 +299,141 @@ test_expect_success 'fetching with wildcard that matches multiple refs' '
grep "want-ref refs/heads/o/bar" log
'
+REPO="$(pwd)/repo-ns"
+
+test_expect_success 'setup namespaced repo' '
+ (
+ git init -b main "$REPO" &&
+ cd "$REPO" &&
+ test_commit a &&
+ test_commit b &&
+ git checkout a &&
+ test_commit c &&
+ git checkout a &&
+ test_commit d &&
+ git update-ref refs/heads/ns-no b &&
+ git update-ref refs/namespaces/ns/refs/heads/ns-yes c &&
+ git update-ref refs/namespaces/ns/refs/heads/hidden d
+ ) &&
+ git -C "$REPO" config uploadpack.allowRefInWant true
+'
+
+test_expect_success 'with namespace: want-ref is considered relative to namespace' '
+ wanted_ref=refs/heads/ns-yes &&
+
+ oid=$(git -C "$REPO" rev-parse "refs/namespaces/ns/$wanted_ref") &&
+ cat >expected_refs <<-EOF &&
+ $oid $wanted_ref
+ EOF
+ cat >expected_commits <<-EOF &&
+ $oid
+ $(git -C "$REPO" rev-parse a)
+ EOF
+
+ write_fetch_command >pkt <<-EOF &&
+ want-ref $wanted_ref
+ EOF
+ test-tool pkt-line pack <pkt >in &&
+
+ GIT_NAMESPACE=ns test-tool -C "$REPO" serve-v2 --stateless-rpc >out <in &&
+ check_output
+'
+
+test_expect_success 'with namespace: want-ref outside namespace is unknown' '
+ wanted_ref=refs/heads/ns-no &&
+
+ write_fetch_command >pkt <<-EOF &&
+ want-ref $wanted_ref
+ EOF
+ test-tool pkt-line pack <pkt >in &&
+
+ test_must_fail env GIT_NAMESPACE=ns \
+ test-tool -C "$REPO" serve-v2 --stateless-rpc >out <in &&
+ grep "unknown ref" out
+'
+
+# Cross-check refs/heads/ns-no indeed exists
+test_expect_success 'without namespace: want-ref outside namespace succeeds' '
+ wanted_ref=refs/heads/ns-no &&
+
+ oid=$(git -C "$REPO" rev-parse $wanted_ref) &&
+ cat >expected_refs <<-EOF &&
+ $oid $wanted_ref
+ EOF
+ cat >expected_commits <<-EOF &&
+ $oid
+ $(git -C "$REPO" rev-parse a)
+ EOF
+
+ write_fetch_command >pkt <<-EOF &&
+ want-ref $wanted_ref
+ EOF
+ test-tool pkt-line pack <pkt >in &&
+
+ test-tool -C "$REPO" serve-v2 --stateless-rpc >out <in &&
+ check_output
+'
+
+test_expect_success 'with namespace: hideRefs is matched, relative to namespace' '
+ wanted_ref=refs/heads/hidden &&
+ git -C "$REPO" config transfer.hideRefs $wanted_ref &&
+
+ write_fetch_command >pkt <<-EOF &&
+ want-ref $wanted_ref
+ EOF
+ test-tool pkt-line pack <pkt >in &&
+
+ test_must_fail env GIT_NAMESPACE=ns \
+ test-tool -C "$REPO" serve-v2 --stateless-rpc >out <in &&
+ grep "unknown ref" out
+'
+
+# Cross-check refs/heads/hidden indeed exists
+test_expect_success 'with namespace: want-ref succeeds if hideRefs is removed' '
+ wanted_ref=refs/heads/hidden &&
+ git -C "$REPO" config --unset transfer.hideRefs $wanted_ref &&
+
+ oid=$(git -C "$REPO" rev-parse "refs/namespaces/ns/$wanted_ref") &&
+ cat >expected_refs <<-EOF &&
+ $oid $wanted_ref
+ EOF
+ cat >expected_commits <<-EOF &&
+ $oid
+ $(git -C "$REPO" rev-parse a)
+ EOF
+
+ write_fetch_command >pkt <<-EOF &&
+ want-ref $wanted_ref
+ EOF
+ test-tool pkt-line pack <pkt >in &&
+
+ GIT_NAMESPACE=ns test-tool -C "$REPO" serve-v2 --stateless-rpc >out <in &&
+ check_output
+'
+
+test_expect_success 'without namespace: relative hideRefs does not match' '
+ wanted_ref=refs/namespaces/ns/refs/heads/hidden &&
+ git -C "$REPO" config transfer.hideRefs refs/heads/hidden &&
+
+ oid=$(git -C "$REPO" rev-parse $wanted_ref) &&
+ cat >expected_refs <<-EOF &&
+ $oid $wanted_ref
+ EOF
+ cat >expected_commits <<-EOF &&
+ $oid
+ $(git -C "$REPO" rev-parse a)
+ EOF
+
+ write_fetch_command >pkt <<-EOF &&
+ want-ref $wanted_ref
+ EOF
+ test-tool pkt-line pack <pkt >in &&
+
+ test-tool -C "$REPO" serve-v2 --stateless-rpc >out <in &&
+ check_output
+'
+
+
. "$TEST_DIRECTORY"/lib-httpd.sh
start_httpd
diff --git a/t/t5704-protocol-violations.sh b/t/t5704-protocol-violations.sh
index 5c941949b9..bc393d7c31 100755
--- a/t/t5704-protocol-violations.sh
+++ b/t/t5704-protocol-violations.sh
@@ -32,4 +32,19 @@ test_expect_success 'extra delim packet in v2 fetch args' '
test_i18ngrep "expected flush after fetch arguments" err
'
+test_expect_success 'bogus symref in v0 capabilities' '
+ test_commit foo &&
+ oid=$(git rev-parse HEAD) &&
+ dst=refs/heads/foo &&
+ {
+ printf "%s HEAD\0symref object-format=%s symref=HEAD:%s\n" \
+ "$oid" "$GIT_DEFAULT_HASH" "$dst" |
+ test-tool pkt-line pack-raw-stdin &&
+ printf "0000"
+ } >input &&
+ git ls-remote --symref --upload-pack="cat input; read junk;:" . >actual &&
+ printf "ref: %s\tHEAD\n%s\tHEAD\n" "$dst" "$oid" >expect &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t5705-session-id-in-capabilities.sh b/t/t5705-session-id-in-capabilities.sh
index f1d189d5bc..eb8c79aafd 100755
--- a/t/t5705-session-id-in-capabilities.sh
+++ b/t/t5705-session-id-in-capabilities.sh
@@ -73,6 +73,17 @@ do
grep \"key\":\"server-sid\" tr2-client-events &&
grep \"key\":\"client-sid\" tr2-server-events
'
+
+ test_expect_success "client & server log negotiated version (v${PROTO})" '
+ test_when_finished "rm -rf local tr2-client-events tr2-server-events" &&
+ cp -r "$LOCAL_PRISTINE" local &&
+ GIT_TRACE2_EVENT="$(pwd)/tr2-client-events" \
+ git -c protocol.version=$PROTO -C local fetch \
+ --upload-pack "GIT_TRACE2_EVENT=\"$(pwd)/tr2-server-events\" git-upload-pack" \
+ origin &&
+ grep \"key\":\"negotiated-version\",\"value\":\"$PROTO\" tr2-client-events &&
+ grep \"key\":\"negotiated-version\",\"value\":\"$PROTO\" tr2-server-events
+ '
done
test_done
diff --git a/t/t6000-rev-list-misc.sh b/t/t6000-rev-list-misc.sh
index 12def7bcbf..ef849e5bc8 100755
--- a/t/t6000-rev-list-misc.sh
+++ b/t/t6000-rev-list-misc.sh
@@ -169,4 +169,35 @@ test_expect_success 'rev-list --count --objects' '
test_line_count = $count actual
'
+test_expect_success 'rev-list --unsorted-input results in different sorting' '
+ git rev-list --unsorted-input HEAD HEAD~ >first &&
+ git rev-list --unsorted-input HEAD~ HEAD >second &&
+ ! test_cmp first second &&
+ sort first >first.sorted &&
+ sort second >second.sorted &&
+ test_cmp first.sorted second.sorted
+'
+
+test_expect_success 'rev-list --unsorted-input incompatible with --no-walk' '
+ cat >expect <<-EOF &&
+ fatal: --no-walk is incompatible with --unsorted-input
+ EOF
+ test_must_fail git rev-list --unsorted-input --no-walk HEAD 2>error &&
+ test_cmp expect error &&
+ test_must_fail git rev-list --unsorted-input --no-walk=sorted HEAD 2>error &&
+ test_cmp expect error &&
+ test_must_fail git rev-list --unsorted-input --no-walk=unsorted HEAD 2>error &&
+ test_cmp expect error &&
+
+ cat >expect <<-EOF &&
+ fatal: --unsorted-input is incompatible with --no-walk
+ EOF
+ test_must_fail git rev-list --no-walk --unsorted-input HEAD 2>error &&
+ test_cmp expect error &&
+ test_must_fail git rev-list --no-walk=sorted --unsorted-input HEAD 2>error &&
+ test_cmp expect error &&
+ test_must_fail git rev-list --no-walk=unsorted --unsorted-input HEAD 2>error &&
+ test_cmp expect error
+'
+
test_done
diff --git a/t/t6001-rev-list-graft.sh b/t/t6001-rev-list-graft.sh
index 90d93f77fa..7294147334 100755
--- a/t/t6001-rev-list-graft.sh
+++ b/t/t6001-rev-list-graft.sh
@@ -23,7 +23,8 @@ test_expect_success setup '
git commit -a -m "Third in one history." &&
A2=$(git rev-parse --verify HEAD) &&
- rm -f .git/refs/heads/main .git/index &&
+ git update-ref -d refs/heads/main &&
+ rm -f .git/index &&
echo >fileA fileA again &&
echo >subdir/fileB fileB again &&
diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
index a1baf4e451..1be85d064e 100755
--- a/t/t6030-bisect-porcelain.sh
+++ b/t/t6030-bisect-porcelain.sh
@@ -962,4 +962,22 @@ test_expect_success 'bisect handles annotated tags' '
grep "$bad is the first bad commit" output
'
+test_expect_success 'bisect run fails with exit code equals or greater than 128' '
+ write_script test_script.sh <<-\EOF &&
+ exit 128
+ EOF
+ test_must_fail git bisect run ./test_script.sh &&
+ write_script test_script.sh <<-\EOF &&
+ exit 255
+ EOF
+ test_must_fail git bisect run ./test_script.sh
+'
+
+test_expect_success 'bisect visualize with a filename with dash and space' '
+ echo "My test line" >>"./-hello 2" &&
+ git add -- "./-hello 2" &&
+ git commit --quiet -m "Add test line" -- "./-hello 2" &&
+ git bisect visualize -p -- "-hello 2"
+'
+
test_done
diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh
index e33d512ec1..2500acc2ef 100755
--- a/t/t6050-replace.sh
+++ b/t/t6050-replace.sh
@@ -132,7 +132,7 @@ tagger T A Gger <> 0 +0000
EOF
test_expect_success 'tag replaced commit' '
- git mktag <tag.sig >.git/refs/tags/mytag
+ git update-ref refs/tags/mytag $(git mktag <tag.sig)
'
test_expect_success '"git fsck" works' '
diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh
index 1a501ee09e..bae2419150 100755
--- a/t/t6120-describe.sh
+++ b/t/t6120-describe.sh
@@ -107,7 +107,8 @@ test_expect_success 'describe --contains defaults to HEAD without commit-ish' '
check_describe tags/A --all A^0
test_expect_success 'renaming tag A to Q locally produces a warning' "
- mv .git/refs/tags/A .git/refs/tags/Q &&
+ git update-ref refs/tags/Q $(git rev-parse refs/tags/A) &&
+ git update-ref -d refs/tags/A &&
git describe HEAD 2>err >out &&
cat >expected <<-\EOF &&
warning: tag 'Q' is externally known as 'A'
@@ -135,7 +136,8 @@ test_expect_success 'abbrev=0 will not break misplaced tag (2)' '
'
test_expect_success 'rename tag Q back to A' '
- mv .git/refs/tags/Q .git/refs/tags/A
+ git update-ref refs/tags/A $(git rev-parse refs/tags/Q) &&
+ git update-ref -d refs/tags/Q
'
test_expect_success 'pack tag refs' 'git pack-refs'
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index 9e0214076b..80679d5e12 100755
--- a/t/t6300-for-each-ref.sh
+++ b/t/t6300-for-each-ref.sh
@@ -59,18 +59,25 @@ test_atom() {
# 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
+ # for commit leg, $3 is changed there
+ expect=$(printf '%s' "$3" | wc -c)
test_expect_${4:-success} $PREREQ "basic atom: $1 contents:size" '
+ type=$(git cat-file -t "$ref") &&
+ case $type in
+ tag)
+ # We cannot use $3 as it expects sanitize_pgp to run
+ git cat-file tag $ref >out &&
+ expect=$(tail -n +6 out | wc -c) &&
+ rm -f out ;;
+ tree | blob)
+ expect="" ;;
+ commit)
+ : "use the calculated expect" ;;
+ *)
+ BUG "unknown object type" ;;
+ esac &&
+ # Leave $expect unquoted to lose possible leading whitespaces
+ echo $expect >expected &&
git for-each-ref --format="%(contents:size)" "$ref" >actual &&
test_cmp expected actual
'
@@ -130,6 +137,8 @@ test_atom head parent:short=10 ''
test_atom head numparent 0
test_atom head object ''
test_atom head type ''
+test_atom head raw "$(git cat-file commit refs/heads/main)
+"
test_atom head '*objectname' ''
test_atom head '*objecttype' ''
test_atom head author 'A U Thor <author@example.com> 1151968724 +0200'
@@ -221,6 +230,15 @@ test_atom tag contents 'Tagging at 1151968727
'
test_atom tag HEAD ' '
+test_expect_success 'basic atom: refs/tags/testtag *raw' '
+ git cat-file commit refs/tags/testtag^{} >expected &&
+ git for-each-ref --format="%(*raw)" refs/tags/testtag >actual &&
+ sanitize_pgp <expected >expected.clean &&
+ echo >>expected.clean &&
+ sanitize_pgp <actual >actual.clean &&
+ test_cmp expected.clean actual.clean
+'
+
test_expect_success 'Check invalid atoms names are errors' '
test_must_fail git for-each-ref --format="%(INVALID)" refs/heads
'
@@ -686,6 +704,15 @@ test_atom refs/tags/signed-empty contents:body ''
test_atom refs/tags/signed-empty contents:signature "$sig"
test_atom refs/tags/signed-empty contents "$sig"
+test_expect_success GPG 'basic atom: refs/tags/signed-empty raw' '
+ git cat-file tag refs/tags/signed-empty >expected &&
+ git for-each-ref --format="%(raw)" refs/tags/signed-empty >actual &&
+ sanitize_pgp <expected >expected.clean &&
+ echo >>expected.clean &&
+ sanitize_pgp <actual >actual.clean &&
+ test_cmp expected.clean actual.clean
+'
+
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'
@@ -695,6 +722,15 @@ test_atom refs/tags/signed-short contents:signature "$sig"
test_atom refs/tags/signed-short contents "subject line
$sig"
+test_expect_success GPG 'basic atom: refs/tags/signed-short raw' '
+ git cat-file tag refs/tags/signed-short >expected &&
+ git for-each-ref --format="%(raw)" refs/tags/signed-short >actual &&
+ sanitize_pgp <expected >expected.clean &&
+ echo >>expected.clean &&
+ sanitize_pgp <actual >actual.clean &&
+ test_cmp expected.clean actual.clean
+'
+
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'
@@ -708,6 +744,15 @@ test_atom refs/tags/signed-long contents "subject line
body contents
$sig"
+test_expect_success GPG 'basic atom: refs/tags/signed-long raw' '
+ git cat-file tag refs/tags/signed-long >expected &&
+ git for-each-ref --format="%(raw)" refs/tags/signed-long >actual &&
+ sanitize_pgp <expected >expected.clean &&
+ echo >>expected.clean &&
+ sanitize_pgp <actual >actual.clean &&
+ test_cmp expected.clean actual.clean
+'
+
test_expect_success 'set up refs pointing to tree and blob' '
git update-ref refs/mytrees/first refs/heads/main^{tree} &&
git update-ref refs/myblobs/first refs/heads/main:one
@@ -720,6 +765,16 @@ test_atom refs/mytrees/first contents:body ""
test_atom refs/mytrees/first contents:signature ""
test_atom refs/mytrees/first contents ""
+test_expect_success 'basic atom: refs/mytrees/first raw' '
+ git cat-file tree refs/mytrees/first >expected &&
+ echo >>expected &&
+ git for-each-ref --format="%(raw)" refs/mytrees/first >actual &&
+ test_cmp expected actual &&
+ git cat-file -s refs/mytrees/first >expected &&
+ git for-each-ref --format="%(raw:size)" refs/mytrees/first >actual &&
+ test_cmp expected actual
+'
+
test_atom refs/myblobs/first subject ""
test_atom refs/myblobs/first contents:subject ""
test_atom refs/myblobs/first body ""
@@ -727,6 +782,189 @@ test_atom refs/myblobs/first contents:body ""
test_atom refs/myblobs/first contents:signature ""
test_atom refs/myblobs/first contents ""
+test_expect_success 'basic atom: refs/myblobs/first raw' '
+ git cat-file blob refs/myblobs/first >expected &&
+ echo >>expected &&
+ git for-each-ref --format="%(raw)" refs/myblobs/first >actual &&
+ test_cmp expected actual &&
+ git cat-file -s refs/myblobs/first >expected &&
+ git for-each-ref --format="%(raw:size)" refs/myblobs/first >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'set up refs pointing to binary blob' '
+ printf "a\0b\0c" >blob1 &&
+ printf "a\0c\0b" >blob2 &&
+ printf "\0a\0b\0c" >blob3 &&
+ printf "abc" >blob4 &&
+ printf "\0 \0 \0 " >blob5 &&
+ printf "\0 \0a\0 " >blob6 &&
+ printf " " >blob7 &&
+ >blob8 &&
+ obj=$(git hash-object -w blob1) &&
+ git update-ref refs/myblobs/blob1 "$obj" &&
+ obj=$(git hash-object -w blob2) &&
+ git update-ref refs/myblobs/blob2 "$obj" &&
+ obj=$(git hash-object -w blob3) &&
+ git update-ref refs/myblobs/blob3 "$obj" &&
+ obj=$(git hash-object -w blob4) &&
+ git update-ref refs/myblobs/blob4 "$obj" &&
+ obj=$(git hash-object -w blob5) &&
+ git update-ref refs/myblobs/blob5 "$obj" &&
+ obj=$(git hash-object -w blob6) &&
+ git update-ref refs/myblobs/blob6 "$obj" &&
+ obj=$(git hash-object -w blob7) &&
+ git update-ref refs/myblobs/blob7 "$obj" &&
+ obj=$(git hash-object -w blob8) &&
+ git update-ref refs/myblobs/blob8 "$obj"
+'
+
+test_expect_success 'Verify sorts with raw' '
+ cat >expected <<-EOF &&
+ refs/myblobs/blob8
+ refs/myblobs/blob5
+ refs/myblobs/blob6
+ refs/myblobs/blob3
+ refs/myblobs/blob7
+ refs/mytrees/first
+ refs/myblobs/first
+ refs/myblobs/blob1
+ refs/myblobs/blob2
+ refs/myblobs/blob4
+ refs/heads/main
+ EOF
+ git for-each-ref --format="%(refname)" --sort=raw \
+ refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'Verify sorts with raw:size' '
+ cat >expected <<-EOF &&
+ refs/myblobs/blob8
+ refs/myblobs/first
+ refs/myblobs/blob7
+ refs/heads/main
+ refs/myblobs/blob4
+ refs/myblobs/blob1
+ refs/myblobs/blob2
+ refs/myblobs/blob3
+ refs/myblobs/blob5
+ refs/myblobs/blob6
+ refs/mytrees/first
+ EOF
+ git for-each-ref --format="%(refname)" --sort=raw:size \
+ refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'validate raw atom with %(if:equals)' '
+ cat >expected <<-EOF &&
+ not equals
+ not equals
+ not equals
+ not equals
+ not equals
+ not equals
+ refs/myblobs/blob4
+ not equals
+ not equals
+ not equals
+ not equals
+ not equals
+ EOF
+ git for-each-ref --format="%(if:equals=abc)%(raw)%(then)%(refname)%(else)not equals%(end)" \
+ refs/myblobs/ refs/heads/ >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'validate raw atom with %(if:notequals)' '
+ cat >expected <<-EOF &&
+ refs/heads/ambiguous
+ refs/heads/main
+ refs/heads/newtag
+ refs/myblobs/blob1
+ refs/myblobs/blob2
+ refs/myblobs/blob3
+ equals
+ refs/myblobs/blob5
+ refs/myblobs/blob6
+ refs/myblobs/blob7
+ refs/myblobs/blob8
+ refs/myblobs/first
+ EOF
+ git for-each-ref --format="%(if:notequals=abc)%(raw)%(then)%(refname)%(else)equals%(end)" \
+ refs/myblobs/ refs/heads/ >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'empty raw refs with %(if)' '
+ cat >expected <<-EOF &&
+ refs/myblobs/blob1 not empty
+ refs/myblobs/blob2 not empty
+ refs/myblobs/blob3 not empty
+ refs/myblobs/blob4 not empty
+ refs/myblobs/blob5 not empty
+ refs/myblobs/blob6 not empty
+ refs/myblobs/blob7 empty
+ refs/myblobs/blob8 empty
+ refs/myblobs/first not empty
+ EOF
+ git for-each-ref --format="%(refname) %(if)%(raw)%(then)not empty%(else)empty%(end)" \
+ refs/myblobs/ >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '%(raw) with --python must fail' '
+ test_must_fail git for-each-ref --format="%(raw)" --python
+'
+
+test_expect_success '%(raw) with --tcl must fail' '
+ test_must_fail git for-each-ref --format="%(raw)" --tcl
+'
+
+test_expect_success '%(raw) with --perl' '
+ git for-each-ref --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob1 --perl | perl >actual &&
+ cmp blob1 actual &&
+ git for-each-ref --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob3 --perl | perl >actual &&
+ cmp blob3 actual &&
+ git for-each-ref --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob8 --perl | perl >actual &&
+ cmp blob8 actual &&
+ git for-each-ref --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/first --perl | perl >actual &&
+ cmp one actual &&
+ git cat-file tree refs/mytrees/first > expected &&
+ git for-each-ref --format="\$name= %(raw);
+print \"\$name\"" refs/mytrees/first --perl | perl >actual &&
+ cmp expected actual
+'
+
+test_expect_success '%(raw) with --shell must fail' '
+ test_must_fail git for-each-ref --format="%(raw)" --shell
+'
+
+test_expect_success '%(raw) with --shell and --sort=raw must fail' '
+ test_must_fail git for-each-ref --format="%(raw)" --sort=raw --shell
+'
+
+test_expect_success '%(raw:size) with --shell' '
+ git for-each-ref --format="%(raw:size)" | while read line
+ do
+ echo "'\''$line'\''" >>expect
+ done &&
+ git for-each-ref --format="%(raw:size)" --shell >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'for-each-ref --format compare with cat-file --batch' '
+ git rev-parse refs/mytrees/first | git cat-file --batch >expected &&
+ git for-each-ref --format="%(objectname) %(objecttype) %(objectsize)
+%(raw)" refs/mytrees/first >actual &&
+ test_cmp expected actual
+'
+
test_expect_success 'set up multiple-sort tags' '
for when in 100000 200000
do
@@ -980,6 +1218,10 @@ test_expect_success 'basic atom: head contents:trailers' '
test_cmp expect actual.clean
'
+test_expect_success 'basic atom: rest must fail' '
+ test_must_fail git for-each-ref --format="%(rest)" refs/heads/main
+'
+
test_expect_success 'trailer parsing not fooled by --- line' '
git commit --allow-empty -F - <<-\EOF &&
this is the subject
diff --git a/t/t6402-merge-rename.sh b/t/t6402-merge-rename.sh
index 3da2896e3b..3a32b1a45c 100755
--- a/t/t6402-merge-rename.sh
+++ b/t/t6402-merge-rename.sh
@@ -103,7 +103,7 @@ test_expect_success 'setup' '
test_expect_success 'pull renaming branch into unrenaming one' \
'
git show-branch &&
- test_expect_code 1 git pull . white &&
+ test_expect_code 1 git pull --no-rebase . white &&
git ls-files -s &&
test_stdout_line_count = 3 git ls-files -u B &&
test_stdout_line_count = 1 git ls-files -s N &&
@@ -119,7 +119,7 @@ test_expect_success 'pull renaming branch into another renaming one' \
rm -f B &&
git reset --hard &&
git checkout red &&
- test_expect_code 1 git pull . white &&
+ test_expect_code 1 git pull --no-rebase . white &&
test_stdout_line_count = 3 git ls-files -u B &&
test_stdout_line_count = 1 git ls-files -s N &&
sed -ne "/^g/{
@@ -133,7 +133,7 @@ test_expect_success 'pull unrenaming branch into renaming one' \
'
git reset --hard &&
git show-branch &&
- test_expect_code 1 git pull . main &&
+ test_expect_code 1 git pull --no-rebase . main &&
test_stdout_line_count = 3 git ls-files -u B &&
test_stdout_line_count = 1 git ls-files -s N &&
sed -ne "/^g/{
@@ -147,7 +147,7 @@ test_expect_success 'pull conflicting renames' \
'
git reset --hard &&
git show-branch &&
- test_expect_code 1 git pull . blue &&
+ test_expect_code 1 git pull --no-rebase . blue &&
test_stdout_line_count = 1 git ls-files -u A &&
test_stdout_line_count = 1 git ls-files -u B &&
test_stdout_line_count = 1 git ls-files -u C &&
@@ -163,7 +163,7 @@ test_expect_success 'interference with untracked working tree file' '
git reset --hard &&
git show-branch &&
echo >A this file should not matter &&
- test_expect_code 1 git pull . white &&
+ test_expect_code 1 git pull --no-rebase . white &&
test_path_is_file A
'
@@ -173,7 +173,7 @@ test_expect_success 'interference with untracked working tree file' '
git show-branch &&
rm -f A &&
echo >A this file should not matter &&
- test_expect_code 1 git pull . red &&
+ test_expect_code 1 git pull --no-rebase . red &&
test_path_is_file A
'
@@ -183,7 +183,7 @@ test_expect_success 'interference with untracked working tree file' '
git checkout -f main &&
git tag -f anchor &&
git show-branch &&
- git pull . yellow &&
+ git pull --no-rebase . yellow &&
test_path_is_missing M &&
git reset --hard anchor
'
@@ -210,7 +210,7 @@ test_expect_success 'updated working tree file should prevent the merge' '
echo >>M one line addition &&
cat M >M.saved &&
git update-index M &&
- test_expect_code 128 git pull . yellow &&
+ test_expect_code 128 git pull --no-rebase . yellow &&
test_cmp M M.saved &&
rm -f M.saved
'
@@ -222,7 +222,7 @@ test_expect_success 'interference with untracked working tree file' '
git tag -f anchor &&
git show-branch &&
echo >M this file should not matter &&
- git pull . main &&
+ git pull --no-rebase . main &&
test_path_is_file M &&
! {
git ls-files -s |
diff --git a/t/t6409-merge-subtree.sh b/t/t6409-merge-subtree.sh
index d406b2343c..ba7890ec52 100755
--- a/t/t6409-merge-subtree.sh
+++ b/t/t6409-merge-subtree.sh
@@ -100,7 +100,7 @@ test_expect_success 'merge update' '
git checkout -b topic_2 &&
git commit -m "update git-gui" &&
cd ../git &&
- git pull -s subtree gui topic_2 &&
+ git pull --no-rebase -s subtree gui topic_2 &&
git ls-files -s >actual &&
(
echo "100644 $o3 0 git-gui/git-gui.sh" &&
@@ -129,7 +129,7 @@ test_expect_success 'initial ambiguous subtree' '
test_expect_success 'merge using explicit' '
cd ../git &&
git reset --hard topic_2 &&
- git pull -Xsubtree=git-gui gui topic_2 &&
+ git pull --no-rebase -Xsubtree=git-gui gui topic_2 &&
git ls-files -s >actual &&
(
echo "100644 $o3 0 git-gui/git-gui.sh" &&
@@ -142,7 +142,7 @@ test_expect_success 'merge using explicit' '
test_expect_success 'merge2 using explicit' '
cd ../git &&
git reset --hard topic_2 &&
- git pull -Xsubtree=git-gui2 gui topic_2 &&
+ git pull --no-rebase -Xsubtree=git-gui2 gui topic_2 &&
git ls-files -s >actual &&
(
echo "100644 $o1 0 git-gui/git-gui.sh" &&
diff --git a/t/t6415-merge-dir-to-symlink.sh b/t/t6415-merge-dir-to-symlink.sh
index 2ce104aca7..2655e295f5 100755
--- a/t/t6415-merge-dir-to-symlink.sh
+++ b/t/t6415-merge-dir-to-symlink.sh
@@ -25,7 +25,8 @@ test_expect_success 'checkout does not clobber untracked symlink' '
git reset --hard main &&
git rm --cached a/b &&
git commit -m "untracked symlink remains" &&
- test_must_fail git checkout start^0
+ test_must_fail git checkout start^0 &&
+ git clean -fd # Do not leave the untracked symlink in the way
'
test_expect_success 'a/b-2/c/d is kept when clobbering symlink b' '
@@ -34,7 +35,8 @@ 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_path_is_file a/b-2/c/d
+ test_path_is_file a/b-2/c/d &&
+ git clean -fd # Do not leave the untracked symlink in the way
'
test_expect_success 'checkout should not have deleted a/b-2/c/d' '
diff --git a/t/t6417-merge-ours-theirs.sh b/t/t6417-merge-ours-theirs.sh
index ac9aee9a66..ec065d6a65 100755
--- a/t/t6417-merge-ours-theirs.sh
+++ b/t/t6417-merge-ours-theirs.sh
@@ -69,11 +69,11 @@ test_expect_success 'binary file with -Xours/-Xtheirs' '
'
test_expect_success 'pull passes -X to underlying merge' '
- git reset --hard main && git pull -s recursive -Xours . side &&
- git reset --hard main && git pull -s recursive -X ours . side &&
- git reset --hard main && git pull -s recursive -Xtheirs . side &&
- git reset --hard main && git pull -s recursive -X theirs . side &&
- git reset --hard main && test_must_fail git pull -s recursive -X bork . side
+ git reset --hard main && git pull --no-rebase -s recursive -Xours . side &&
+ git reset --hard main && git pull --no-rebase -s recursive -X ours . side &&
+ git reset --hard main && git pull --no-rebase -s recursive -Xtheirs . side &&
+ git reset --hard main && git pull --no-rebase -s recursive -X theirs . side &&
+ git reset --hard main && test_must_fail git pull --no-rebase -s recursive -X bork . side
'
test_expect_success SYMLINKS 'symlink with -Xours/-Xtheirs' '
diff --git a/t/t6424-merge-unrelated-index-changes.sh b/t/t6424-merge-unrelated-index-changes.sh
index 5e3779ebc9..89dd544f38 100755
--- a/t/t6424-merge-unrelated-index-changes.sh
+++ b/t/t6424-merge-unrelated-index-changes.sh
@@ -132,6 +132,7 @@ test_expect_success 'merge-recursive, when index==head but head!=HEAD' '
# Make index match B
git diff C B -- | git apply --cached &&
+ test_when_finished "git clean -fd" && # Do not leave untracked around
# Merge B & F, with B as "head"
git merge-recursive A -- B F > out &&
test_i18ngrep "Already up to date" out
diff --git a/t/t6430-merge-recursive.sh b/t/t6430-merge-recursive.sh
index ffcc01fe65..a0efe7cb6d 100755
--- a/t/t6430-merge-recursive.sh
+++ b/t/t6430-merge-recursive.sh
@@ -718,7 +718,9 @@ test_expect_success 'merge-recursive remembers the names of all base trees' '
# merge-recursive prints in reverse order, but we do not care
sort <trees >expect &&
sed -n "s/^virtual //p" out | sort >actual &&
- test_cmp expect actual
+ test_cmp expect actual &&
+
+ git clean -fd
'
test_expect_success 'merge-recursive internal merge resolves to the sameness' '
diff --git a/t/t6436-merge-overwrite.sh b/t/t6436-merge-overwrite.sh
index 84b4aacf49..c0b7bd7c3f 100755
--- a/t/t6436-merge-overwrite.sh
+++ b/t/t6436-merge-overwrite.sh
@@ -68,7 +68,8 @@ test_expect_success 'will not overwrite removed file' '
git commit -m "rm c1.c" &&
cp important c1.c &&
test_must_fail git merge c1a &&
- test_cmp important c1.c
+ test_cmp important c1.c &&
+ rm c1.c # Do not leave untracked file in way of future tests
'
test_expect_success 'will not overwrite re-added file' '
diff --git a/t/t6500-gc.sh b/t/t6500-gc.sh
index 10c7ae7f09..c2021267f2 100755
--- a/t/t6500-gc.sh
+++ b/t/t6500-gc.sh
@@ -241,7 +241,7 @@ test_expect_success 'background auto gc respects lock for all operations' '
# create a ref whose loose presence we can use to detect a pack-refs run
git update-ref refs/heads/should-be-loose HEAD &&
- test_path_is_file .git/refs/heads/should-be-loose &&
+ (ls -1 .git/refs/heads .git/reftable >expect || true) &&
# now fake a concurrent gc that holds the lock; we can use our
# shell pid so that it looks valid.
@@ -258,7 +258,8 @@ test_expect_success 'background auto gc respects lock for all operations' '
# our gc should exit zero without doing anything
run_and_wait_for_auto_gc &&
- test_path_is_file .git/refs/heads/should-be-loose
+ (ls -1 .git/refs/heads .git/reftable >actual || true) &&
+ test_cmp expect actual
'
# DO NOT leave a detached auto gc process running near the end of the
diff --git a/t/t7002-mv-sparse-checkout.sh b/t/t7002-mv-sparse-checkout.sh
new file mode 100755
index 0000000000..545748949a
--- /dev/null
+++ b/t/t7002-mv-sparse-checkout.sh
@@ -0,0 +1,189 @@
+#!/bin/sh
+
+test_description='git mv in sparse working trees'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' "
+ mkdir -p sub/dir sub/dir2 &&
+ touch a b c sub/d sub/dir/e sub/dir2/e &&
+ git add -A &&
+ git commit -m files &&
+
+ cat >sparse_error_header <<-EOF &&
+ The following paths and/or pathspecs matched paths that exist
+ outside of your sparse-checkout definition, so will not be
+ updated in the index:
+ EOF
+
+ cat >sparse_hint <<-EOF
+ hint: If you intend to update such entries, try one of the following:
+ hint: * Use the --sparse option.
+ hint: * Disable or modify the sparsity rules.
+ hint: Disable this message with \"git config advice.updateSparsePath false\"
+ EOF
+"
+
+test_expect_success 'mv refuses to move sparse-to-sparse' '
+ test_when_finished rm -f e &&
+ git reset --hard &&
+ git sparse-checkout set a &&
+ touch b &&
+ test_must_fail git mv b e 2>stderr &&
+ cat sparse_error_header >expect &&
+ echo b >>expect &&
+ echo e >>expect &&
+ cat sparse_hint >>expect &&
+ test_cmp expect stderr &&
+ git mv --sparse b e 2>stderr &&
+ test_must_be_empty stderr
+'
+
+test_expect_success 'mv refuses to move sparse-to-sparse, ignores failure' '
+ test_when_finished rm -f b c e &&
+ git reset --hard &&
+ git sparse-checkout set a &&
+
+ # tracked-to-untracked
+ touch b &&
+ git mv -k b e 2>stderr &&
+ test_path_exists b &&
+ test_path_is_missing e &&
+ cat sparse_error_header >expect &&
+ echo b >>expect &&
+ echo e >>expect &&
+ cat sparse_hint >>expect &&
+ test_cmp expect stderr &&
+
+ git mv --sparse b e 2>stderr &&
+ test_must_be_empty stderr &&
+ test_path_is_missing b &&
+ test_path_exists e &&
+
+ # tracked-to-tracked
+ git reset --hard &&
+ touch b &&
+ git mv -k b c 2>stderr &&
+ test_path_exists b &&
+ test_path_is_missing c &&
+ cat sparse_error_header >expect &&
+ echo b >>expect &&
+ echo c >>expect &&
+ cat sparse_hint >>expect &&
+ test_cmp expect stderr &&
+
+ git mv --sparse b c 2>stderr &&
+ test_must_be_empty stderr &&
+ test_path_is_missing b &&
+ test_path_exists c
+'
+
+test_expect_success 'mv refuses to move non-sparse-to-sparse' '
+ test_when_finished rm -f b c e &&
+ git reset --hard &&
+ git sparse-checkout set a &&
+
+ # tracked-to-untracked
+ test_must_fail git mv a e 2>stderr &&
+ test_path_exists a &&
+ test_path_is_missing e &&
+ cat sparse_error_header >expect &&
+ echo e >>expect &&
+ cat sparse_hint >>expect &&
+ test_cmp expect stderr &&
+ git mv --sparse a e 2>stderr &&
+ test_must_be_empty stderr &&
+ test_path_is_missing a &&
+ test_path_exists e &&
+
+ # tracked-to-tracked
+ rm e &&
+ git reset --hard &&
+ test_must_fail git mv a c 2>stderr &&
+ test_path_exists a &&
+ test_path_is_missing c &&
+ cat sparse_error_header >expect &&
+ echo c >>expect &&
+ cat sparse_hint >>expect &&
+ test_cmp expect stderr &&
+ git mv --sparse a c 2>stderr &&
+ test_must_be_empty stderr &&
+ test_path_is_missing a &&
+ test_path_exists c
+'
+
+test_expect_success 'mv refuses to move sparse-to-non-sparse' '
+ test_when_finished rm -f b c e &&
+ git reset --hard &&
+ git sparse-checkout set a e &&
+
+ # tracked-to-untracked
+ touch b &&
+ test_must_fail git mv b e 2>stderr &&
+ cat sparse_error_header >expect &&
+ echo b >>expect &&
+ cat sparse_hint >>expect &&
+ test_cmp expect stderr &&
+ git mv --sparse b e 2>stderr &&
+ test_must_be_empty stderr
+'
+
+test_expect_success 'recursive mv refuses to move (possible) sparse' '
+ test_when_finished rm -rf b c e sub2 &&
+ git reset --hard &&
+ # Without cone mode, "sub" and "sub2" do not match
+ git sparse-checkout set sub/dir sub2/dir &&
+
+ # Add contained contents to ensure we avoid non-existence errors
+ mkdir sub/dir2 &&
+ touch sub/d sub/dir2/e &&
+
+ test_must_fail git mv sub sub2 2>stderr &&
+ cat sparse_error_header >expect &&
+ cat >>expect <<-\EOF &&
+ sub/d
+ sub2/d
+ sub/dir/e
+ sub2/dir/e
+ sub/dir2/e
+ sub2/dir2/e
+ EOF
+ cat sparse_hint >>expect &&
+ test_cmp expect stderr &&
+ git mv --sparse sub sub2 2>stderr &&
+ test_must_be_empty stderr &&
+ git commit -m "moved sub to sub2" &&
+ git rev-parse HEAD~1:sub >expect &&
+ git rev-parse HEAD:sub2 >actual &&
+ test_cmp expect actual &&
+ git reset --hard HEAD~1
+'
+
+test_expect_success 'recursive mv refuses to move sparse' '
+ git reset --hard &&
+ # Use cone mode so "sub/" matches the sparse-checkout patterns
+ git sparse-checkout init --cone &&
+ git sparse-checkout set sub/dir sub2/dir &&
+
+ # Add contained contents to ensure we avoid non-existence errors
+ mkdir sub/dir2 &&
+ touch sub/dir2/e &&
+
+ test_must_fail git mv sub sub2 2>stderr &&
+ cat sparse_error_header >expect &&
+ cat >>expect <<-\EOF &&
+ sub/dir2/e
+ sub2/dir2/e
+ EOF
+ cat sparse_hint >>expect &&
+ test_cmp expect stderr &&
+ git mv --sparse sub sub2 2>stderr &&
+ test_must_be_empty stderr &&
+ git commit -m "moved sub to sub2" &&
+ git rev-parse HEAD~1:sub >expect &&
+ git rev-parse HEAD:sub2 >actual &&
+ test_cmp expect actual &&
+ git reset --hard HEAD~1
+'
+
+test_done
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index 2f72c5c688..082be85dff 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -1998,6 +1998,10 @@ test_expect_success '--format should list tags as per format given' '
test_cmp expect actual
'
+test_expect_success 'git tag -l with --format="%(rest)" must fail' '
+ test_must_fail git tag -l --format="%(rest)" "v1*"
+'
+
test_expect_success "set up color tests" '
echo "<RED>v1.0<RESET>" >expect.color &&
echo "v1.0" >expect.bare &&
diff --git a/t/t7030-verify-tag.sh b/t/t7030-verify-tag.sh
index 3cefde9602..10faa64515 100755
--- a/t/t7030-verify-tag.sh
+++ b/t/t7030-verify-tag.sh
@@ -194,6 +194,10 @@ test_expect_success GPG 'verifying tag with --format' '
test_cmp expect actual
'
+test_expect_success GPG 'verifying tag with --format="%(rest)" must fail' '
+ test_must_fail git verify-tag --format="%(rest)" "fourth-signed"
+'
+
test_expect_success GPG 'verifying a forged tag with --format should fail silently' '
test_must_fail git verify-tag --format="tagname : %(tag)" $(cat forged1.tag) >actual-forged &&
test_must_be_empty actual-forged
diff --git a/t/t7064-wtstatus-pv2.sh b/t/t7064-wtstatus-pv2.sh
index 4613882caf..eeb0534163 100755
--- a/t/t7064-wtstatus-pv2.sh
+++ b/t/t7064-wtstatus-pv2.sh
@@ -373,10 +373,7 @@ test_expect_success 'verify upstream fields in branch header' '
## Test upstream-gone case. Fake this by pointing
## origin/initial-branch at a non-existing commit.
- OLD=$(git rev-parse origin/initial-branch) &&
- NEW=$ZERO_OID &&
- mv .git/packed-refs .git/old-packed-refs &&
- sed "s/$OLD/$NEW/g" <.git/old-packed-refs >.git/packed-refs &&
+ git update-ref -d refs/remotes/origin/initial-branch &&
HUF=$(git rev-parse HEAD) &&
diff --git a/t/t7112-reset-submodule.sh b/t/t7112-reset-submodule.sh
index 19830d9036..a3e2413bc3 100755
--- a/t/t7112-reset-submodule.sh
+++ b/t/t7112-reset-submodule.sh
@@ -6,7 +6,6 @@ test_description='reset can handle submodules'
. "$TEST_DIRECTORY"/lib-submodule-update.sh
KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1
-KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED=1
test_submodule_switch_recursing_with_args "reset --keep"
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 7f6e23a4bb..b7ba1c3268 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -585,6 +585,7 @@ test_expect_success 'checkout --conflict=diff3' '
'
test_expect_success 'failing checkout -b should not break working tree' '
+ git clean -fd && # Remove untracked files in the way
git reset --hard main &&
git symbolic-ref HEAD refs/heads/main &&
test_must_fail git checkout -b renamer side^ &&
diff --git a/t/t7500-commit-template-squash-signoff.sh b/t/t7500-commit-template-squash-signoff.sh
index 54c2082acb..8dd0f98812 100755
--- a/t/t7500-commit-template-squash-signoff.sh
+++ b/t/t7500-commit-template-squash-signoff.sh
@@ -270,7 +270,7 @@ EOF
test_expect_success 'commit --fixup provides correct one-line commit message' '
commit_for_rebase_autosquash_setup &&
- git commit --fixup HEAD~1 &&
+ EDITOR="echo ignored >>" git commit --fixup HEAD~1 &&
commit_msg_is "fixup! target message subject line"
'
@@ -281,6 +281,13 @@ test_expect_success 'commit --fixup -m"something" -m"extra"' '
extra"
'
+test_expect_success 'commit --fixup --edit' '
+ commit_for_rebase_autosquash_setup &&
+ EDITOR="printf \"something\nextra\" >>" git commit --fixup HEAD~1 --edit &&
+ commit_msg_is "fixup! target message subject linesomething
+extra"
+'
+
get_commit_msg () {
rev="$1" &&
git log -1 --pretty=format:"%B" "$rev"
diff --git a/t/t7505-prepare-commit-msg-hook.sh b/t/t7505-prepare-commit-msg-hook.sh
index 7a8194ce72..2a07c70867 100755
--- a/t/t7505-prepare-commit-msg-hook.sh
+++ b/t/t7505-prepare-commit-msg-hook.sh
@@ -250,7 +250,6 @@ test_rebase () {
}
test_rebase success
-test_have_prereq !REBASE_P || test_rebase success -p
test_expect_success 'with hook (cherry-pick)' '
test_when_finished "git checkout -f main" &&
diff --git a/t/t7517-per-repo-email.sh b/t/t7517-per-repo-email.sh
index 405420ae4d..163ae80468 100755
--- a/t/t7517-per-repo-email.sh
+++ b/t/t7517-per-repo-email.sh
@@ -75,19 +75,6 @@ test_expect_success 'noop interactive rebase does not care about ident' '
git rebase -i HEAD^
'
-test_expect_success REBASE_P \
- 'fast-forward rebase does not care about ident (preserve)' '
- git checkout -B tmp side-without-commit &&
- git rebase -p main
-'
-
-test_expect_success REBASE_P \
- 'non-fast-forward rebase refuses to write commits (preserve)' '
- test_when_finished "git rebase --abort || true" &&
- git checkout -B tmp side-with-commit &&
- test_must_fail git rebase -p main
-'
-
test_expect_success 'author.name overrides user.name' '
test_config user.name user &&
test_config user.email user@example.com &&
diff --git a/t/t7519-status-fsmonitor.sh b/t/t7519-status-fsmonitor.sh
index deea88d443..f488d930df 100755
--- a/t/t7519-status-fsmonitor.sh
+++ b/t/t7519-status-fsmonitor.sh
@@ -389,47 +389,55 @@ test_expect_success 'status succeeds after staging/unstaging' '
# If "!" is supplied, then we verify that we do not call ensure_full_index
# during a call to 'git status'. Otherwise, we verify that we _do_ call it.
check_sparse_index_behavior () {
- git status --porcelain=v2 >expect &&
- git sparse-checkout init --cone --sparse-index &&
- git sparse-checkout set dir1 dir2 &&
+ git -C full status --porcelain=v2 >expect &&
GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
- git status --porcelain=v2 >actual &&
+ git -C sparse status --porcelain=v2 >actual &&
test_region $1 index ensure_full_index trace2.txt &&
test_region fsm_hook query trace2.txt &&
test_cmp expect actual &&
- rm trace2.txt &&
- git sparse-checkout disable
+ rm trace2.txt
}
test_expect_success 'status succeeds with sparse index' '
- git reset --hard &&
-
- test_config core.fsmonitor "$TEST_DIRECTORY/t7519/fsmonitor-all" &&
- check_sparse_index_behavior ! &&
-
- write_script .git/hooks/fsmonitor-test<<-\EOF &&
- printf "last_update_token\0"
- EOF
- git config core.fsmonitor .git/hooks/fsmonitor-test &&
- check_sparse_index_behavior ! &&
+ (
+ sane_unset GIT_TEST_SPLIT_INDEX &&
- write_script .git/hooks/fsmonitor-test<<-\EOF &&
- printf "last_update_token\0"
- printf "dir1/modified\0"
- EOF
- check_sparse_index_behavior ! &&
+ git clone . full &&
+ git clone --sparse . sparse &&
+ git -C sparse sparse-checkout init --cone --sparse-index &&
+ git -C sparse sparse-checkout set dir1 dir2 &&
- cp -r dir1 dir1a &&
- git add dir1a &&
- git commit -m "add dir1a" &&
+ write_script .git/hooks/fsmonitor-test <<-\EOF &&
+ printf "last_update_token\0"
+ EOF
+ git -C full config core.fsmonitor ../.git/hooks/fsmonitor-test &&
+ git -C sparse config core.fsmonitor ../.git/hooks/fsmonitor-test &&
+ check_sparse_index_behavior ! &&
- # This one modifies outside the sparse-checkout definition
- # and hence we expect to expand the sparse-index.
- write_script .git/hooks/fsmonitor-test<<-\EOF &&
- printf "last_update_token\0"
- printf "dir1a/modified\0"
- EOF
- check_sparse_index_behavior
+ write_script .git/hooks/fsmonitor-test <<-\EOF &&
+ printf "last_update_token\0"
+ printf "dir1/modified\0"
+ EOF
+ check_sparse_index_behavior ! &&
+
+ git -C sparse sparse-checkout add dir1a &&
+
+ for repo in full sparse
+ do
+ cp -r $repo/dir1 $repo/dir1a &&
+ git -C $repo add dir1a &&
+ git -C $repo commit -m "add dir1a" || return 1
+ done &&
+ git -C sparse sparse-checkout set dir1 dir2 &&
+
+ # This one modifies outside the sparse-checkout definition
+ # and hence we expect to expand the sparse-index.
+ write_script .git/hooks/fsmonitor-test <<-\EOF &&
+ printf "last_update_token\0"
+ printf "dir1a/modified\0"
+ EOF
+ check_sparse_index_behavior
+ )
'
test_done
diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh
index 2ef39d3088..c773e30b3f 100755
--- a/t/t7600-merge.sh
+++ b/t/t7600-merge.sh
@@ -717,6 +717,7 @@ test_expect_success 'failed fast-forward merge with --autostash' '
git reset --hard c0 &&
git merge-file file file.orig file.5 &&
cp file.5 other &&
+ test_when_finished "rm other" &&
test_must_fail git merge --autostash c1 2>err &&
test_i18ngrep "Applied autostash." err &&
test_cmp file.5 file
diff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh
index 52e8ccc933..1f652f433e 100755
--- a/t/t7601-merge-pull-config.sh
+++ b/t/t7601-merge-pull-config.sh
@@ -27,120 +27,324 @@ test_expect_success 'setup' '
git tag c3
'
-test_expect_success 'pull.rebase not set' '
+test_expect_success 'pull.rebase not set, ff possible' '
git reset --hard c0 &&
git pull . c1 2>err &&
- test_i18ngrep ! "Pulling without specifying how to reconcile" err
+ test_i18ngrep ! "You have divergent branches" 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_i18ngrep ! "You have divergent branches" 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_i18ngrep ! "You have divergent branches" 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_i18ngrep ! "You have divergent branches" 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_i18ngrep ! "You have divergent branches" 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_i18ngrep ! "You have divergent branches" 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_i18ngrep ! "You have divergent branches" 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_i18ngrep ! "You have divergent branches" 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_i18ngrep ! "You have divergent branches" err
'
test_expect_success 'pull.rebase not set (not-fast-forward)' '
git reset --hard c2 &&
- git -c color.advice=always pull . c1 2>err &&
+ test_must_fail git -c color.advice=always pull . c1 2>err &&
test_decode_color <err >decoded &&
test_i18ngrep "<YELLOW>hint: " decoded &&
- test_i18ngrep "Pulling without specifying how to reconcile" decoded
+ test_i18ngrep "You have divergent branches" decoded
'
test_expect_success 'pull.rebase not set and pull.ff=true (not-fast-forward)' '
git reset --hard c2 &&
test_config pull.ff true &&
git pull . c1 2>err &&
- test_i18ngrep ! "Pulling without specifying how to reconcile" err
+ test_i18ngrep ! "You have divergent branches" err
'
test_expect_success 'pull.rebase not set and pull.ff=false (not-fast-forward)' '
git reset --hard c2 &&
test_config pull.ff false &&
git pull . c1 2>err &&
- test_i18ngrep ! "Pulling without specifying how to reconcile" err
+ test_i18ngrep ! "You have divergent branches" err
'
test_expect_success 'pull.rebase not set and pull.ff=only (not-fast-forward)' '
git reset --hard c2 &&
test_config pull.ff only &&
test_must_fail git pull . c1 2>err &&
- test_i18ngrep ! "Pulling without specifying how to reconcile" err
+ test_i18ngrep ! "You have divergent branches" err
'
test_expect_success 'pull.rebase not set and --rebase given (not-fast-forward)' '
git reset --hard c2 &&
git pull --rebase . c1 2>err &&
- test_i18ngrep ! "Pulling without specifying how to reconcile" err
+ test_i18ngrep ! "You have divergent branches" err
'
test_expect_success 'pull.rebase not set and --no-rebase given (not-fast-forward)' '
git reset --hard c2 &&
git pull --no-rebase . c1 2>err &&
- test_i18ngrep ! "Pulling without specifying how to reconcile" err
+ test_i18ngrep ! "You have divergent branches" err
'
test_expect_success 'pull.rebase not set and --ff given (not-fast-forward)' '
git reset --hard c2 &&
git pull --ff . c1 2>err &&
- test_i18ngrep ! "Pulling without specifying how to reconcile" err
+ test_i18ngrep ! "You have divergent branches" err
'
test_expect_success 'pull.rebase not set and --no-ff given (not-fast-forward)' '
git reset --hard c2 &&
git pull --no-ff . c1 2>err &&
- test_i18ngrep ! "Pulling without specifying how to reconcile" err
+ test_i18ngrep ! "You have divergent branches" err
'
test_expect_success 'pull.rebase not set and --ff-only given (not-fast-forward)' '
git reset --hard c2 &&
test_must_fail git pull --ff-only . c1 2>err &&
- test_i18ngrep ! "Pulling without specifying how to reconcile" err
+ test_i18ngrep ! "You have divergent branches" err
+'
+
+test_does_rebase () {
+ git reset --hard c2 &&
+ git "$@" . c1 &&
+ # Check that we actually did a rebase
+ git rev-list --count HEAD >actual &&
+ git rev-list --merges --count HEAD >>actual &&
+ test_write_lines 3 0 >expect &&
+ test_cmp expect actual &&
+ rm actual expect
+}
+
+# Prefers merge over fast-forward
+test_does_merge_when_ff_possible () {
+ git reset --hard c0 &&
+ git "$@" . c1 &&
+ # Check that we actually did a merge
+ git rev-list --count HEAD >actual &&
+ git rev-list --merges --count HEAD >>actual &&
+ test_write_lines 3 1 >expect &&
+ test_cmp expect actual &&
+ rm actual expect
+}
+
+# Prefers fast-forward over merge or rebase
+test_does_fast_forward () {
+ git reset --hard c0 &&
+ git "$@" . c1 &&
+
+ # Check that we did not get any merges
+ git rev-list --count HEAD >actual &&
+ git rev-list --merges --count HEAD >>actual &&
+ test_write_lines 2 0 >expect &&
+ test_cmp expect actual &&
+
+ # Check that we ended up at c1
+ git rev-parse HEAD >actual &&
+ git rev-parse c1^{commit} >expect &&
+ test_cmp actual expect &&
+
+ # Remove temporary files
+ rm actual expect
+}
+
+# Doesn't fail when fast-forward not possible; does a merge
+test_falls_back_to_full_merge () {
+ git reset --hard c2 &&
+ git "$@" . c1 &&
+ # Check that we actually did a merge
+ git rev-list --count HEAD >actual &&
+ git rev-list --merges --count HEAD >>actual &&
+ test_write_lines 4 1 >expect &&
+ test_cmp expect actual &&
+ rm actual expect
+}
+
+# Attempts fast forward, which is impossible, and bails
+test_attempts_fast_forward () {
+ git reset --hard c2 &&
+ test_must_fail git "$@" . c1 2>err &&
+ test_i18ngrep "Not possible to fast-forward, aborting" err
+}
+
+#
+# Group 1: Interaction of --ff-only with --[no-]rebase
+# (And related interaction of pull.ff=only with pull.rebase)
+#
+test_expect_success '--ff-only overrides --rebase' '
+ test_attempts_fast_forward pull --rebase --ff-only
+'
+
+test_expect_success '--ff-only overrides --rebase even if first' '
+ test_attempts_fast_forward pull --ff-only --rebase
+'
+
+test_expect_success '--ff-only overrides --no-rebase' '
+ test_attempts_fast_forward pull --ff-only --no-rebase
+'
+
+test_expect_success 'pull.ff=only overrides pull.rebase=true' '
+ test_attempts_fast_forward -c pull.ff=only -c pull.rebase=true pull
+'
+
+test_expect_success 'pull.ff=only overrides pull.rebase=false' '
+ test_attempts_fast_forward -c pull.ff=only -c pull.rebase=false pull
+'
+
+# Group 2: --rebase=[!false] overrides --no-ff and --ff
+# (And related interaction of pull.rebase=!false and pull.ff=!only)
+test_expect_success '--rebase overrides --no-ff' '
+ test_does_rebase pull --rebase --no-ff
+'
+
+test_expect_success '--rebase overrides --ff' '
+ test_does_rebase pull --rebase --ff
+'
+
+test_expect_success '--rebase fast-forwards when possible' '
+ test_does_fast_forward pull --rebase --ff
+'
+
+test_expect_success 'pull.rebase=true overrides pull.ff=false' '
+ test_does_rebase -c pull.rebase=true -c pull.ff=false pull
+'
+
+test_expect_success 'pull.rebase=true overrides pull.ff=true' '
+ test_does_rebase -c pull.rebase=true -c pull.ff=true pull
+'
+
+# Group 3: command line flags take precedence over config
+test_expect_success '--ff-only takes precedence over pull.rebase=true' '
+ test_attempts_fast_forward -c pull.rebase=true pull --ff-only
+'
+
+test_expect_success '--ff-only takes precedence over pull.rebase=false' '
+ test_attempts_fast_forward -c pull.rebase=false pull --ff-only
+'
+
+test_expect_success '--no-rebase takes precedence over pull.ff=only' '
+ test_falls_back_to_full_merge -c pull.ff=only pull --no-rebase
+'
+
+test_expect_success '--rebase takes precedence over pull.ff=only' '
+ test_does_rebase -c pull.ff=only pull --rebase
+'
+
+test_expect_success '--rebase overrides pull.ff=true' '
+ test_does_rebase -c pull.ff=true pull --rebase
+'
+
+test_expect_success '--rebase overrides pull.ff=false' '
+ test_does_rebase -c pull.ff=false pull --rebase
+'
+
+test_expect_success '--rebase overrides pull.ff unset' '
+ test_does_rebase pull --rebase
+'
+
+# Group 4: --no-rebase heeds pull.ff=!only or explict --ff or --no-ff
+
+test_expect_success '--no-rebase works with --no-ff' '
+ test_does_merge_when_ff_possible pull --no-rebase --no-ff
+'
+
+test_expect_success '--no-rebase works with --ff' '
+ test_does_fast_forward pull --no-rebase --ff
+'
+
+test_expect_success '--no-rebase does ff if pull.ff unset' '
+ test_does_fast_forward pull --no-rebase
+'
+
+test_expect_success '--no-rebase heeds pull.ff=true' '
+ test_does_fast_forward -c pull.ff=true pull --no-rebase
+'
+
+test_expect_success '--no-rebase heeds pull.ff=false' '
+ test_does_merge_when_ff_possible -c pull.ff=false pull --no-rebase
+'
+
+# Group 5: pull.rebase=!false in combination with --no-ff or --ff
+test_expect_success 'pull.rebase=true and --no-ff' '
+ test_does_rebase -c pull.rebase=true pull --no-ff
+'
+
+test_expect_success 'pull.rebase=true and --ff' '
+ test_does_rebase -c pull.rebase=true pull --ff
+'
+
+test_expect_success 'pull.rebase=false and --no-ff' '
+ test_does_merge_when_ff_possible -c pull.rebase=false pull --no-ff
+'
+
+test_expect_success 'pull.rebase=false and --ff, ff possible' '
+ test_does_fast_forward -c pull.rebase=false pull --ff
+'
+
+test_expect_success 'pull.rebase=false and --ff, ff not possible' '
+ test_falls_back_to_full_merge -c pull.rebase=false pull --ff
+'
+
+# End of groupings for conflicting merge vs. rebase flags/options
+
+test_expect_success 'Multiple heads warns about inability to fast forward' '
+ git reset --hard c1 &&
+ test_must_fail git pull . c2 c3 2>err &&
+ test_i18ngrep "You have divergent branches" err
+'
+
+test_expect_success 'Multiple can never be fast forwarded' '
+ git reset --hard c0 &&
+ test_must_fail git -c pull.ff=only pull . c1 c2 c3 2>err &&
+ test_i18ngrep ! "You have divergent branches" err &&
+ # In addition to calling out "cannot fast-forward", we very much
+ # want the "multiple branches" piece to be called out to users.
+ test_i18ngrep "Cannot fast-forward to multiple branches" err
+'
+
+test_expect_success 'Cannot rebase with multiple heads' '
+ git reset --hard c0 &&
+ test_must_fail git -c pull.rebase=true pull . c1 c2 c3 2>err &&
+ test_i18ngrep ! "You have divergent branches" err &&
+ test_i18ngrep "Cannot rebase onto multiple branches." err
'
test_expect_success 'merge c1 with c2' '
diff --git a/t/t7603-merge-reduce-heads.sh b/t/t7603-merge-reduce-heads.sh
index 98948955ae..27cd94ad6f 100755
--- a/t/t7603-merge-reduce-heads.sh
+++ b/t/t7603-merge-reduce-heads.sh
@@ -68,7 +68,7 @@ test_expect_success 'merge c1 with c2, c3, c4, c5' '
test_expect_success 'pull c2, c3, c4, c5 into c1' '
git reset --hard c1 &&
- git pull . c2 c3 c4 c5 &&
+ git pull --no-rebase . c2 c3 c4 c5 &&
test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
index a173f564bc..096456292c 100755
--- a/t/t7800-difftool.sh
+++ b/t/t7800-difftool.sh
@@ -453,6 +453,13 @@ run_dir_diff_test 'difftool --dir-diff' '
grep "^file$" output
'
+run_dir_diff_test 'difftool --dir-diff avoids repeated slashes in TMPDIR' '
+ TMPDIR="${TMPDIR:-/tmp}////" \
+ git difftool --dir-diff $symlinks --extcmd echo branch >output &&
+ grep -v // output >actual &&
+ test_line_count = 1 actual
+'
+
run_dir_diff_test 'difftool --dir-diff ignores --prompt' '
git difftool --dir-diff $symlinks --prompt --extcmd ls branch >output &&
grep "^sub$" output &&
@@ -674,7 +681,6 @@ test_expect_success SYMLINKS 'difftool --dir-diff handles modified symlinks' '
rm c &&
ln -s d c &&
cat >expect <<-EOF &&
- b
c
c
@@ -710,7 +716,6 @@ test_expect_success SYMLINKS 'difftool --dir-diff handles modified symlinks' '
# Deleted symlinks
rm -f c &&
cat >expect <<-EOF &&
- b
c
EOF
@@ -723,6 +728,71 @@ test_expect_success SYMLINKS 'difftool --dir-diff handles modified symlinks' '
test_cmp expect actual
'
+test_expect_success SYMLINKS 'difftool --dir-diff writes symlinks as raw text' '
+ # Start out on a branch called "branch-init".
+ git init -b branch-init symlink-files &&
+ (
+ cd symlink-files &&
+ # This test ensures that symlinks are written as raw text.
+ # The "cat" tools output link and file contents.
+ git config difftool.cat-left-link.cmd "cat \"\$LOCAL/link\"" &&
+ git config difftool.cat-left-a.cmd "cat \"\$LOCAL/file-a\"" &&
+ git config difftool.cat-right-link.cmd "cat \"\$REMOTE/link\"" &&
+ git config difftool.cat-right-b.cmd "cat \"\$REMOTE/file-b\"" &&
+
+ # Record the empty initial state so that we can come back here
+ # later and not have to consider the any cases where difftool
+ # will create symlinks back into the worktree.
+ test_tick &&
+ git commit --allow-empty -m init &&
+
+ # Create a file called "file-a" with a symlink pointing to it.
+ git switch -c branch-a &&
+ echo a >file-a &&
+ ln -s file-a link &&
+ git add file-a link &&
+ test_tick &&
+ git commit -m link-to-file-a &&
+
+ # Create a file called "file-b" and point the symlink to it.
+ git switch -c branch-b &&
+ echo b >file-b &&
+ rm link &&
+ ln -s file-b link &&
+ git add file-b link &&
+ git rm file-a &&
+ test_tick &&
+ git commit -m link-to-file-b &&
+
+ # Checkout the initial branch so that the --symlinks behavior is
+ # not activated. The two directories should be completely
+ # independent with no symlinks pointing back here.
+ git switch branch-init &&
+
+ # The left link must be "file-a" and "file-a" must contain "a".
+ echo file-a >expect &&
+ git difftool --symlinks --dir-diff --tool cat-left-link \
+ branch-a branch-b >actual &&
+ test_cmp expect actual &&
+
+ echo a >expect &&
+ git difftool --symlinks --dir-diff --tool cat-left-a \
+ branch-a branch-b >actual &&
+ test_cmp expect actual &&
+
+ # The right link must be "file-b" and "file-b" must contain "b".
+ echo file-b >expect &&
+ git difftool --symlinks --dir-diff --tool cat-right-link \
+ branch-a branch-b >actual &&
+ test_cmp expect actual &&
+
+ echo b >expect &&
+ git difftool --symlinks --dir-diff --tool cat-right-b \
+ branch-a branch-b >actual &&
+ test_cmp expect actual
+ )
+'
+
test_expect_success 'add -N and difftool -d' '
test_when_finished git reset --hard &&
diff --git a/t/t7814-grep-recurse-submodules.sh b/t/t7814-grep-recurse-submodules.sh
index 828cb3ba58..058e5d0c96 100755
--- a/t/t7814-grep-recurse-submodules.sh
+++ b/t/t7814-grep-recurse-submodules.sh
@@ -8,6 +8,9 @@ submodules.
. ./test-lib.sh
+GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1
+export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB
+
test_expect_success 'setup directory structure and submodule' '
echo "(1|2)d(3|4)" >a &&
mkdir b &&
@@ -438,4 +441,107 @@ test_expect_success 'grep --recurse-submodules with --cached ignores worktree mo
test_must_fail git grep --recurse-submodules --cached "A modified line in submodule" >actual 2>&1 &&
test_must_be_empty actual
'
+
+test_expect_failure 'grep --textconv: superproject .gitattributes does not affect submodules' '
+ reset_and_clean &&
+ test_config_global diff.d2x.textconv "sed -e \"s/d/x/\"" &&
+ echo "a diff=d2x" >.gitattributes &&
+
+ cat >expect <<-\EOF &&
+ a:(1|2)x(3|4)
+ EOF
+ git grep --textconv --recurse-submodules x >actual &&
+ test_cmp expect actual
+'
+
+test_expect_failure 'grep --textconv: superproject .gitattributes (from index) does not affect submodules' '
+ reset_and_clean &&
+ test_config_global diff.d2x.textconv "sed -e \"s/d/x/\"" &&
+ echo "a diff=d2x" >.gitattributes &&
+ git add .gitattributes &&
+ rm .gitattributes &&
+
+ cat >expect <<-\EOF &&
+ a:(1|2)x(3|4)
+ EOF
+ git grep --textconv --recurse-submodules x >actual &&
+ test_cmp expect actual
+'
+
+test_expect_failure 'grep --textconv: superproject .git/info/attributes does not affect submodules' '
+ reset_and_clean &&
+ test_config_global diff.d2x.textconv "sed -e \"s/d/x/\"" &&
+ super_attr="$(git rev-parse --git-path info/attributes)" &&
+ test_when_finished "rm -f \"$super_attr\"" &&
+ echo "a diff=d2x" >"$super_attr" &&
+
+ cat >expect <<-\EOF &&
+ a:(1|2)x(3|4)
+ EOF
+ git grep --textconv --recurse-submodules x >actual &&
+ test_cmp expect actual
+'
+
+# Note: what currently prevents this test from passing is not that the
+# .gitattributes file from "./submodule" is being ignored, but that it is being
+# propagated to the nested "./submodule/sub" files.
+#
+test_expect_failure 'grep --textconv correctly reads submodule .gitattributes' '
+ reset_and_clean &&
+ test_config_global diff.d2x.textconv "sed -e \"s/d/x/\"" &&
+ echo "a diff=d2x" >submodule/.gitattributes &&
+
+ cat >expect <<-\EOF &&
+ submodule/a:(1|2)x(3|4)
+ EOF
+ git grep --textconv --recurse-submodules x >actual &&
+ test_cmp expect actual
+'
+
+test_expect_failure 'grep --textconv correctly reads submodule .gitattributes (from index)' '
+ reset_and_clean &&
+ test_config_global diff.d2x.textconv "sed -e \"s/d/x/\"" &&
+ echo "a diff=d2x" >submodule/.gitattributes &&
+ git -C submodule add .gitattributes &&
+ rm submodule/.gitattributes &&
+
+ cat >expect <<-\EOF &&
+ submodule/a:(1|2)x(3|4)
+ EOF
+ git grep --textconv --recurse-submodules x >actual &&
+ test_cmp expect actual
+'
+
+test_expect_failure 'grep --textconv correctly reads submodule .git/info/attributes' '
+ reset_and_clean &&
+ test_config_global diff.d2x.textconv "sed -e \"s/d/x/\"" &&
+
+ submodule_attr="$(git -C submodule rev-parse --path-format=absolute --git-path info/attributes)" &&
+ test_when_finished "rm -f \"$submodule_attr\"" &&
+ echo "a diff=d2x" >"$submodule_attr" &&
+
+ cat >expect <<-\EOF &&
+ submodule/a:(1|2)x(3|4)
+ EOF
+ git grep --textconv --recurse-submodules x >actual &&
+ test_cmp expect actual
+'
+
+test_expect_failure 'grep saves textconv cache in the appropriate repository' '
+ reset_and_clean &&
+ test_config_global diff.d2x_cached.textconv "sed -e \"s/d/x/\"" &&
+ test_config_global diff.d2x_cached.cachetextconv true &&
+ echo "a diff=d2x_cached" >submodule/.gitattributes &&
+
+ # We only read/write to the textconv cache when grepping from an OID,
+ # as the working tree file might have modifications.
+ git grep --textconv --cached --recurse-submodules x &&
+
+ super_textconv_cache="$(git rev-parse --git-path refs/notes/textconv/d2x_cached)" &&
+ sub_textconv_cache="$(git -C submodule rev-parse \
+ --path-format=absolute --git-path refs/notes/textconv/d2x_cached)" &&
+ test_path_is_missing "$super_textconv_cache" &&
+ test_path_is_file "$sub_textconv_cache"
+'
+
test_done
diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh
index 58f46c77e6..9b9f11a8e7 100755
--- a/t/t7900-maintenance.sh
+++ b/t/t7900-maintenance.sh
@@ -20,6 +20,17 @@ test_xmllint () {
fi
}
+test_lazy_prereq SYSTEMD_ANALYZE '
+ systemd-analyze verify /lib/systemd/system/basic.target
+'
+
+test_systemd_analyze_verify () {
+ if test_have_prereq SYSTEMD_ANALYZE
+ then
+ systemd-analyze verify "$@"
+ fi
+}
+
test_expect_success 'help text' '
test_expect_code 129 git maintenance -h 2>err &&
test_i18ngrep "usage: git maintenance <subcommand>" err &&
@@ -265,7 +276,7 @@ test_expect_success 'incremental-repack task' '
# Delete refs that have not been repacked in these packs.
git for-each-ref --format="delete %(refname)" \
- refs/prefetch refs/tags >refs &&
+ refs/prefetch refs/tags refs/remotes >refs &&
git update-ref --stdin <refs &&
# Replace the object directory with this pack layout.
@@ -274,6 +285,10 @@ test_expect_success 'incremental-repack task' '
ls $packDir/*.pack >packs-before &&
test_line_count = 3 packs-before &&
+ # make sure we do not have any broken refs that were
+ # missed in the deletion above
+ git for-each-ref &&
+
# the job repacks the two into a new pack, but does not
# delete the old ones.
git maintenance run --task=incremental-repack &&
@@ -492,8 +507,21 @@ test_expect_success !MINGW 'register and unregister with regex metacharacters' '
maintenance.repo "$(pwd)/$META"
'
+test_expect_success 'start --scheduler=<scheduler>' '
+ test_expect_code 129 git maintenance start --scheduler=foo 2>err &&
+ test_i18ngrep "unrecognized --scheduler argument" err &&
+
+ test_expect_code 129 git maintenance start --no-scheduler 2>err &&
+ test_i18ngrep "unknown option" err &&
+
+ test_expect_code 128 \
+ env GIT_TEST_MAINT_SCHEDULER="launchctl:true,schtasks:true" \
+ git maintenance start --scheduler=crontab 2>err &&
+ test_i18ngrep "fatal: crontab scheduler is not available" err
+'
+
test_expect_success 'start from empty cron table' '
- GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab cron.txt" git maintenance start &&
+ GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab cron.txt" git maintenance start --scheduler=crontab &&
# start registers the repo
git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
@@ -516,7 +544,7 @@ test_expect_success 'stop from existing schedule' '
test_expect_success 'start preserves existing schedule' '
echo "Important information!" >cron.txt &&
- GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab cron.txt" git maintenance start &&
+ GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab cron.txt" git maintenance start --scheduler=crontab &&
grep "Important information!" cron.txt
'
@@ -545,7 +573,7 @@ test_expect_success 'start and stop macOS maintenance' '
EOF
rm -f args &&
- GIT_TEST_MAINT_SCHEDULER=launchctl:./print-args git maintenance start &&
+ GIT_TEST_MAINT_SCHEDULER=launchctl:./print-args git maintenance start --scheduler=launchctl &&
# start registers the repo
git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
@@ -582,6 +610,23 @@ test_expect_success 'start and stop macOS maintenance' '
test_line_count = 0 actual
'
+test_expect_success 'use launchctl list to prevent extra work' '
+ # ensure we are registered
+ GIT_TEST_MAINT_SCHEDULER=launchctl:./print-args git maintenance start --scheduler=launchctl &&
+
+ # do it again on a fresh args file
+ rm -f args &&
+ GIT_TEST_MAINT_SCHEDULER=launchctl:./print-args git maintenance start --scheduler=launchctl &&
+
+ ls "$HOME/Library/LaunchAgents" >actual &&
+ cat >expect <<-\EOF &&
+ list org.git-scm.git.hourly
+ list org.git-scm.git.daily
+ list org.git-scm.git.weekly
+ EOF
+ test_cmp expect args
+'
+
test_expect_success 'start and stop Windows maintenance' '
write_script print-args <<-\EOF &&
echo $* >>args
@@ -596,7 +641,7 @@ test_expect_success 'start and stop Windows maintenance' '
EOF
rm -f args &&
- GIT_TEST_MAINT_SCHEDULER="schtasks:./print-args" git maintenance start &&
+ GIT_TEST_MAINT_SCHEDULER="schtasks:./print-args" git maintenance start --scheduler=schtasks &&
# start registers the repo
git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
@@ -619,6 +664,83 @@ test_expect_success 'start and stop Windows maintenance' '
test_cmp expect args
'
+test_expect_success 'start and stop Linux/systemd maintenance' '
+ write_script print-args <<-\EOF &&
+ printf "%s\n" "$*" >>args
+ EOF
+
+ XDG_CONFIG_HOME="$PWD" &&
+ export XDG_CONFIG_HOME &&
+ rm -f args &&
+ GIT_TEST_MAINT_SCHEDULER="systemctl:./print-args" git maintenance start --scheduler=systemd-timer &&
+
+ # start registers the repo
+ git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
+
+ test_systemd_analyze_verify "systemd/user/git-maintenance@.service" &&
+
+ printf -- "--user enable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
+ test_cmp expect args &&
+
+ rm -f args &&
+ GIT_TEST_MAINT_SCHEDULER="systemctl:./print-args" git maintenance stop &&
+
+ # stop does not unregister the repo
+ git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
+
+ test_path_is_missing "systemd/user/git-maintenance@.timer" &&
+ test_path_is_missing "systemd/user/git-maintenance@.service" &&
+
+ printf -- "--user disable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
+ test_cmp expect args
+'
+
+test_expect_success 'start and stop when several schedulers are available' '
+ write_script print-args <<-\EOF &&
+ printf "%s\n" "$*" | sed "s:gui/[0-9][0-9]*:gui/[UID]:; s:\(schtasks /create .* /xml\).*:\1:;" >>args
+ EOF
+
+ rm -f args &&
+ GIT_TEST_MAINT_SCHEDULER="systemctl:./print-args systemctl,launchctl:./print-args launchctl,schtasks:./print-args schtasks" git maintenance start --scheduler=systemd-timer &&
+ printf "launchctl bootout gui/[UID] $pfx/Library/LaunchAgents/org.git-scm.git.%s.plist\n" \
+ hourly daily weekly >expect &&
+ printf "schtasks /delete /tn Git Maintenance (%s) /f\n" \
+ hourly daily weekly >>expect &&
+ printf -- "systemctl --user enable --now git-maintenance@%s.timer\n" hourly daily weekly >>expect &&
+ test_cmp expect args &&
+
+ rm -f args &&
+ GIT_TEST_MAINT_SCHEDULER="systemctl:./print-args systemctl,launchctl:./print-args launchctl,schtasks:./print-args schtasks" git maintenance start --scheduler=launchctl &&
+ printf -- "systemctl --user disable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
+ printf "schtasks /delete /tn Git Maintenance (%s) /f\n" \
+ hourly daily weekly >>expect &&
+ for frequency in hourly daily weekly
+ do
+ PLIST="$pfx/Library/LaunchAgents/org.git-scm.git.$frequency.plist" &&
+ echo "launchctl bootout gui/[UID] $PLIST" >>expect &&
+ echo "launchctl bootstrap gui/[UID] $PLIST" >>expect || return 1
+ done &&
+ test_cmp expect args &&
+
+ rm -f args &&
+ GIT_TEST_MAINT_SCHEDULER="systemctl:./print-args systemctl,launchctl:./print-args launchctl,schtasks:./print-args schtasks" git maintenance start --scheduler=schtasks &&
+ printf -- "systemctl --user disable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
+ printf "launchctl bootout gui/[UID] $pfx/Library/LaunchAgents/org.git-scm.git.%s.plist\n" \
+ hourly daily weekly >>expect &&
+ printf "schtasks /create /tn Git Maintenance (%s) /f /xml\n" \
+ hourly daily weekly >>expect &&
+ test_cmp expect args &&
+
+ rm -f args &&
+ GIT_TEST_MAINT_SCHEDULER="systemctl:./print-args systemctl,launchctl:./print-args launchctl,schtasks:./print-args schtasks" git maintenance stop &&
+ printf -- "systemctl --user disable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
+ printf "launchctl bootout gui/[UID] $pfx/Library/LaunchAgents/org.git-scm.git.%s.plist\n" \
+ hourly daily weekly >>expect &&
+ printf "schtasks /delete /tn Git Maintenance (%s) /f\n" \
+ hourly daily weekly >>expect &&
+ test_cmp expect args
+'
+
test_expect_success 'register preserves existing strategy' '
git config maintenance.strategy none &&
git maintenance register &&
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index 57fc10e7f8..aa0c20499b 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -1533,6 +1533,21 @@ test_expect_success $PREREQ 'sendemail.8bitEncoding works' '
test_cmp content-type-decl actual
'
+test_expect_success $PREREQ 'sendemail.8bitEncoding in .git/config overrides --global .gitconfig' '
+ clean_fake_sendmail &&
+ git config sendemail.assume8bitEncoding UTF-8 &&
+ test_when_finished "rm -rf home" &&
+ mkdir home &&
+ git config -f home/.gitconfig sendemail.assume8bitEncoding "bogus too" &&
+ echo bogus |
+ env HOME="$(pwd)/home" DEBUG=1 \
+ git send-email --from=author@example.com --to=nobody@example.com \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ email-using-8bit >stdout &&
+ egrep "Content|MIME" msgtxt1 >actual &&
+ test_cmp content-type-decl actual
+'
+
test_expect_success $PREREQ '--8bit-encoding overrides sendemail.8bitEncoding' '
clean_fake_sendmail &&
git config sendemail.assume8bitEncoding "bogus too" &&
@@ -2198,7 +2213,7 @@ test_expect_success $PREREQ 'leading and trailing whitespaces are removed' '
test_expect_success $PREREQ 'test using command name with --sendmail-cmd' '
clean_fake_sendmail &&
- PATH="$(pwd):$PATH" \
+ PATH="$PWD:$PATH" \
git send-email \
--from="Example <nobody@example.com>" \
--to=nobody@example.com \
@@ -2227,6 +2242,51 @@ test_expect_success $PREREQ 'test shell expression with --sendmail-cmd' '
test_path_is_file commandline1
'
+test_expect_success $PREREQ 'set up in-reply-to/references patches' '
+ cat >has-reply.patch <<-\EOF &&
+ From: A U Thor <author@example.com>
+ Subject: patch with in-reply-to
+ Message-ID: <patch.with.in.reply.to@example.com>
+ In-Reply-To: <replied.to@example.com>
+ References: <replied.to@example.com>
+
+ This is the body.
+ EOF
+ cat >no-reply.patch <<-\EOF
+ From: A U Thor <author@example.com>
+ Subject: patch without in-reply-to
+ Message-ID: <patch.without.in.reply.to@example.com>
+
+ This is the body.
+ EOF
+'
+
+test_expect_success $PREREQ 'patch reply headers correct with --no-thread' '
+ clean_fake_sendmail &&
+ git send-email \
+ --no-thread \
+ --to=nobody@example.com \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ has-reply.patch no-reply.patch &&
+ grep "In-Reply-To: <replied.to@example.com>" msgtxt1 &&
+ grep "References: <replied.to@example.com>" msgtxt1 &&
+ ! grep replied.to@example.com msgtxt2
+'
+
+test_expect_success $PREREQ 'cmdline in-reply-to used with --no-thread' '
+ clean_fake_sendmail &&
+ git send-email \
+ --no-thread \
+ --in-reply-to="<cmdline.reply@example.com>" \
+ --to=nobody@example.com \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ has-reply.patch no-reply.patch &&
+ grep "In-Reply-To: <cmdline.reply@example.com>" msgtxt1 &&
+ grep "References: <cmdline.reply@example.com>" msgtxt1 &&
+ grep "In-Reply-To: <cmdline.reply@example.com>" msgtxt2 &&
+ grep "References: <cmdline.reply@example.com>" msgtxt2
+'
+
test_expect_success $PREREQ 'invoke hook' '
mkdir -p .git/hooks &&
diff --git a/t/t9002-column.sh b/t/t9002-column.sh
index 89983527b6..6d3dbde3fe 100755
--- a/t/t9002-column.sh
+++ b/t/t9002-column.sh
@@ -42,6 +42,24 @@ EOF
test_cmp expected actual
'
+test_expect_success '--nl' '
+ cat >expected <<\EOF &&
+oneZ
+twoZ
+threeZ
+fourZ
+fiveZ
+sixZ
+sevenZ
+eightZ
+nineZ
+tenZ
+elevenZ
+EOF
+ git column --nl="Z$LF" --mode=plain <lista >actual &&
+ test_cmp expected actual
+'
+
test_expect_success '80 columns' '
cat >expected <<\EOF &&
one two three four five six seven eight nine ten eleven
diff --git a/t/t9351-fast-export-anonymize.sh b/t/t9351-fast-export-anonymize.sh
index 1c6e6fcdaf..77047e250d 100755
--- a/t/t9351-fast-export-anonymize.sh
+++ b/t/t9351-fast-export-anonymize.sh
@@ -18,7 +18,8 @@ test_expect_success 'setup simple repo' '
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
+ git tag -m "annotated tag" mytag &&
+ git tag -m "annotated tag with long message" longtag
'
test_expect_success 'export anonymized stream' '
@@ -55,7 +56,8 @@ test_expect_success 'stream retains other as refname' '
test_expect_success 'stream omits other refnames' '
! grep main stream &&
- ! grep mytag stream
+ ! grep mytag stream &&
+ ! grep longtag stream
'
test_expect_success 'stream omits identities' '
@@ -118,9 +120,9 @@ test_expect_success 'identical gitlinks got identical oid' '
test_line_count = 1 commits
'
-test_expect_success 'tag points to branch tip' '
+test_expect_success 'all tags point to branch tip' '
git rev-parse $other_branch >expect &&
- git for-each-ref --format="%(*objectname)" | grep . >actual &&
+ git for-each-ref --format="%(*objectname)" | grep . | uniq >actual &&
test_cmp expect actual
'
diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh
index 2d29d486ee..17f988edd2 100755
--- a/t/t9400-git-cvsserver-server.sh
+++ b/t/t9400-git-cvsserver-server.sh
@@ -36,6 +36,13 @@ CVSWORK="$PWD/cvswork"
CVS_SERVER=git-cvsserver
export CVSROOT CVS_SERVER
+if perl -e 'exit(1) if not defined crypt("", "cv")'
+then
+ PWDHASH='lac2ItudM3.KM'
+else
+ PWDHASH='$2b$10$t8fGvE/a9eLmfOLzsZme2uOa2QtoMYwIxq9wZA6aBKtF1Yb7FJIzi'
+fi
+
rm -rf "$CVSWORK" "$SERVERDIR"
test_expect_success 'setup' '
git config push.default matching &&
@@ -54,7 +61,7 @@ test_expect_success 'setup' '
GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true &&
GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" &&
GIT_DIR="$SERVERDIR" git config gitcvs.authdb "$SERVERDIR/auth.db" &&
- echo cvsuser:cvGVEarMLnhlA > "$SERVERDIR/auth.db"
+ echo "cvsuser:$PWDHASH" >"$SERVERDIR/auth.db"
'
# note that cvs doesn't accept absolute pathnames
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 11573936d5..5decc3b269 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -540,6 +540,15 @@ test_expect_success '__gitcomp - expand/narrow all negative options' '
EOF
'
+test_expect_success '__gitcomp - equal skip' '
+ test_gitcomp "--option=" "--option=" <<-\EOF &&
+
+ EOF
+ test_gitcomp "option=" "option=" <<-\EOF
+
+ EOF
+'
+
test_expect_success '__gitcomp - doesnt fail because of invalid variable name' '
__gitcomp "$invalid_variable_name"
'
@@ -2380,6 +2389,12 @@ test_expect_success 'git clone --config= - value' '
EOF
'
+test_expect_success 'options with value' '
+ test_completion "git merge -X diff-algorithm=" <<-\EOF
+
+ EOF
+'
+
test_expect_success 'sourcing the completion script clears cached commands' '
__git_compute_all_commands &&
verbose test -n "$__git_all_commands" &&
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index e28411bb75..eef2262a36 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -137,33 +137,110 @@ test_tick () {
# Stop execution and start a shell. This is useful for debugging tests.
#
# Be sure to remove all invocations of this command before submitting.
+# WARNING: the shell invoked by this helper does not have the same environment
+# as the one running the tests (shell variables and functions are not
+# available, and the options below further modify the environment). As such,
+# commands copied from a test script might behave differently than when
+# running the test.
+#
+# Usage: test_pause [options]
+# -t
+# Use your original TERM instead of test-lib.sh's "dumb".
+# This usually restores color output in the invoked shell.
+# -s
+# Invoke $SHELL instead of $TEST_SHELL_PATH.
+# -h
+# Use your original HOME instead of test-lib.sh's "$TRASH_DIRECTORY".
+# This allows you to use your regular shell environment and Git aliases.
+# CAUTION: running commands copied from a test script into the paused shell
+# might result in files in your HOME being overwritten.
+# -a
+# Shortcut for -t -s -h
test_pause () {
- "$SHELL_PATH" <&6 >&5 2>&7
+ PAUSE_TERM=$TERM &&
+ PAUSE_SHELL=$TEST_SHELL_PATH &&
+ PAUSE_HOME=$HOME &&
+ while test $# != 0
+ do
+ case "$1" in
+ -t)
+ PAUSE_TERM="$USER_TERM"
+ ;;
+ -s)
+ PAUSE_SHELL="$SHELL"
+ ;;
+ -h)
+ PAUSE_HOME="$USER_HOME"
+ ;;
+ -a)
+ PAUSE_TERM="$USER_TERM"
+ PAUSE_SHELL="$SHELL"
+ PAUSE_HOME="$USER_HOME"
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+ done &&
+ TERM="$PAUSE_TERM" HOME="$PAUSE_HOME" "$PAUSE_SHELL" <&6 >&5 2>&7
}
# Wrap git with a debugger. Adding this to a command can make it easier
# to understand what is going on in a failing test.
#
+# Usage: debug [options] <git command>
+# -d <debugger>
+# --debugger=<debugger>
+# Use <debugger> instead of GDB
+# -t
+# Use your original TERM instead of test-lib.sh's "dumb".
+# This usually restores color output in the debugger.
+# WARNING: the command being debugged might behave differently than when
+# running the test.
+#
# Examples:
# debug git checkout master
# debug --debugger=nemiver git $ARGS
# debug -d "valgrind --tool=memcheck --track-origins=yes" git $ARGS
debug () {
- case "$1" in
- -d)
- GIT_DEBUGGER="$2" &&
- shift 2
- ;;
- --debugger=*)
- GIT_DEBUGGER="${1#*=}" &&
- shift 1
- ;;
- *)
- GIT_DEBUGGER=1
- ;;
- esac &&
- GIT_DEBUGGER="${GIT_DEBUGGER}" "$@" <&6 >&5 2>&7
+ GIT_DEBUGGER=1 &&
+ DEBUG_TERM=$TERM &&
+ while test $# != 0
+ do
+ case "$1" in
+ -t)
+ DEBUG_TERM="$USER_TERM"
+ ;;
+ -d)
+ GIT_DEBUGGER="$2" &&
+ shift
+ ;;
+ --debugger=*)
+ GIT_DEBUGGER="${1#*=}"
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+ done &&
+
+ dotfiles=".gdbinit .lldbinit"
+
+ for dotfile in $dotfiles
+ do
+ dotfile="$USER_HOME/$dotfile" &&
+ test -f "$dotfile" && cp "$dotfile" "$HOME" || :
+ done &&
+
+ TERM="$DEBUG_TERM" GIT_DEBUGGER="${GIT_DEBUGGER}" "$@" <&6 >&5 2>&7 &&
+
+ for dotfile in $dotfiles
+ do
+ rm -f "$HOME/$dotfile"
+ done
}
# Usage: test_commit [options] <message> [<file> [<contents> [<tag>]]]
diff --git a/t/test-lib.sh b/t/test-lib.sh
index abcfbed6d6..151da80c56 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -534,7 +534,7 @@ SQ=\'
# when case-folding filenames
u200c=$(printf '\342\200\214')
-export _x05 _x35 _x40 _z40 LF u200c EMPTY_TREE EMPTY_BLOB ZERO_OID OID_REGEX
+export _x05 _x35 LF u200c EMPTY_TREE EMPTY_BLOB ZERO_OID OID_REGEX
# Each test should start with something like this, after copyright notices:
#
@@ -585,8 +585,9 @@ else
}
fi
+USER_TERM="$TERM"
TERM=dumb
-export TERM
+export TERM USER_TERM
error () {
say_color error "error: $*"
@@ -1343,7 +1344,8 @@ fi
GIT_TEMPLATE_DIR="$GIT_BUILD_DIR"/templates/blt
GIT_CONFIG_NOSYSTEM=1
GIT_ATTR_NOSYSTEM=1
-export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_ATTR_NOSYSTEM
+GIT_CEILING_DIRECTORIES="$TRASH_DIRECTORY/.."
+export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_ATTR_NOSYSTEM GIT_CEILING_DIRECTORIES
if test -z "$GIT_TEST_CMP"
then
@@ -1379,10 +1381,31 @@ then
test_done
fi
+# skip non-whitelisted tests when compiled with SANITIZE=leak
+if test -n "$SANITIZE_LEAK"
+then
+ if test_bool_env GIT_TEST_PASSING_SANITIZE_LEAK false
+ then
+ # We need to see it in "git env--helper" (via
+ # test_bool_env)
+ export TEST_PASSES_SANITIZE_LEAK
+
+ if ! test_bool_env TEST_PASSES_SANITIZE_LEAK false
+ then
+ skip_all="skipping $this_test under GIT_TEST_PASSING_SANITIZE_LEAK=true"
+ test_done
+ fi
+ fi
+elif test_bool_env GIT_TEST_PASSING_SANITIZE_LEAK false
+then
+ error "GIT_TEST_PASSING_SANITIZE_LEAK=true has no effect except when compiled with SANITIZE=leak"
+fi
+
# Last-minute variable setup
+USER_HOME="$HOME"
HOME="$TRASH_DIRECTORY"
GNUPGHOME="$HOME/gnupg-home-not-used"
-export HOME GNUPGHOME
+export HOME GNUPGHOME USER_HOME
# Test repository
rm -fr "$TRASH_DIRECTORY" || {
@@ -1422,10 +1445,9 @@ then
fi
# Convenience
-# A regexp to match 5, 35 and 40 hexdigits
+# A regexp to match 5 and 35 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
@@ -1434,7 +1456,6 @@ 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
@@ -1533,6 +1554,7 @@ test -z "$NO_PYTHON" && test_set_prereq PYTHON
test -n "$USE_LIBPCRE2" && test_set_prereq PCRE
test -n "$USE_LIBPCRE2" && test_set_prereq LIBPCRE2
test -z "$NO_GETTEXT" && test_set_prereq GETTEXT
+test -n "$SANITIZE_LEAK" && test_set_prereq SANITIZE_LEAK
if test -z "$GIT_TEST_CHECK_CACHE_TREE"
then
@@ -1708,10 +1730,6 @@ test_lazy_prereq SHA1 '
esac
'
-test_lazy_prereq REBASE_P '
- test -z "$GIT_TEST_SKIP_REBASE_P"
-'
-
# Ensure that no test accidentally triggers a Git command
# that runs the actual maintenance scheduler, affecting a user's
# system permanently.
diff --git a/trace.h b/trace.h
index 0dbbad0e41..e25984051a 100644
--- a/trace.h
+++ b/trace.h
@@ -89,7 +89,7 @@ struct trace_key {
extern struct trace_key trace_default_key;
-#define TRACE_KEY_INIT(name) { "GIT_TRACE_" #name, 0, 0, 0 }
+#define TRACE_KEY_INIT(name) { .key = "GIT_TRACE_" #name }
extern struct trace_key trace_perf_key;
extern struct trace_key trace_setup_key;
diff --git a/trace2.c b/trace2.c
index 256120c7fd..b2d471526f 100644
--- a/trace2.c
+++ b/trace2.c
@@ -260,6 +260,19 @@ void trace2_cmd_path_fl(const char *file, int line, const char *pathname)
tgt_j->pfn_command_path_fl(file, line, pathname);
}
+void trace2_cmd_ancestry_fl(const char *file, int line, const char **parent_names)
+{
+ struct tr2_tgt *tgt_j;
+ int j;
+
+ if (!trace2_enabled)
+ return;
+
+ for_each_wanted_builtin (j, tgt_j)
+ if (tgt_j->pfn_command_ancestry_fl)
+ tgt_j->pfn_command_ancestry_fl(file, line, parent_names);
+}
+
void trace2_cmd_name_fl(const char *file, int line, const char *name)
{
struct tr2_tgt *tgt_j;
@@ -381,6 +394,37 @@ void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd,
us_elapsed_child);
}
+void trace2_child_ready_fl(const char *file, int line,
+ struct child_process *cmd,
+ const char *ready)
+{
+ struct tr2_tgt *tgt_j;
+ int j;
+ uint64_t us_now;
+ uint64_t us_elapsed_absolute;
+ uint64_t us_elapsed_child;
+
+ if (!trace2_enabled)
+ return;
+
+ us_now = getnanotime() / 1000;
+ us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
+
+ if (cmd->trace2_child_us_start)
+ us_elapsed_child = us_now - cmd->trace2_child_us_start;
+ else
+ us_elapsed_child = 0;
+
+ for_each_wanted_builtin (j, tgt_j)
+ if (tgt_j->pfn_child_ready_fl)
+ tgt_j->pfn_child_ready_fl(file, line,
+ us_elapsed_absolute,
+ cmd->trace2_child_id,
+ cmd->pid,
+ ready,
+ us_elapsed_child);
+}
+
int trace2_exec_fl(const char *file, int line, const char *exe,
const char **argv)
{
diff --git a/trace2.h b/trace2.h
index ede18c2e06..0cc7b5f531 100644
--- a/trace2.h
+++ b/trace2.h
@@ -134,6 +134,16 @@ void trace2_cmd_path_fl(const char *file, int line, const char *pathname);
#define trace2_cmd_path(p) trace2_cmd_path_fl(__FILE__, __LINE__, (p))
/*
+ * Emit an 'ancestry' event with the process name of the current process's
+ * parent process.
+ * This gives post-processors a way to determine what invoked the command and
+ * learn more about usage patterns.
+ */
+void trace2_cmd_ancestry_fl(const char *file, int line, const char **parent_names);
+
+#define trace2_cmd_ancestry(v) trace2_cmd_ancestry_fl(__FILE__, __LINE__, (v))
+
+/*
* Emit a 'cmd_name' event with the canonical name of the command.
* This gives post-processors a simple field to identify the command
* without having to parse the argv.
@@ -244,6 +254,31 @@ void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd,
trace2_child_exit_fl(__FILE__, __LINE__, (cmd), (code))
/**
+ * Emits a "child_ready" message containing the "child-id" and a flag
+ * indicating whether the child was considered "ready" when we
+ * released it.
+ *
+ * This function should be called after starting a daemon process in
+ * the background (and after giving it sufficient time to boot
+ * up) to indicate that we no longer control or own it.
+ *
+ * The "ready" argument should contain one of { "ready", "timeout",
+ * "error" } to indicate the state of the running daemon when we
+ * released it.
+ *
+ * If the daemon process fails to start or it exits or is terminated
+ * while we are still waiting for it, the caller should emit a
+ * regular "child_exit" to report the normal process exit information.
+ *
+ */
+void trace2_child_ready_fl(const char *file, int line,
+ struct child_process *cmd,
+ const char *ready);
+
+#define trace2_child_ready(cmd, ready) \
+ trace2_child_ready_fl(__FILE__, __LINE__, (cmd), (ready))
+
+/**
* Emit an 'exec' event prior to calling one of exec(), execv(),
* execvp(), and etc. On Unix-derived systems, this will be the
* last event emitted for the current process, unless the exec
@@ -340,7 +375,7 @@ void trace2_def_repo_fl(const char *file, int line, struct repository *repo);
* being started, such as "read_recursive" or "do_read_index".
*
* The `repo` field, if set, will be used to get the "repo-id", so that
- * recursive oerations can be attributed to the correct repository.
+ * recursive operations can be attributed to the correct repository.
*/
void trace2_region_enter_fl(const char *file, int line, const char *category,
const char *label, const struct repository *repo, ...);
@@ -492,13 +527,7 @@ enum trace2_process_info_reason {
TRACE2_PROCESS_INFO_EXIT,
};
-#if defined(GIT_WINDOWS_NATIVE)
void trace2_collect_process_info(enum trace2_process_info_reason reason);
-#else
-#define trace2_collect_process_info(reason) \
- do { \
- } while (0)
-#endif
const char *trace2_session_id(void);
diff --git a/trace2/tr2_tgt.h b/trace2/tr2_tgt.h
index 7b90469212..65f94e1574 100644
--- a/trace2/tr2_tgt.h
+++ b/trace2/tr2_tgt.h
@@ -27,6 +27,8 @@ typedef void(tr2_tgt_evt_error_va_fl_t)(const char *file, int line,
typedef void(tr2_tgt_evt_command_path_fl_t)(const char *file, int line,
const char *command_path);
+typedef void(tr2_tgt_evt_command_ancestry_fl_t)(const char *file, int line,
+ const char **parent_names);
typedef void(tr2_tgt_evt_command_name_fl_t)(const char *file, int line,
const char *name,
const char *hierarchy);
@@ -43,6 +45,10 @@ typedef void(tr2_tgt_evt_child_exit_fl_t)(const char *file, int line,
uint64_t us_elapsed_absolute, int cid,
int pid, int code,
uint64_t us_elapsed_child);
+typedef void(tr2_tgt_evt_child_ready_fl_t)(const char *file, int line,
+ uint64_t us_elapsed_absolute,
+ int cid, int pid, const char *ready,
+ uint64_t us_elapsed_child);
typedef void(tr2_tgt_evt_thread_start_fl_t)(const char *file, int line,
uint64_t us_elapsed_absolute);
@@ -108,11 +114,13 @@ struct tr2_tgt {
tr2_tgt_evt_atexit_t *pfn_atexit;
tr2_tgt_evt_error_va_fl_t *pfn_error_va_fl;
tr2_tgt_evt_command_path_fl_t *pfn_command_path_fl;
+ tr2_tgt_evt_command_ancestry_fl_t *pfn_command_ancestry_fl;
tr2_tgt_evt_command_name_fl_t *pfn_command_name_fl;
tr2_tgt_evt_command_mode_fl_t *pfn_command_mode_fl;
tr2_tgt_evt_alias_fl_t *pfn_alias_fl;
tr2_tgt_evt_child_start_fl_t *pfn_child_start_fl;
tr2_tgt_evt_child_exit_fl_t *pfn_child_exit_fl;
+ tr2_tgt_evt_child_ready_fl_t *pfn_child_ready_fl;
tr2_tgt_evt_thread_start_fl_t *pfn_thread_start_fl;
tr2_tgt_evt_thread_exit_fl_t *pfn_thread_exit_fl;
tr2_tgt_evt_exec_fl_t *pfn_exec_fl;
diff --git a/trace2/tr2_tgt_event.c b/trace2/tr2_tgt_event.c
index 6353e8ad91..70cfc2f77c 100644
--- a/trace2/tr2_tgt_event.c
+++ b/trace2/tr2_tgt_event.c
@@ -261,6 +261,26 @@ static void fn_command_path_fl(const char *file, int line, const char *pathname)
jw_release(&jw);
}
+static void fn_command_ancestry_fl(const char *file, int line, const char **parent_names)
+{
+ const char *event_name = "cmd_ancestry";
+ const char *parent_name = NULL;
+ struct json_writer jw = JSON_WRITER_INIT;
+
+ jw_object_begin(&jw, 0);
+ event_fmt_prepare(event_name, file, line, NULL, &jw);
+ jw_object_inline_begin_array(&jw, "ancestry");
+
+ while ((parent_name = *parent_names++))
+ jw_array_string(&jw, parent_name);
+
+ jw_end(&jw); /* 'ancestry' array */
+ jw_end(&jw); /* event object */
+
+ tr2_dst_write_line(&tr2dst_event, &jw.json);
+ jw_release(&jw);
+}
+
static void fn_command_name_fl(const char *file, int line, const char *name,
const char *hierarchy)
{
@@ -363,6 +383,27 @@ static void fn_child_exit_fl(const char *file, int line,
jw_release(&jw);
}
+static void fn_child_ready_fl(const char *file, int line,
+ uint64_t us_elapsed_absolute, int cid, int pid,
+ const char *ready, uint64_t us_elapsed_child)
+{
+ const char *event_name = "child_ready";
+ struct json_writer jw = JSON_WRITER_INIT;
+ double t_rel = (double)us_elapsed_child / 1000000.0;
+
+ jw_object_begin(&jw, 0);
+ event_fmt_prepare(event_name, file, line, NULL, &jw);
+ jw_object_intmax(&jw, "child_id", cid);
+ jw_object_intmax(&jw, "pid", pid);
+ jw_object_string(&jw, "ready", ready);
+ jw_object_double(&jw, "t_rel", 6, t_rel);
+ jw_end(&jw);
+
+ tr2_dst_write_line(&tr2dst_event, &jw.json);
+
+ jw_release(&jw);
+}
+
static void fn_thread_start_fl(const char *file, int line,
uint64_t us_elapsed_absolute)
{
@@ -584,11 +625,13 @@ struct tr2_tgt tr2_tgt_event = {
fn_atexit,
fn_error_va_fl,
fn_command_path_fl,
+ fn_command_ancestry_fl,
fn_command_name_fl,
fn_command_mode_fl,
fn_alias_fl,
fn_child_start_fl,
fn_child_exit_fl,
+ fn_child_ready_fl,
fn_thread_start_fl,
fn_thread_exit_fl,
fn_exec_fl,
diff --git a/trace2/tr2_tgt_normal.c b/trace2/tr2_tgt_normal.c
index 31b602c171..58d9e430f0 100644
--- a/trace2/tr2_tgt_normal.c
+++ b/trace2/tr2_tgt_normal.c
@@ -160,6 +160,24 @@ static void fn_command_path_fl(const char *file, int line, const char *pathname)
strbuf_release(&buf_payload);
}
+static void fn_command_ancestry_fl(const char *file, int line, const char **parent_names)
+{
+ const char *parent_name = NULL;
+ struct strbuf buf_payload = STRBUF_INIT;
+
+ /* cmd_ancestry parent <- grandparent <- great-grandparent */
+ strbuf_addstr(&buf_payload, "cmd_ancestry ");
+ while ((parent_name = *parent_names++)) {
+ strbuf_addstr(&buf_payload, parent_name);
+ /* if we'll write another one after this, add a delimiter */
+ if (parent_names && *parent_names)
+ strbuf_addstr(&buf_payload, " <- ");
+ }
+
+ normal_io_write_fl(file, line, &buf_payload);
+ strbuf_release(&buf_payload);
+}
+
static void fn_command_name_fl(const char *file, int line, const char *name,
const char *hierarchy)
{
@@ -233,6 +251,19 @@ static void fn_child_exit_fl(const char *file, int line,
strbuf_release(&buf_payload);
}
+static void fn_child_ready_fl(const char *file, int line,
+ uint64_t us_elapsed_absolute, int cid, int pid,
+ const char *ready, uint64_t us_elapsed_child)
+{
+ struct strbuf buf_payload = STRBUF_INIT;
+ double elapsed = (double)us_elapsed_child / 1000000.0;
+
+ strbuf_addf(&buf_payload, "child_ready[%d] pid:%d ready:%s elapsed:%.6f",
+ cid, pid, ready, elapsed);
+ normal_io_write_fl(file, line, &buf_payload);
+ strbuf_release(&buf_payload);
+}
+
static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,
int exec_id, const char *exe, const char **argv)
{
@@ -306,11 +337,13 @@ struct tr2_tgt tr2_tgt_normal = {
fn_atexit,
fn_error_va_fl,
fn_command_path_fl,
+ fn_command_ancestry_fl,
fn_command_name_fl,
fn_command_mode_fl,
fn_alias_fl,
fn_child_start_fl,
fn_child_exit_fl,
+ fn_child_ready_fl,
NULL, /* thread_start */
NULL, /* thread_exit */
fn_exec_fl,
diff --git a/trace2/tr2_tgt_perf.c b/trace2/tr2_tgt_perf.c
index a8018f18cc..e4acca13d6 100644
--- a/trace2/tr2_tgt_perf.c
+++ b/trace2/tr2_tgt_perf.c
@@ -253,6 +253,21 @@ static void fn_command_path_fl(const char *file, int line, const char *pathname)
strbuf_release(&buf_payload);
}
+static void fn_command_ancestry_fl(const char *file, int line, const char **parent_names)
+{
+ const char *event_name = "cmd_ancestry";
+ struct strbuf buf_payload = STRBUF_INIT;
+
+ strbuf_addstr(&buf_payload, "ancestry:[");
+ /* It's not an argv but the rules are basically the same. */
+ sq_append_quote_argv_pretty(&buf_payload, parent_names);
+ strbuf_addch(&buf_payload, ']');
+
+ perf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,
+ &buf_payload);
+ strbuf_release(&buf_payload);
+}
+
static void fn_command_name_fl(const char *file, int line, const char *name,
const char *hierarchy)
{
@@ -345,6 +360,20 @@ static void fn_child_exit_fl(const char *file, int line,
strbuf_release(&buf_payload);
}
+static void fn_child_ready_fl(const char *file, int line,
+ uint64_t us_elapsed_absolute, int cid, int pid,
+ const char *ready, uint64_t us_elapsed_child)
+{
+ const char *event_name = "child_ready";
+ struct strbuf buf_payload = STRBUF_INIT;
+
+ strbuf_addf(&buf_payload, "[ch%d] pid:%d ready:%s", cid, pid, ready);
+
+ perf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,
+ &us_elapsed_child, NULL, &buf_payload);
+ strbuf_release(&buf_payload);
+}
+
static void fn_thread_start_fl(const char *file, int line,
uint64_t us_elapsed_absolute)
{
@@ -532,11 +561,13 @@ struct tr2_tgt tr2_tgt_perf = {
fn_atexit,
fn_error_va_fl,
fn_command_path_fl,
+ fn_command_ancestry_fl,
fn_command_name_fl,
fn_command_mode_fl,
fn_alias_fl,
fn_child_start_fl,
fn_child_exit_fl,
+ fn_child_ready_fl,
fn_thread_start_fl,
fn_thread_exit_fl,
fn_exec_fl,
diff --git a/trace2/tr2_tls.c b/trace2/tr2_tls.c
index 067c23755f..7da94aba52 100644
--- a/trace2/tr2_tls.c
+++ b/trace2/tr2_tls.c
@@ -95,6 +95,7 @@ void tr2tls_unset_self(void)
pthread_setspecific(tr2tls_key, NULL);
+ strbuf_release(&ctx->thread_name);
free(ctx->array_us_start);
free(ctx);
}
diff --git a/transport-helper.c b/transport-helper.c
index 4be035edb8..e8dbdd1153 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -671,8 +671,8 @@ static int connect_helper(struct transport *transport, const char *name,
static struct ref *get_refs_list_using_list(struct transport *transport,
int for_push);
-static int fetch(struct transport *transport,
- int nr_heads, struct ref **to_fetch)
+static int fetch_refs(struct transport *transport,
+ int nr_heads, struct ref **to_fetch)
{
struct helper_data *data = transport->data;
int i, count;
@@ -681,7 +681,7 @@ static int fetch(struct transport *transport,
if (process_connect(transport, 0)) {
do_take_over(transport);
- return transport->vtable->fetch(transport, nr_heads, to_fetch);
+ return transport->vtable->fetch_refs(transport, nr_heads, to_fetch);
}
/*
@@ -1261,12 +1261,12 @@ static struct ref *get_refs_list_using_list(struct transport *transport,
}
static struct transport_vtable vtable = {
- set_helper_option,
- get_refs_list,
- fetch,
- push_refs,
- connect_helper,
- release_helper
+ .set_option = set_helper_option,
+ .get_refs_list = get_refs_list,
+ .fetch_refs = fetch_refs,
+ .push_refs = push_refs,
+ .connect = connect_helper,
+ .disconnect = release_helper
};
int transport_helper_init(struct transport *transport, const char *name)
diff --git a/transport-internal.h b/transport-internal.h
index b60f1ba907..c4ca0b733a 100644
--- a/transport-internal.h
+++ b/transport-internal.h
@@ -34,7 +34,7 @@ struct transport_vtable {
* get_refs_list(), it should set the old_sha1 fields in the
* provided refs now.
**/
- int (*fetch)(struct transport *transport, int refs_nr, struct ref **refs);
+ int (*fetch_refs)(struct transport *transport, int refs_nr, struct ref **refs);
/**
* Push the objects and refs. Send the necessary objects, and
diff --git a/transport.c b/transport.c
index 17e9629710..e4f1decae2 100644
--- a/transport.c
+++ b/transport.c
@@ -1,7 +1,7 @@
#include "cache.h"
#include "config.h"
#include "transport.h"
-#include "run-command.h"
+#include "hook.h"
#include "pkt-line.h"
#include "fetch-pack.h"
#include "remote.h"
@@ -162,12 +162,16 @@ static int fetch_refs_from_bundle(struct transport *transport,
int nr_heads, struct ref **to_fetch)
{
struct bundle_transport_data *data = transport->data;
+ struct strvec extra_index_pack_args = STRVEC_INIT;
int ret;
+ if (transport->progress)
+ strvec_push(&extra_index_pack_args, "-v");
+
if (!data->get_refs_from_bundle_called)
get_refs_from_bundle(transport, 0, NULL);
ret = unbundle(the_repository, &data->header, data->fd,
- transport->progress ? BUNDLE_VERBOSE : 0);
+ &extra_index_pack_args);
transport->hash_algo = data->header.hash_algo;
return ret;
}
@@ -883,12 +887,10 @@ static int disconnect_git(struct transport *transport)
}
static struct transport_vtable taken_over_vtable = {
- NULL,
- get_refs_via_connect,
- fetch_refs_via_pack,
- git_transport_push,
- NULL,
- disconnect_git
+ .get_refs_list = get_refs_via_connect,
+ .fetch_refs = fetch_refs_via_pack,
+ .push_refs = git_transport_push,
+ .disconnect = disconnect_git
};
void transport_take_over(struct transport *transport,
@@ -1032,21 +1034,17 @@ void transport_check_allowed(const char *type)
}
static struct transport_vtable bundle_vtable = {
- NULL,
- get_refs_from_bundle,
- fetch_refs_from_bundle,
- NULL,
- NULL,
- close_bundle
+ .get_refs_list = get_refs_from_bundle,
+ .fetch_refs = fetch_refs_from_bundle,
+ .disconnect = close_bundle
};
static struct transport_vtable builtin_smart_vtable = {
- NULL,
- get_refs_via_connect,
- fetch_refs_via_pack,
- git_transport_push,
- connect_git,
- disconnect_git
+ .get_refs_list = get_refs_via_connect,
+ .fetch_refs = fetch_refs_via_pack,
+ .push_refs = git_transport_push,
+ .connect = connect_git,
+ .disconnect = disconnect_git
};
struct transport *transport_get(struct remote *remote, const char *url)
@@ -1453,7 +1451,7 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs)
heads[nr_heads++] = rm;
}
- rc = transport->vtable->fetch(transport, nr_heads, heads);
+ rc = transport->vtable->fetch_refs(transport, nr_heads, heads);
free(heads);
return rc;
diff --git a/transport.h b/transport.h
index 1cbab11373..8bb4c8bbc8 100644
--- a/transport.h
+++ b/transport.h
@@ -262,7 +262,9 @@ struct transport_ls_refs_options {
*/
char *unborn_head_target;
};
-#define TRANSPORT_LS_REFS_OPTIONS_INIT { STRVEC_INIT }
+#define TRANSPORT_LS_REFS_OPTIONS_INIT { \
+ .ref_prefixes = STRVEC_INIT, \
+}
/*
* Retrieve refs from a remote.
diff --git a/tree-diff.c b/tree-diff.c
index 1572615bd9..437c98a70e 100644
--- a/tree-diff.c
+++ b/tree-diff.c
@@ -21,7 +21,9 @@
ALLOC_ARRAY((x), nr); \
} while(0)
#define FAST_ARRAY_FREE(x, nr) do { \
- if ((nr) > 2) \
+ if ((nr) <= 2) \
+ xalloca_free((x)); \
+ else \
free((x)); \
} while(0)
diff --git a/unicode-width.h b/unicode-width.h
index b50e686bae..97c851b27d 100644
--- a/unicode-width.h
+++ b/unicode-width.h
@@ -26,7 +26,9 @@ static const struct interval zero_width[] = {
{ 0x0825, 0x0827 },
{ 0x0829, 0x082D },
{ 0x0859, 0x085B },
-{ 0x08D3, 0x0902 },
+{ 0x0890, 0x0891 },
+{ 0x0898, 0x089F },
+{ 0x08CA, 0x0902 },
{ 0x093A, 0x093A },
{ 0x093C, 0x093C },
{ 0x0941, 0x0948 },
@@ -66,6 +68,7 @@ static const struct interval zero_width[] = {
{ 0x0BCD, 0x0BCD },
{ 0x0C00, 0x0C00 },
{ 0x0C04, 0x0C04 },
+{ 0x0C3C, 0x0C3C },
{ 0x0C3E, 0x0C40 },
{ 0x0C46, 0x0C48 },
{ 0x0C4A, 0x0C4D },
@@ -116,7 +119,7 @@ static const struct interval zero_width[] = {
{ 0x1160, 0x11FF },
{ 0x135D, 0x135F },
{ 0x1712, 0x1714 },
-{ 0x1732, 0x1734 },
+{ 0x1732, 0x1733 },
{ 0x1752, 0x1753 },
{ 0x1772, 0x1773 },
{ 0x17B4, 0x17B5 },
@@ -124,7 +127,7 @@ static const struct interval zero_width[] = {
{ 0x17C6, 0x17C6 },
{ 0x17C9, 0x17D3 },
{ 0x17DD, 0x17DD },
-{ 0x180B, 0x180E },
+{ 0x180B, 0x180F },
{ 0x1885, 0x1886 },
{ 0x18A9, 0x18A9 },
{ 0x1920, 0x1922 },
@@ -140,7 +143,7 @@ static const struct interval zero_width[] = {
{ 0x1A65, 0x1A6C },
{ 0x1A73, 0x1A7C },
{ 0x1A7F, 0x1A7F },
-{ 0x1AB0, 0x1AC0 },
+{ 0x1AB0, 0x1ACE },
{ 0x1B00, 0x1B03 },
{ 0x1B34, 0x1B34 },
{ 0x1B36, 0x1B3A },
@@ -163,8 +166,7 @@ static const struct interval zero_width[] = {
{ 0x1CED, 0x1CED },
{ 0x1CF4, 0x1CF4 },
{ 0x1CF8, 0x1CF9 },
-{ 0x1DC0, 0x1DF9 },
-{ 0x1DFB, 0x1DFF },
+{ 0x1DC0, 0x1DFF },
{ 0x200B, 0x200F },
{ 0x202A, 0x202E },
{ 0x2060, 0x2064 },
@@ -227,12 +229,16 @@ static const struct interval zero_width[] = {
{ 0x10D24, 0x10D27 },
{ 0x10EAB, 0x10EAC },
{ 0x10F46, 0x10F50 },
+{ 0x10F82, 0x10F85 },
{ 0x11001, 0x11001 },
{ 0x11038, 0x11046 },
+{ 0x11070, 0x11070 },
+{ 0x11073, 0x11074 },
{ 0x1107F, 0x11081 },
{ 0x110B3, 0x110B6 },
{ 0x110B9, 0x110BA },
{ 0x110BD, 0x110BD },
+{ 0x110C2, 0x110C2 },
{ 0x110CD, 0x110CD },
{ 0x11100, 0x11102 },
{ 0x11127, 0x1112B },
@@ -315,6 +321,8 @@ static const struct interval zero_width[] = {
{ 0x16FE4, 0x16FE4 },
{ 0x1BC9D, 0x1BC9E },
{ 0x1BCA0, 0x1BCA3 },
+{ 0x1CF00, 0x1CF2D },
+{ 0x1CF30, 0x1CF46 },
{ 0x1D167, 0x1D169 },
{ 0x1D173, 0x1D182 },
{ 0x1D185, 0x1D18B },
@@ -332,6 +340,7 @@ static const struct interval zero_width[] = {
{ 0x1E023, 0x1E024 },
{ 0x1E026, 0x1E02A },
{ 0x1E130, 0x1E136 },
+{ 0x1E2AE, 0x1E2AE },
{ 0x1E2EC, 0x1E2EF },
{ 0x1E8D0, 0x1E8D6 },
{ 0x1E944, 0x1E94A },
@@ -404,7 +413,10 @@ static const struct interval double_width[] = {
{ 0x17000, 0x187F7 },
{ 0x18800, 0x18CD5 },
{ 0x18D00, 0x18D08 },
-{ 0x1B000, 0x1B11E },
+{ 0x1AFF0, 0x1AFF3 },
+{ 0x1AFF5, 0x1AFFB },
+{ 0x1AFFD, 0x1AFFE },
+{ 0x1B000, 0x1B122 },
{ 0x1B150, 0x1B152 },
{ 0x1B164, 0x1B167 },
{ 0x1B170, 0x1B2FB },
@@ -439,21 +451,23 @@ static const struct interval double_width[] = {
{ 0x1F6CC, 0x1F6CC },
{ 0x1F6D0, 0x1F6D2 },
{ 0x1F6D5, 0x1F6D7 },
+{ 0x1F6DD, 0x1F6DF },
{ 0x1F6EB, 0x1F6EC },
{ 0x1F6F4, 0x1F6FC },
{ 0x1F7E0, 0x1F7EB },
+{ 0x1F7F0, 0x1F7F0 },
{ 0x1F90C, 0x1F93A },
{ 0x1F93C, 0x1F945 },
-{ 0x1F947, 0x1F978 },
-{ 0x1F97A, 0x1F9CB },
-{ 0x1F9CD, 0x1F9FF },
+{ 0x1F947, 0x1F9FF },
{ 0x1FA70, 0x1FA74 },
-{ 0x1FA78, 0x1FA7A },
+{ 0x1FA78, 0x1FA7C },
{ 0x1FA80, 0x1FA86 },
-{ 0x1FA90, 0x1FAA8 },
-{ 0x1FAB0, 0x1FAB6 },
-{ 0x1FAC0, 0x1FAC2 },
-{ 0x1FAD0, 0x1FAD6 },
+{ 0x1FA90, 0x1FAAC },
+{ 0x1FAB0, 0x1FABA },
+{ 0x1FAC0, 0x1FAC5 },
+{ 0x1FAD0, 0x1FAD9 },
+{ 0x1FAE0, 0x1FAE7 },
+{ 0x1FAF0, 0x1FAF6 },
{ 0x20000, 0x2FFFD },
{ 0x30000, 0x3FFFD }
};
diff --git a/unpack-trees.c b/unpack-trees.c
index 5786645f31..a7e1712d23 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -111,17 +111,17 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
strvec_init(&opts->msgs_to_free);
if (!strcmp(cmd, "checkout"))
- msg = advice_commit_before_merge
+ msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
? _("Your local changes to the following files would be overwritten by checkout:\n%%s"
"Please commit your changes or stash them before you switch branches.")
: _("Your local changes to the following files would be overwritten by checkout:\n%%s");
else if (!strcmp(cmd, "merge"))
- msg = advice_commit_before_merge
+ msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
? _("Your local changes to the following files would be overwritten by merge:\n%%s"
"Please commit your changes or stash them before you merge.")
: _("Your local changes to the following files would be overwritten by merge:\n%%s");
else
- msg = advice_commit_before_merge
+ msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
? _("Your local changes to the following files would be overwritten by %s:\n%%s"
"Please commit your changes or stash them before you %s.")
: _("Your local changes to the following files would be overwritten by %s:\n%%s");
@@ -132,17 +132,17 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
_("Updating the following directories would lose untracked files in them:\n%s");
if (!strcmp(cmd, "checkout"))
- msg = advice_commit_before_merge
+ msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
? _("The following untracked working tree files would be removed by checkout:\n%%s"
"Please move or remove them before you switch branches.")
: _("The following untracked working tree files would be removed by checkout:\n%%s");
else if (!strcmp(cmd, "merge"))
- msg = advice_commit_before_merge
+ msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
? _("The following untracked working tree files would be removed by merge:\n%%s"
"Please move or remove them before you merge.")
: _("The following untracked working tree files would be removed by merge:\n%%s");
else
- msg = advice_commit_before_merge
+ msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
? _("The following untracked working tree files would be removed by %s:\n%%s"
"Please move or remove them before you %s.")
: _("The following untracked working tree files would be removed by %s:\n%%s");
@@ -150,17 +150,17 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
strvec_pushf(&opts->msgs_to_free, msg, cmd, cmd);
if (!strcmp(cmd, "checkout"))
- msg = advice_commit_before_merge
+ msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
? _("The following untracked working tree files would be overwritten by checkout:\n%%s"
"Please move or remove them before you switch branches.")
: _("The following untracked working tree files would be overwritten by checkout:\n%%s");
else if (!strcmp(cmd, "merge"))
- msg = advice_commit_before_merge
+ msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
? _("The following untracked working tree files would be overwritten by merge:\n%%s"
"Please move or remove them before you merge.")
: _("The following untracked working tree files would be overwritten by merge:\n%%s");
else
- msg = advice_commit_before_merge
+ msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
? _("The following untracked working tree files would be overwritten by %s:\n%%s"
"Please move or remove them before you %s.")
: _("The following untracked working tree files would be overwritten by %s:\n%%s");
@@ -479,7 +479,7 @@ static int check_updates(struct unpack_trees_options *o,
errs |= run_parallel_checkout(&state, pc_workers, pc_threshold,
progress, &cnt);
stop_progress(&progress);
- errs |= finish_delayed_checkout(&state, NULL);
+ errs |= finish_delayed_checkout(&state, NULL, o->verbose_update);
git_attr_set_direction(GIT_ATTR_CHECKIN);
if (o->clone)
@@ -1255,7 +1255,7 @@ static int sparse_dir_matches_path(const struct cache_entry *ce,
static struct cache_entry *find_cache_entry(struct traverse_info *info,
const struct name_entry *p)
{
- struct cache_entry *ce;
+ const char *path;
int pos = find_cache_pos(info, p->path, p->pathlen);
struct unpack_trees_options *o = info->data;
@@ -1281,9 +1281,11 @@ static struct cache_entry *find_cache_entry(struct traverse_info *info,
* paths (e.g. "subdir-").
*/
while (pos >= 0) {
- ce = o->src_index->cache[pos];
+ struct cache_entry *ce = o->src_index->cache[pos];
- if (strncmp(ce->name, p->path, p->pathlen))
+ if (!skip_prefix(ce->name, info->traverse_path, &path) ||
+ strncmp(path, p->path, p->pathlen) ||
+ path[p->pathlen] != '/')
return NULL;
if (S_ISSPARSEDIR(ce->ce_mode) &&
@@ -1692,9 +1694,15 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
static struct cache_entry *dfc;
struct pattern_list pl;
int free_pattern_list = 0;
+ struct dir_struct dir = DIR_INIT;
+
+ if (o->reset == UNPACK_RESET_INVALID)
+ BUG("o->reset had a value of 1; should be UNPACK_TREES_*_UNTRACKED");
if (len > MAX_UNPACK_TREES)
die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES);
+ if (o->dir)
+ BUG("o->dir is for internal use only");
trace_performance_enter();
trace2_region_enter("unpack_trees", "unpack_trees", the_repository);
@@ -1705,6 +1713,16 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
ensure_full_index(o->dst_index);
}
+ if (o->reset == UNPACK_RESET_OVERWRITE_UNTRACKED &&
+ o->preserve_ignored)
+ BUG("UNPACK_RESET_OVERWRITE_UNTRACKED incompatible with preserved ignored files");
+
+ if (!o->preserve_ignored) {
+ o->dir = &dir;
+ o->dir->flags |= DIR_SHOW_IGNORED;
+ setup_standard_excludes(o->dir);
+ }
+
if (!core_apply_sparse_checkout || !o->update)
o->skip_sparse_checkout = 1;
if (!o->skip_sparse_checkout && !o->pl) {
@@ -1866,6 +1884,10 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
done:
if (free_pattern_list)
clear_pattern_list(&pl);
+ if (o->dir) {
+ dir_clear(o->dir);
+ o->dir = NULL;
+ }
trace2_region_leave("unpack_trees", "unpack_trees", the_repository);
trace_performance_leave("unpack_trees");
return ret;
@@ -2156,9 +2178,15 @@ static int icase_exists(struct unpack_trees_options *o, const char *name, int le
return src && !ie_match_stat(o->src_index, src, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
}
+enum absent_checking_type {
+ COMPLETELY_ABSENT,
+ ABSENT_ANY_DIRECTORY
+};
+
static int check_ok_to_remove(const char *name, int len, int dtype,
const struct cache_entry *ce, struct stat *st,
enum unpack_trees_error_types error_type,
+ enum absent_checking_type absent_type,
struct unpack_trees_options *o)
{
const struct cache_entry *result;
@@ -2193,6 +2221,10 @@ static int check_ok_to_remove(const char *name, int len, int dtype,
return 0;
}
+ /* If we only care about directories, then we can remove */
+ if (absent_type == ABSENT_ANY_DIRECTORY)
+ return 0;
+
/*
* The previous round may already have decided to
* delete this path, which is in a subdirectory that
@@ -2213,12 +2245,14 @@ static int check_ok_to_remove(const char *name, int len, int dtype,
*/
static int verify_absent_1(const struct cache_entry *ce,
enum unpack_trees_error_types error_type,
+ enum absent_checking_type absent_type,
struct unpack_trees_options *o)
{
int len;
struct stat st;
- if (o->index_only || o->reset || !o->update)
+ if (o->index_only || !o->update ||
+ o->reset == UNPACK_RESET_OVERWRITE_UNTRACKED)
return 0;
len = check_leading_path(ce->name, ce_namelen(ce), 0);
@@ -2238,7 +2272,8 @@ static int verify_absent_1(const struct cache_entry *ce,
NULL, o);
else
ret = check_ok_to_remove(path, len, DT_UNKNOWN, NULL,
- &st, error_type, o);
+ &st, error_type,
+ absent_type, o);
}
free(path);
return ret;
@@ -2253,7 +2288,7 @@ static int verify_absent_1(const struct cache_entry *ce,
return check_ok_to_remove(ce->name, ce_namelen(ce),
ce_to_dtype(ce), ce, &st,
- error_type, o);
+ error_type, absent_type, o);
}
}
@@ -2263,14 +2298,23 @@ static int verify_absent(const struct cache_entry *ce,
{
if (!o->skip_sparse_checkout && (ce->ce_flags & CE_NEW_SKIP_WORKTREE))
return 0;
- return verify_absent_1(ce, error_type, o);
+ return verify_absent_1(ce, error_type, COMPLETELY_ABSENT, o);
+}
+
+static int verify_absent_if_directory(const struct cache_entry *ce,
+ enum unpack_trees_error_types error_type,
+ struct unpack_trees_options *o)
+{
+ if (!o->skip_sparse_checkout && (ce->ce_flags & CE_NEW_SKIP_WORKTREE))
+ return 0;
+ return verify_absent_1(ce, error_type, ABSENT_ANY_DIRECTORY, o);
}
static int verify_absent_sparse(const struct cache_entry *ce,
enum unpack_trees_error_types error_type,
struct unpack_trees_options *o)
{
- return verify_absent_1(ce, error_type, o);
+ return verify_absent_1(ce, error_type, COMPLETELY_ABSENT, o);
}
static int merged_entry(const struct cache_entry *ce,
@@ -2344,6 +2388,12 @@ static int merged_entry(const struct cache_entry *ce,
* Previously unmerged entry left as an existence
* marker by read_index_unmerged();
*/
+ if (verify_absent_if_directory(merge,
+ ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o)) {
+ discard_cache_entry(merge);
+ return -1;
+ }
+
invalidate_ce_path(old, o);
}
@@ -2361,7 +2411,10 @@ static int deleted_entry(const struct cache_entry *ce,
if (verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_REMOVED, o))
return -1;
return 0;
+ } else if (verify_absent_if_directory(ce, ERROR_WOULD_LOSE_UNTRACKED_REMOVED, o)) {
+ return -1;
}
+
if (!(old->ce_flags & CE_CONFLICTED) && verify_uptodate(old, o))
return -1;
add_entry(o, ce, CE_REMOVE, 0);
diff --git a/unpack-trees.h b/unpack-trees.h
index 2d88b19dca..71ffb7eeb0 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -45,10 +45,17 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
*/
void clear_unpack_trees_porcelain(struct unpack_trees_options *opts);
+enum unpack_trees_reset_type {
+ UNPACK_RESET_NONE = 0, /* traditional "false" value; still valid */
+ UNPACK_RESET_INVALID = 1, /* "true" no longer valid; use below values */
+ UNPACK_RESET_PROTECT_UNTRACKED,
+ UNPACK_RESET_OVERWRITE_UNTRACKED
+};
+
struct unpack_trees_options {
- unsigned int reset,
- merge,
+ unsigned int merge,
update,
+ preserve_ignored,
clone,
index_only,
nontrivial_merge,
@@ -64,9 +71,9 @@ struct unpack_trees_options {
exiting_early,
show_all_errors,
dry_run;
+ enum unpack_trees_reset_type reset;
const char *prefix;
int cache_bottom;
- struct dir_struct *dir;
struct pathspec *pathspec;
merge_fn_t fn;
const char *msgs[NB_UNPACK_TREES_WARNING_TYPES];
@@ -88,6 +95,7 @@ struct unpack_trees_options {
struct index_state result;
struct pattern_list *pl; /* for internal use */
+ struct dir_struct *dir; /* for internal use only */
struct checkout_metadata meta;
};
diff --git a/upload-pack.c b/upload-pack.c
index 297b76fcb4..c78d55bc67 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -1207,14 +1207,14 @@ static int send_ref(const char *refname, const struct object_id *oid,
format_symref_info(&symref_info, &data->symref);
format_session_id(&session_id, data);
- packet_write_fmt(1, "%s %s%c%s%s%s%s%s%s%s object-format=%s agent=%s\n",
+ packet_fwrite_fmt(stdout, "%s %s%c%s%s%s%s%s%s%s object-format=%s agent=%s\n",
oid_to_hex(oid), refname_nons,
0, capabilities,
(data->allow_uor & ALLOW_TIP_SHA1) ?
" allow-tip-sha1-in-want" : "",
(data->allow_uor & ALLOW_REACHABLE_SHA1) ?
" allow-reachable-sha1-in-want" : "",
- data->stateless_rpc ? " no-done" : "",
+ data->no_done ? " no-done" : "",
symref_info.buf,
data->allow_filter ? " filter" : "",
session_id.buf,
@@ -1223,11 +1223,11 @@ static int send_ref(const char *refname, const struct object_id *oid,
strbuf_release(&symref_info);
strbuf_release(&session_id);
} else {
- packet_write_fmt(1, "%s %s\n", oid_to_hex(oid), refname_nons);
+ packet_fwrite_fmt(stdout, "%s %s\n", oid_to_hex(oid), refname_nons);
}
capabilities = NULL;
if (!peel_iterated_oid(oid, &peeled))
- packet_write_fmt(1, "%s %s^{}\n", oid_to_hex(&peeled), refname_nons);
+ packet_fwrite_fmt(stdout, "%s %s^{}\n", oid_to_hex(&peeled), refname_nons);
return 0;
}
@@ -1329,7 +1329,8 @@ static int upload_pack_config(const char *var, const char *value, void *cb_data)
return parse_hide_refs_config(var, value, "uploadpack");
}
-void upload_pack(struct upload_pack_options *options)
+void upload_pack(const int advertise_refs, const int stateless_rpc,
+ const int timeout)
{
struct packet_reader reader;
struct upload_pack_data data;
@@ -1338,16 +1339,24 @@ void upload_pack(struct upload_pack_options *options)
git_config(upload_pack_config, &data);
- data.stateless_rpc = options->stateless_rpc;
- data.daemon_mode = options->daemon_mode;
- data.timeout = options->timeout;
+ data.stateless_rpc = stateless_rpc;
+ data.timeout = timeout;
+ if (data.timeout)
+ data.daemon_mode = 1;
head_ref_namespaced(find_symref, &data.symref);
- if (options->advertise_refs || !data.stateless_rpc) {
+ if (advertise_refs || !data.stateless_rpc) {
reset_timeout(data.timeout);
+ if (advertise_refs)
+ data.no_done = 1;
head_ref_namespaced(send_ref, &data);
for_each_namespaced_ref(send_ref, &data);
+ /*
+ * fflush stdout before calling advertise_shallow_grafts because send_ref
+ * uses stdio.
+ */
+ fflush_or_die(stdout);
advertise_shallow_grafts(1);
packet_flush(1);
} else {
@@ -1355,7 +1364,7 @@ void upload_pack(struct upload_pack_options *options)
for_each_namespaced_ref(check_ref, NULL);
}
- if (!options->advertise_refs) {
+ if (!advertise_refs) {
packet_reader_init(&reader, 0, NULL, 0,
PACKET_READ_CHOMP_NEWLINE |
PACKET_READ_DIE_ON_ERR_PACKET);
@@ -1417,21 +1426,25 @@ static int parse_want_ref(struct packet_writer *writer, const char *line,
struct string_list *wanted_refs,
struct object_array *want_obj)
{
- const char *arg;
- if (skip_prefix(line, "want-ref ", &arg)) {
+ const char *refname_nons;
+ if (skip_prefix(line, "want-ref ", &refname_nons)) {
struct object_id oid;
struct string_list_item *item;
struct object *o;
+ struct strbuf refname = STRBUF_INIT;
- if (read_ref(arg, &oid)) {
- packet_writer_error(writer, "unknown ref %s", arg);
- die("unknown ref %s", arg);
+ strbuf_addf(&refname, "%s%s", get_git_namespace(), refname_nons);
+ if (ref_is_hidden(refname_nons, refname.buf) ||
+ read_ref(refname.buf, &oid)) {
+ packet_writer_error(writer, "unknown ref %s", refname_nons);
+ die("unknown ref %s", refname_nons);
}
+ strbuf_release(&refname);
- item = string_list_append(wanted_refs, arg);
+ item = string_list_append(wanted_refs, refname_nons);
item->util = oiddup(&oid);
- o = parse_object_or_die(&oid, arg);
+ o = parse_object_or_die(&oid, refname_nons);
if (!(o->flags & WANTED)) {
o->flags |= WANTED;
add_object_array(o, NULL, want_obj);
@@ -1655,8 +1668,7 @@ enum fetch_state {
FETCH_DONE,
};
-int upload_pack_v2(struct repository *r, struct strvec *keys,
- struct packet_reader *request)
+int upload_pack_v2(struct repository *r, struct packet_reader *request)
{
enum fetch_state state = FETCH_PROCESS_ARGS;
struct upload_pack_data data;
diff --git a/upload-pack.h b/upload-pack.h
index 27ddcdc6cb..d6ee25ea98 100644
--- a/upload-pack.h
+++ b/upload-pack.h
@@ -1,20 +1,12 @@
#ifndef UPLOAD_PACK_H
#define UPLOAD_PACK_H
-struct upload_pack_options {
- int stateless_rpc;
- int advertise_refs;
- unsigned int timeout;
- int daemon_mode;
-};
-
-void upload_pack(struct upload_pack_options *options);
+void upload_pack(const int advertise_refs, const int stateless_rpc,
+ const int timeout);
struct repository;
-struct strvec;
struct packet_reader;
-int upload_pack_v2(struct repository *r, struct strvec *keys,
- struct packet_reader *request);
+int upload_pack_v2(struct repository *r, struct packet_reader *request);
struct strbuf;
int upload_pack_advertise(struct repository *r,
diff --git a/userdiff.c b/userdiff.c
index d9b2ba752f..af02b1878c 100644
--- a/userdiff.c
+++ b/userdiff.c
@@ -13,6 +13,16 @@ static int drivers_alloc;
#define IPATTERN(name, pattern, word_regex) \
{ name, NULL, -1, { pattern, REG_EXTENDED | REG_ICASE }, \
word_regex "|[^[:space:]]|[\xc0-\xff][\x80-\xbf]+" }
+
+/*
+ * Built-in drivers for various languages, sorted by their names
+ * (except that the "default" is left at the end).
+ *
+ * When writing or updating patterns, assume that the contents these
+ * patterns are applied to are syntactically correct. The patterns
+ * can be simple without implementing all syntactical corner cases, as
+ * long as they are sufficiently permissive.
+ */
static struct userdiff_driver builtin_drivers[] = {
IPATTERN("ada",
"!^(.*[ \t])?(is[ \t]+new|renames|is[ \t]+separate)([ \t].*)?$\n"
@@ -142,7 +152,11 @@ PATTERNS("html",
"[^<>= \t]+"),
PATTERNS("java",
"!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n"
- "^[ \t]*(([A-Za-z_][A-Za-z_0-9]*[ \t]+)+[A-Za-z_][A-Za-z_0-9]*[ \t]*\\([^;]*)$",
+ /* Class, enum, and interface declarations */
+ "^[ \t]*(([a-z]+[ \t]+)*(class|enum|interface)[ \t]+[A-Za-z][A-Za-z0-9_$]*[ \t]+.*)$\n"
+ /* Method definitions; note that constructor signatures are not */
+ /* matched because they are indistinguishable from method calls. */
+ "^[ \t]*(([A-Za-z_<>&][][?&<>.,A-Za-z_0-9]*[ \t]+)+[A-Za-z_][A-Za-z_0-9]*[ \t]*\\([^;]*)$",
/* -- */
"[a-zA-Z_][a-zA-Z0-9_]*"
"|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
@@ -214,7 +228,7 @@ PATTERNS("perl",
"|<<|<>|<=>|>>"),
PATTERNS("php",
"^[\t ]*(((public|protected|private|static|abstract|final)[\t ]+)*function.*)$\n"
- "^[\t ]*((((final|abstract)[\t ]+)?class|interface|trait).*)$",
+ "^[\t ]*((((final|abstract)[\t ]+)?class|enum|interface|trait).*)$",
/* -- */
"[a-zA-Z_][a-zA-Z0-9_]*"
"|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+"
diff --git a/wrapper.c b/wrapper.c
index 563ad590df..1460d4e27b 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -145,6 +145,18 @@ void *xcalloc(size_t nmemb, size_t size)
return ret;
}
+void xsetenv(const char *name, const char *value, int overwrite)
+{
+ if (setenv(name, value, overwrite))
+ die_errno(_("could not setenv '%s'"), name ? name : "(null)");
+}
+
+void xunsetenv(const char *name)
+{
+ if (!unsetenv(name))
+ die_errno(_("could not unsetenv '%s'"), name ? name : "(null)");
+}
+
/*
* Limit size of IO chunks, because huge chunks only cause pain. OS X
* 64-bit is buggy, returning EINVAL if len >= INT_MAX; and even in
@@ -193,7 +205,9 @@ int xopen(const char *path, int oflag, ...)
if (errno == EINTR)
continue;
- if ((oflag & O_RDWR) == O_RDWR)
+ if ((oflag & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
+ die_errno(_("unable to create '%s'"), path);
+ else if ((oflag & O_RDWR) == O_RDWR)
die_errno(_("could not open '%s' for reading and writing"), path);
else if ((oflag & O_WRONLY) == O_WRONLY)
die_errno(_("could not open '%s' for writing"), path);
diff --git a/write-or-die.c b/write-or-die.c
index d33e68f6ab..0b1ec8190b 100644
--- a/write-or-die.c
+++ b/write-or-die.c
@@ -70,3 +70,15 @@ void write_or_die(int fd, const void *buf, size_t count)
die_errno("write error");
}
}
+
+void fwrite_or_die(FILE *f, const void *buf, size_t count)
+{
+ if (fwrite(buf, 1, count, f) != count)
+ die_errno("fwrite error");
+}
+
+void fflush_or_die(FILE *f)
+{
+ if (fflush(f))
+ die_errno("fflush error");
+}
diff --git a/wt-status.c b/wt-status.c
index eaed30eafb..e4f29b2b4c 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -787,7 +787,7 @@ static void wt_status_collect_untracked(struct wt_status *s)
dir_clear(&dir);
- if (advice_status_u_option)
+ if (advice_enabled(ADVICE_STATUS_U_OPTION))
s->untracked_in_ms = (getnanotime() - t_begin) / 1000000;
}
@@ -1158,7 +1158,7 @@ static void wt_longstatus_print_tracking(struct wt_status *s)
if (!format_tracking_info(branch, &sb, s->ahead_behind_flags))
return;
- if (advice_status_ahead_behind_warning &&
+ if (advice_enabled(ADVICE_STATUS_AHEAD_BEHIND_WARNING) &&
s->ahead_behind_flags == AHEAD_BEHIND_FULL) {
uint64_t t_delta_in_ms = (getnanotime() - t_begin) / 1000000;
if (t_delta_in_ms > AB_DELAY_WARNING_IN_MS) {
@@ -1845,7 +1845,7 @@ static void wt_longstatus_print(struct wt_status *s)
wt_longstatus_print_other(s, &s->untracked, _("Untracked files"), "add");
if (s->show_ignored_mode)
wt_longstatus_print_other(s, &s->ignored, _("Ignored files"), "add -f");
- if (advice_status_u_option && 2000 < s->untracked_in_ms) {
+ if (advice_enabled(ADVICE_STATUS_U_OPTION) && 2000 < s->untracked_in_ms) {
status_printf_ln(s, GIT_COLOR_NORMAL, "%s", "");
status_printf_ln(s, GIT_COLOR_NORMAL,
_("It took %.2f seconds to enumerate untracked files. 'status -uno'\n"