summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/main.yml5
-rw-r--r--.gitignore2
-rw-r--r--.mailmap1
-rw-r--r--Documentation/CodingGuidelines12
-rw-r--r--Documentation/Makefile25
-rw-r--r--Documentation/RelNotes/2.31.1.txt27
-rw-r--r--Documentation/RelNotes/2.32.0.txt416
-rw-r--r--Documentation/SubmittingPatches11
-rw-r--r--Documentation/config.txt2
-rw-r--r--Documentation/config/advice.txt4
-rw-r--r--Documentation/config/checkout.txt21
-rw-r--r--Documentation/config/clone.txt4
-rw-r--r--Documentation/config/commitgraph.txt6
-rw-r--r--Documentation/config/index.txt5
-rw-r--r--Documentation/config/log.txt5
-rw-r--r--Documentation/config/pack.txt15
-rw-r--r--Documentation/config/push.txt7
-rw-r--r--Documentation/config/rebase.txt7
-rw-r--r--Documentation/config/stash.txt5
-rw-r--r--Documentation/config/uploadpack.txt9
-rw-r--r--Documentation/diff-generate-patch.txt7
-rw-r--r--Documentation/diff-options.txt20
-rw-r--r--Documentation/fetch-options.txt5
-rw-r--r--Documentation/git-am.txt4
-rw-r--r--Documentation/git-apply.txt11
-rw-r--r--Documentation/git-cat-file.txt67
-rw-r--r--Documentation/git-clone.txt7
-rw-r--r--Documentation/git-commit.txt59
-rw-r--r--Documentation/git-config.txt5
-rw-r--r--Documentation/git-credential.txt4
-rw-r--r--Documentation/git-cvsserver.txt24
-rw-r--r--Documentation/git-format-patch.txt34
-rw-r--r--Documentation/git-grep.txt64
-rw-r--r--Documentation/git-interpret-trailers.txt94
-rw-r--r--Documentation/git-mailinfo.txt21
-rw-r--r--Documentation/git-maintenance.txt6
-rw-r--r--Documentation/git-mktag.txt16
-rw-r--r--Documentation/git-multi-pack-index.txt14
-rw-r--r--Documentation/git-p4.txt4
-rw-r--r--Documentation/git-pack-objects.txt10
-rw-r--r--Documentation/git-push.txt2
-rw-r--r--Documentation/git-rebase.txt55
-rw-r--r--Documentation/git-repack.txt23
-rw-r--r--Documentation/git-rm.txt4
-rw-r--r--Documentation/git-sparse-checkout.txt14
-rw-r--r--Documentation/git-stash.txt24
-rw-r--r--Documentation/git-svn.txt38
-rw-r--r--Documentation/git.txt12
-rw-r--r--Documentation/gitattributes.txt11
-rw-r--r--Documentation/gitdiffcore.txt2
-rw-r--r--Documentation/githooks.txt33
-rw-r--r--Documentation/gitignore.txt6
-rw-r--r--Documentation/gitmailmap.txt7
-rw-r--r--Documentation/gitmodules.txt8
-rw-r--r--Documentation/gitnamespaces.txt4
-rw-r--r--Documentation/gitweb.conf.txt11
-rw-r--r--Documentation/howto/coordinate-embargoed-releases.txt131
-rwxr-xr-xDocumentation/lint-gitlink.perl108
-rwxr-xr-xDocumentation/lint-man-end-blurb.perl24
-rwxr-xr-xDocumentation/lint-man-section-order.perl105
-rw-r--r--Documentation/pretty-formats.txt19
-rw-r--r--Documentation/rev-list-options.txt8
-rw-r--r--Documentation/technical/api-error-handling.txt10
-rw-r--r--Documentation/technical/api-simple-ipc.txt105
-rw-r--r--Documentation/technical/api-trace2.txt2
-rw-r--r--Documentation/technical/index-format.txt19
-rw-r--r--Documentation/technical/multi-pack-index.txt5
-rw-r--r--Documentation/technical/pack-format.txt83
-rw-r--r--Documentation/technical/parallel-checkout.txt270
-rw-r--r--Documentation/technical/protocol-v2.txt39
-rw-r--r--Documentation/technical/reftable.txt9
-rw-r--r--Documentation/technical/sparse-index.txt208
-rw-r--r--Documentation/user-manual.txt3
-rwxr-xr-xGIT-VERSION-GEN2
-rw-r--r--INSTALL4
-rw-r--r--Makefile97
l---------RelNotes2
-rw-r--r--SECURITY.md51
-rw-r--r--add-interactive.c6
-rw-r--r--advice.c20
-rw-r--r--advice.h4
-rw-r--r--apply.c35
-rw-r--r--archive-tar.c2
-rw-r--r--archive.c52
-rw-r--r--archive.h2
-rw-r--r--attr.c86
-rw-r--r--attr.h4
-rw-r--r--bisect.c2
-rw-r--r--blame.c19
-rw-r--r--block-sha1/sha1.c2
-rw-r--r--bloom.c3
-rw-r--r--branch.c5
-rw-r--r--builtin.h1
-rw-r--r--builtin/add.c81
-rw-r--r--builtin/am.c51
-rw-r--r--builtin/bisect--helper.c10
-rw-r--r--builtin/branch.c10
-rw-r--r--builtin/bugreport.c8
-rw-r--r--builtin/check-ignore.c4
-rw-r--r--builtin/checkout--worker.c145
-rw-r--r--builtin/checkout-index.c27
-rw-r--r--builtin/checkout.c43
-rw-r--r--builtin/clean.c10
-rw-r--r--builtin/clone.c37
-rw-r--r--builtin/column.c8
-rw-r--r--builtin/commit.c148
-rw-r--r--builtin/config.c6
-rw-r--r--builtin/credential-cache--daemon.c3
-rw-r--r--builtin/credential-cache.c2
-rw-r--r--builtin/describe.c2
-rw-r--r--builtin/diff.c2
-rw-r--r--builtin/difftool.c6
-rw-r--r--builtin/fast-export.c10
-rw-r--r--builtin/fast-import.c16
-rw-r--r--builtin/fetch.c86
-rw-r--r--builtin/for-each-ref.c18
-rw-r--r--builtin/fsck.c4
-rw-r--r--builtin/gc.c49
-rw-r--r--builtin/grep.c7
-rw-r--r--builtin/index-pack.c16
-rw-r--r--builtin/init-db.c37
-rw-r--r--builtin/log.c35
-rw-r--r--builtin/ls-files.c97
-rw-r--r--builtin/ls-remote.c6
-rw-r--r--builtin/ls-tree.c6
-rw-r--r--builtin/mailinfo.c115
-rw-r--r--builtin/merge-index.c5
-rw-r--r--builtin/merge.c16
-rw-r--r--builtin/multi-pack-index.c182
-rw-r--r--builtin/mv.c2
-rw-r--r--builtin/notes.c2
-rw-r--r--builtin/pack-objects.c589
-rw-r--r--builtin/pack-redundant.c30
-rw-r--r--builtin/range-diff.c2
-rw-r--r--builtin/rebase.c17
-rw-r--r--builtin/receive-pack.c14
-rw-r--r--builtin/remote.c12
-rw-r--r--builtin/repack.c218
-rw-r--r--builtin/reset.c2
-rw-r--r--builtin/rev-list.c36
-rw-r--r--builtin/rev-parse.c2
-rw-r--r--builtin/revert.c4
-rw-r--r--builtin/rm.c39
-rw-r--r--builtin/show-index.c4
-rw-r--r--builtin/sparse-checkout.c52
-rw-r--r--builtin/stash.c67
-rw-r--r--builtin/submodule--helper.c29
-rw-r--r--builtin/symbolic-ref.c4
-rw-r--r--builtin/tag.c15
-rw-r--r--builtin/unpack-objects.c11
-rw-r--r--builtin/update-index.c2
-rw-r--r--builtin/worktree.c18
-rw-r--r--bulk-checkin.c4
-rw-r--r--cache-tree.c42
-rw-r--r--cache.h83
-rwxr-xr-xci/run-build-and-tests.sh2
-rw-r--r--combine-diff.c22
-rw-r--r--commit-graph.c60
-rw-r--r--commit-graph.h1
-rw-r--r--commit-reach.c6
-rw-r--r--commit.c18
-rw-r--r--commit.h3
-rw-r--r--compat/mingw.c2
-rw-r--r--compat/precompose_utf8.c9
-rw-r--r--compat/precompose_utf8.h1
-rw-r--r--compat/simple-ipc/ipc-shared.c32
-rw-r--r--compat/simple-ipc/ipc-unix-socket.c1003
-rw-r--r--compat/simple-ipc/ipc-win32.c755
-rw-r--r--config.c59
-rw-r--r--config.h4
-rw-r--r--config.mak.uname2
-rw-r--r--connect.c2
-rw-r--r--contrib/buildsystems/CMakeLists.txt42
-rw-r--r--contrib/coccinelle/array.cocci8
-rw-r--r--contrib/coccinelle/xcalloc.cocci10
-rw-r--r--contrib/completion/git-completion.bash166
-rw-r--r--contrib/completion/git-prompt.sh6
-rwxr-xr-xcontrib/subtree/git-subtree.sh642
-rw-r--r--contrib/subtree/git-subtree.txt192
-rwxr-xr-xcontrib/subtree/t/t7900-subtree.sh1450
-rw-r--r--contrib/subtree/todo6
-rw-r--r--convert.c173
-rw-r--r--convert.h112
-rw-r--r--csum-file.c33
-rw-r--r--daemon.c12
-rw-r--r--decorate.c2
-rw-r--r--diff-lib.c29
-rw-r--r--diff-merges.c58
-rw-r--r--diff-merges.h2
-rw-r--r--diff-no-index.c7
-rw-r--r--diff.c26
-rw-r--r--diffcore-rename.c681
-rw-r--r--diffcore.h21
-rw-r--r--dir.c192
-rw-r--r--dir.h17
-rw-r--r--entry.c111
-rw-r--r--entry.h59
-rw-r--r--ewah/bitmap.c2
-rw-r--r--fetch-pack.c260
-rw-r--r--fetch-pack.h15
-rw-r--r--fmt-merge-msg.c2
-rw-r--r--fsck.c84
-rw-r--r--fsck.h3
-rw-r--r--fsmonitor.c6
-rw-r--r--fsmonitor.h11
-rw-r--r--git-compat-util.h14
-rwxr-xr-xgit-filter-branch.sh16
-rwxr-xr-xgit-p4.py21
-rwxr-xr-xgit-send-email.perl58
-rwxr-xr-xgit-submodule.sh4
-rw-r--r--git.c12
-rwxr-xr-xgitweb/gitweb.perl34
-rw-r--r--grep.c109
-rw-r--r--grep.h9
-rw-r--r--hash.h96
-rw-r--r--hashmap.c2
-rw-r--r--hex.c20
-rw-r--r--http-backend.c2
-rw-r--r--http-push.c8
-rw-r--r--http-walker.c2
-rw-r--r--http.c19
-rw-r--r--imap-send.c4
-rw-r--r--line-log.c2
-rw-r--r--line-range.c2
-rw-r--r--list-objects-filter-options.c15
-rw-r--r--list-objects-filter-options.h3
-rw-r--r--list-objects-filter.c122
-rw-r--r--list-objects-filter.h2
-rw-r--r--list-objects.c30
-rw-r--r--ll-merge.c2
-rw-r--r--log-tree.c12
-rw-r--r--mailinfo.c53
-rw-r--r--mailinfo.h10
-rw-r--r--mailmap.c24
-rw-r--r--match-trees.c2
-rw-r--r--mem-pool.c2
-rw-r--r--merge-ort-wrappers.c2
-rw-r--r--merge-ort.c748
-rw-r--r--merge-recursive.c68
-rw-r--r--midx.c229
-rw-r--r--midx.h11
-rw-r--r--name-hash.c19
-rw-r--r--negotiator/default.c2
-rw-r--r--negotiator/skipping.c4
-rw-r--r--notes-merge.c11
-rw-r--r--notes-utils.c2
-rw-r--r--notes.c11
-rw-r--r--object-file.c71
-rw-r--r--object-store.h5
-rw-r--r--object.c11
-rw-r--r--object.h4
-rw-r--r--pack-bitmap.c77
-rw-r--r--pack-bitmap.h7
-rw-r--r--pack-objects.c2
-rw-r--r--pack-objects.h159
-rw-r--r--pack-revindex.c126
-rw-r--r--pack-revindex.h53
-rw-r--r--pack-write.c36
-rw-r--r--pack.h1
-rw-r--r--packfile.c78
-rw-r--r--packfile.h5
-rw-r--r--parallel-checkout.c673
-rw-r--r--parallel-checkout.h111
-rw-r--r--parse-options-cb.c2
-rw-r--r--parse-options.c19
-rw-r--r--parse-options.h35
-rw-r--r--patch-ids.c2
-rw-r--r--path.c6
-rw-r--r--path.h2
-rw-r--r--pathspec.c35
-rw-r--r--pathspec.h22
-rw-r--r--perl/Git/I18N.pm10
-rw-r--r--pkt-line.c86
-rw-r--r--pkt-line.h17
-rw-r--r--pretty.c69
-rw-r--r--pretty.h5
-rw-r--r--progress.c2
-rw-r--r--promisor-remote.c2
-rw-r--r--protocol-caps.c113
-rw-r--r--protocol-caps.h10
-rw-r--r--range-diff.c4
-rw-r--r--reachable.c15
-rw-r--r--read-cache.c111
-rw-r--r--rebase-interactive.c7
-rw-r--r--ref-filter.c38
-rw-r--r--ref-filter.h2
-rw-r--r--reflog-walk.c4
-rw-r--r--refs.c10
-rw-r--r--refs/debug.c54
-rw-r--r--refs/files-backend.c24
-rw-r--r--refs/iterator.c2
-rw-r--r--refs/packed-backend.c4
-rw-r--r--refs/ref-cache.c2
-rw-r--r--remote-curl.c2
-rw-r--r--remote.c8
-rw-r--r--repo-settings.c15
-rw-r--r--repository.c15
-rw-r--r--repository.h3
-rw-r--r--rerere.c4
-rw-r--r--reset.c2
-rw-r--r--resolve-undo.c8
-rw-r--r--revision.c46
-rw-r--r--revision.h9
-rw-r--r--run-command.c4
-rw-r--r--send-pack.c65
-rw-r--r--sequencer.c348
-rw-r--r--sequencer.h6
-rw-r--r--serve.c2
-rw-r--r--server-info.c2
-rw-r--r--setup.c28
-rw-r--r--simple-ipc.h235
-rw-r--r--sparse-index.c349
-rw-r--r--sparse-index.h23
-rw-r--r--split-index.c6
-rw-r--r--strbuf.c2
-rw-r--r--streaming.c268
-rw-r--r--submodule-config.c2
-rw-r--r--submodule.c32
-rw-r--r--submodule.h6
-rw-r--r--symlinks.c54
-rw-r--r--t/README15
-rw-r--r--t/annotate-tests.sh34
-rw-r--r--t/helper/test-bitmap.c24
-rw-r--r--t/helper/test-bloom.c4
-rw-r--r--t/helper/test-chmtime.c4
-rw-r--r--t/helper/test-example-decorate.c6
-rw-r--r--t/helper/test-path-utils.c46
-rw-r--r--t/helper/test-read-cache.c66
-rw-r--r--t/helper/test-read-midx.c24
-rw-r--r--t/helper/test-simple-ipc.c787
-rw-r--r--t/helper/test-submodule-nested-repo-config.c2
-rw-r--r--t/helper/test-tool.c3
-rw-r--r--t/helper/test-tool.h3
-rw-r--r--t/helper/test-userdiff.c46
-rw-r--r--t/lib-encoding.sh25
-rw-r--r--t/lib-parallel-checkout.sh45
-rw-r--r--t/lib-rebase.sh11
-rwxr-xr-xt/perf/p2000-sparse-operations.sh101
-rwxr-xr-xt/perf/p5303-many-packs.sh36
-rwxr-xr-xt/perf/p5310-pack-bitmaps.sh14
-rwxr-xr-xt/perf/p5600-partial-clone.sh16
-rwxr-xr-xt/perf/p7519-fsmonitor.sh4
-rw-r--r--t/perf/perf-lib.sh31
-rwxr-xr-xt/t0001-init.sh28
-rwxr-xr-xt/t0003-attributes.sh36
-rwxr-xr-xt/t0008-ignores.sh34
-rwxr-xr-xt/t0021-conversion.sh24
-rwxr-xr-xt/t0027-auto-crlf.sh7
-rwxr-xr-xt/t0028-working-tree-encoding.sh25
-rwxr-xr-xt/t0052-simple-ipc.sh122
-rwxr-xr-xt/t0060-path-utils.sh30
-rwxr-xr-xt/t1091-sparse-checkout-builtin.sh13
-rwxr-xr-xt/t1092-sparse-checkout-compatibility.sh143
-rwxr-xr-xt/t1300-config.sh100
-rwxr-xr-xt/t1306-xdg-files.sh2
-rwxr-xr-xt/t1400-update-ref.sh9
-rwxr-xr-xt/t1500-rev-parse.sh4
-rwxr-xr-xt/t2021-checkout-overwrite.sh12
-rwxr-xr-xt/t2080-parallel-checkout-basics.sh229
-rwxr-xr-xt/t2081-parallel-checkout-collisions.sh162
-rwxr-xr-xt/t2082-parallel-checkout-attributes.sh194
-rwxr-xr-xt/t3001-ls-files-others-exclude.sh5
-rwxr-xr-xt/t3003-ls-files-exclude.sh4
-rwxr-xr-xt/t3060-ls-files-with-tree.sh41
-rwxr-xr-xt/t3070-wildmatch.sh5
-rwxr-xr-xt/t3206-range-diff.sh24
-rwxr-xr-xt/t3400-rebase.sh16
-rwxr-xr-xt/t3415-rebase-autosquash.sh30
-rwxr-xr-xt/t3418-rebase-continue.sh27
-rwxr-xr-xt/t3437-rebase-fixup-options.sh211
-rw-r--r--t/t3437/expected-combined-message21
-rw-r--r--t/t3437/expected-squash-message51
-rwxr-xr-xt/t3510-cherry-pick-sequence.sh32
-rwxr-xr-xt/t3512-cherry-pick-submodule.sh7
-rwxr-xr-xt/t3513-revert-submodule.sh5
-rwxr-xr-xt/t3602-rm-sparse-checkout.sh78
-rwxr-xr-xt/t3700-add.sh10
-rwxr-xr-xt/t3705-add-sparse-checkout.sh155
-rwxr-xr-xt/t3800-mktag.sh2
-rwxr-xr-xt/t3900-i18n-commit.sh4
-rwxr-xr-xt/t3905-stash-include-untracked.sh125
-rwxr-xr-xt/t4013-diff-various.sh31
-rwxr-xr-xt/t4014-format-patch.sh34
-rwxr-xr-xt/t4018-diff-funcname.sh53
-rw-r--r--t/t4018/README3
-rw-r--r--t/t4018/scheme-class7
-rw-r--r--t/t4018/scheme-def4
-rw-r--r--t/t4018/scheme-def-variant4
-rw-r--r--t/t4018/scheme-define-slash-public7
-rw-r--r--t/t4018/scheme-define-syntax8
-rw-r--r--t/t4018/scheme-define-variant4
-rw-r--r--t/t4018/scheme-library11
-rw-r--r--t/t4018/scheme-local-define4
-rw-r--r--t/t4018/scheme-module6
-rw-r--r--t/t4018/scheme-top-level-define4
-rw-r--r--t/t4018/scheme-user-defined-define6
-rwxr-xr-xt/t4034-diff-words.sh6
-rw-r--r--t/t4034/scheme/expect11
-rw-r--r--t/t4034/scheme/post6
-rw-r--r--t/t4034/scheme/pre6
-rwxr-xr-xt/t4053-diff-no-index.sh60
-rwxr-xr-xt/t4108-apply-threeway.sh70
-rwxr-xr-xt/t4203-mailmap.sh31
-rwxr-xr-xt/t4205-log-pretty-formats.sh50
-rwxr-xr-xt/t4258-am-quoted-cr.sh37
-rw-r--r--t/t4258/mbox12
-rwxr-xr-xt/t5001-archive-attr.sh14
-rwxr-xr-xt/t5100-mailinfo.sh40
-rw-r--r--t/t5100/quoted-cr-info5
-rw-r--r--t/t5100/quoted-cr-msg2
-rw-r--r--t/t5100/quoted-cr-patch22
-rw-r--r--t/t5100/quoted-cr.mbox47
-rwxr-xr-xt/t5300-pack-object.sh406
-rwxr-xr-xt/t5304-prune.sh16
-rwxr-xr-xt/t5310-pack-bitmaps.sh61
-rwxr-xr-xt/t5316-pack-delta-depth.sh15
-rwxr-xr-xt/t5318-commit-graph.sh2
-rwxr-xr-xt/t5319-multi-pack-index.sh42
-rwxr-xr-xt/t5324-split-commit-graph.sh4
-rwxr-xr-xt/t5505-remote.sh3
-rwxr-xr-xt/t5516-fetch-push.sh35
-rwxr-xr-xt/t5523-push-upstream.sh7
-rwxr-xr-xt/t5551-http-fetch-smart.sh41
-rwxr-xr-xt/t5572-pull-submodule.sh7
-rwxr-xr-xt/t5582-fetch-negative-refspec.sh43
-rwxr-xr-xt/t5601-clone.sh9
-rwxr-xr-xt/t5606-clone-options.sh37
-rwxr-xr-xt/t5611-clone-config.sh25
-rwxr-xr-xt/t5612-clone-refspec.sh1
-rwxr-xr-xt/t5616-partial-clone.sh8
-rwxr-xr-xt/t5701-git-serve.sh28
-rwxr-xr-xt/t5702-protocol-v2.sh89
-rwxr-xr-xt/t6030-bisect-porcelain.sh23
-rwxr-xr-xt/t6112-rev-list-filters-objects.sh72
-rwxr-xr-xt/t6113-rev-list-bitmap-filters.sh68
-rwxr-xr-xt/t6114-keep-packs.sh69
-rwxr-xr-xt/t6300-for-each-ref.sh16
-rwxr-xr-xt/t6302-for-each-ref-filter.sh19
-rwxr-xr-xt/t6423-merge-rename-directories.sh73
-rwxr-xr-xt/t6428-merge-conflicts-sparse.sh158
-rwxr-xr-xt/t6437-submodule-merge.sh5
-rwxr-xr-xt/t6438-submodule-directory-file-conflicts.sh7
-rwxr-xr-xt/t6501-freshen-objects.sh22
-rwxr-xr-xt/t6600-test-reach.sh2
-rwxr-xr-xt/t7003-filter-branch.sh31
-rwxr-xr-xt/t7007-show.sh39
-rwxr-xr-xt/t7011-skip-worktree-reading.sh5
-rwxr-xr-xt/t7012-skip-worktree-writing.sh19
-rwxr-xr-xt/t7063-status-untracked-cache.sh206
-rwxr-xr-xt/t7300-clean.sh42
-rwxr-xr-xt/t7406-submodule-update.sh24
-rwxr-xr-xt/t7450-bad-git-dotfiles.sh (renamed from t/t7415-submodule-names.sh)129
-rwxr-xr-xt/t7500-commit-template-squash-signoff.sh159
-rwxr-xr-xt/t7502-commit-porcelain.sh312
-rwxr-xr-xt/t7513-interpret-trailers.sh84
-rwxr-xr-xt/t7519-status-fsmonitor.sh8
-rwxr-xr-xt/t7703-repack-geometric.sh183
-rwxr-xr-xt/t7810-grep.sh3
-rwxr-xr-xt/t7900-maintenance.sh22
-rwxr-xr-xt/t9001-send-email.sh80
-rwxr-xr-xt/t9117-git-svn-init-clone.sh6
-rwxr-xr-xt/t9148-git-svn-propset.sh27
-rwxr-xr-xt/t9801-git-p4-branch.sh40
-rwxr-xr-xt/t9902-completion.sh22
-rw-r--r--t/test-lib-functions.sh7
-rw-r--r--t/test-lib.sh11
-rw-r--r--trailer.c43
-rw-r--r--transport-helper.c14
-rw-r--r--transport.c48
-rw-r--r--transport.h10
-rw-r--r--tree-diff.c4
-rw-r--r--tree-walk.c2
-rw-r--r--tree.c119
-rw-r--r--tree.h18
-rw-r--r--unix-socket.c53
-rw-r--r--unix-socket.h12
-rw-r--r--unix-stream-server.c125
-rw-r--r--unix-stream-server.h33
-rw-r--r--unpack-trees.c67
-rw-r--r--unpack-trees.h2
-rw-r--r--upload-pack.c20
-rw-r--r--usage.c17
-rw-r--r--userdiff.c178
-rw-r--r--userdiff.h13
-rw-r--r--utf8.c5
-rw-r--r--utf8.h1
-rw-r--r--vcs-svn/fast_export.h34
-rw-r--r--vcs-svn/line_buffer.h30
-rw-r--r--vcs-svn/sliding_window.h18
-rw-r--r--vcs-svn/svndiff.h10
-rw-r--r--vcs-svn/svndump.h10
-rw-r--r--walker.c2
-rw-r--r--worktree.c16
-rw-r--r--wrapper.c16
-rw-r--r--wt-status.c14
-rw-r--r--xdiff-interface.c2
-rw-r--r--xdiff/xpatience.c14
497 files changed, 20751 insertions, 4440 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 5f2f884b92..73856bafc9 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -186,6 +186,11 @@ jobs:
## Unzip and remove the artifact
unzip artifacts.zip
rm artifacts.zip
+ - name: initialize vcpkg
+ uses: actions/checkout@v2
+ with:
+ repository: 'microsoft/vcpkg'
+ path: 'compat/vcbuild/vcpkg'
- name: download vcpkg artifacts
shell: powershell
run: |
diff --git a/.gitignore b/.gitignore
index 3dcdb6bb5a..311841f9be 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,6 +33,7 @@
/git-check-mailmap
/git-check-ref-format
/git-checkout
+/git-checkout--worker
/git-checkout-index
/git-cherry
/git-cherry-pick
@@ -162,6 +163,7 @@
/git-stripspace
/git-submodule
/git-submodule--helper
+/git-subtree
/git-svn
/git-switch
/git-symbolic-ref
diff --git a/.mailmap b/.mailmap
index bde7aba756..9c6a446bdf 100644
--- a/.mailmap
+++ b/.mailmap
@@ -220,6 +220,7 @@ Philipp A. Hartmann <pah@qo.cx> <ph@sorgh.de>
Philippe Bruhat <book@cpan.org>
Ralf Thielow <ralf.thielow@gmail.com> <ralf.thielow@googlemail.com>
Ramsay Jones <ramsay@ramsayjones.plus.com> <ramsay@ramsay1.demon.co.uk>
+Ramkumar Ramachandra <r@artagnon.com> <artagnon@gmail.com>
Randall S. Becker <randall.becker@nexbridge.ca> <rsbecker@nexbridge.com>
René Scharfe <l.s.r@web.de> <rene.scharfe@lsrfire.ath.cx>
René Scharfe <l.s.r@web.de> Rene Scharfe
diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines
index 45465bc0c9..e3af089ecf 100644
--- a/Documentation/CodingGuidelines
+++ b/Documentation/CodingGuidelines
@@ -175,6 +175,11 @@ For shell scripts specifically (not exhaustive):
does not have such a problem.
+ - Even though "local" is not part of POSIX, we make heavy use of it
+ in our test suite. We do not use it in scripted Porcelains, and
+ hopefully nobody starts using "local" before they are reimplemented
+ in C ;-)
+
For C programs:
@@ -498,7 +503,12 @@ Error Messages
- Do not end error messages with a full stop.
- - Do not capitalize ("unable to open %s", not "Unable to open %s")
+ - Do not capitalize the first word, only because it is the first word
+ in the message ("unable to open %s", not "Unable to open %s"). But
+ "SHA-3 not supported" is fine, because the reason the first word is
+ capitalized is not because it is at the beginning of the sentence,
+ but because the word would be spelled in capital letters even when
+ it appeared in the middle of the sentence.
- Say what the error is first ("cannot open %s", not "%s: cannot open")
diff --git a/Documentation/Makefile b/Documentation/Makefile
index 81d1bf7a04..2aae4c9cbb 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -2,6 +2,8 @@
MAN1_TXT =
MAN5_TXT =
MAN7_TXT =
+HOWTO_TXT =
+DOC_DEP_TXT =
TECH_DOCS =
ARTICLES =
SP_ARTICLES =
@@ -42,6 +44,11 @@ MAN7_TXT += gittutorial-2.txt
MAN7_TXT += gittutorial.txt
MAN7_TXT += gitworkflows.txt
+HOWTO_TXT += $(wildcard howto/*.txt)
+
+DOC_DEP_TXT += $(wildcard *.txt)
+DOC_DEP_TXT += $(wildcard config/*.txt)
+
ifdef MAN_FILTER
MAN_TXT = $(filter $(MAN_FILTER),$(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT))
else
@@ -76,6 +83,7 @@ SP_ARTICLES += howto/rebuild-from-update-hook
SP_ARTICLES += howto/rebase-from-internal-branch
SP_ARTICLES += howto/keep-canonical-history-correct
SP_ARTICLES += howto/maintain-git
+SP_ARTICLES += howto/coordinate-embargoed-releases
API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technical/api-index.txt, $(wildcard technical/api-*.txt)))
SP_ARTICLES += $(API_DOCS)
@@ -90,6 +98,7 @@ TECH_DOCS += technical/multi-pack-index
TECH_DOCS += technical/pack-format
TECH_DOCS += technical/pack-heuristics
TECH_DOCS += technical/pack-protocol
+TECH_DOCS += technical/parallel-checkout
TECH_DOCS += technical/partial-clone
TECH_DOCS += technical/protocol-capabilities
TECH_DOCS += technical/protocol-common
@@ -284,7 +293,7 @@ docdep_prereqs = \
mergetools-list.made $(mergetools_txt) \
cmd-list.made $(cmds_txt)
-doc.dep : $(docdep_prereqs) $(wildcard *.txt) $(wildcard config/*.txt) build-docdep.perl
+doc.dep : $(docdep_prereqs) $(DOC_DEP_TXT) build-docdep.perl
$(QUIET_GEN)$(RM) $@+ $@ && \
$(PERL_PATH) ./build-docdep.perl >$@+ $(QUIET_STDERR) && \
mv $@+ $@
@@ -427,9 +436,9 @@ $(patsubst %.txt,%.texi,$(MAN_TXT)): %.texi : %.xml
$(DOCBOOK2X_TEXI) --to-stdout $*.xml >$@+ && \
mv $@+ $@
-howto-index.txt: howto-index.sh $(wildcard howto/*.txt)
+howto-index.txt: howto-index.sh $(HOWTO_TXT)
$(QUIET_GEN)$(RM) $@+ $@ && \
- '$(SHELL_PATH_SQ)' ./howto-index.sh $(sort $(wildcard howto/*.txt)) >$@+ && \
+ '$(SHELL_PATH_SQ)' ./howto-index.sh $(sort $(HOWTO_TXT)) >$@+ && \
mv $@+ $@
$(patsubst %,%.html,$(ARTICLES)) : %.html : %.txt
@@ -438,7 +447,7 @@ $(patsubst %,%.html,$(ARTICLES)) : %.html : %.txt
WEBDOC_DEST = /pub/software/scm/git/docs
howto/%.html: ASCIIDOC_EXTRA += -a git-relative-html-prefix=../
-$(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt GIT-ASCIIDOCFLAGS
+$(patsubst %.txt,%.html,$(HOWTO_TXT)): %.html : %.txt GIT-ASCIIDOCFLAGS
$(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
sed -e '1,/^$$/d' $< | \
$(TXT_TO_HTML) - >$@+ && \
@@ -470,7 +479,13 @@ print-man1:
@for i in $(MAN1_TXT); do echo $$i; done
lint-docs::
- $(QUIET_LINT)$(PERL_PATH) lint-gitlink.perl
+ $(QUIET_LINT)$(PERL_PATH) lint-gitlink.perl \
+ $(HOWTO_TXT) $(DOC_DEP_TXT) \
+ --section=1 $(MAN1_TXT) \
+ --section=5 $(MAN5_TXT) \
+ --section=7 $(MAN7_TXT); \
+ $(PERL_PATH) lint-man-end-blurb.perl $(MAN_TXT); \
+ $(PERL_PATH) lint-man-section-order.perl $(MAN_TXT);
ifeq ($(wildcard po/Makefile),po/Makefile)
doc-l10n install-l10n::
diff --git a/Documentation/RelNotes/2.31.1.txt b/Documentation/RelNotes/2.31.1.txt
new file mode 100644
index 0000000000..f9b06b8e1b
--- /dev/null
+++ b/Documentation/RelNotes/2.31.1.txt
@@ -0,0 +1,27 @@
+Git 2.31.1 Release Notes
+========================
+
+Fixes since v2.31
+-----------------
+
+ * The fsmonitor interface read from its input without making sure
+ there is something to read from. This bug is new in 2.31
+ timeframe.
+
+ * The data structure used by fsmonitor interface was not properly
+ duplicated during an in-core merge, leading to use-after-free etc.
+
+ * "git bisect" reimplemented more in C during 2.30 timeframe did not
+ take an annotated tag as a good/bad endpoint well. This regression
+ has been corrected.
+
+ * Fix macros that can silently inject unintended null-statements.
+
+ * CALLOC_ARRAY() macro replaces many uses of xcalloc().
+
+ * Update insn in Makefile comments to run fuzz-all target.
+
+ * Fix a corner case bug in "git mv" on case insensitive systems,
+ which was introduced in 2.29 timeframe.
+
+Also contains various documentation updates and code clean-ups.
diff --git a/Documentation/RelNotes/2.32.0.txt b/Documentation/RelNotes/2.32.0.txt
new file mode 100644
index 0000000000..87d56fa1aa
--- /dev/null
+++ b/Documentation/RelNotes/2.32.0.txt
@@ -0,0 +1,416 @@
+Git 2.32 Release Notes
+======================
+
+Backward compatibility notes
+----------------------------
+
+ * ".gitattributes", ".gitignore", and ".mailmap" files that are
+ symbolic links are ignored.
+
+ * "git apply --3way" used to first attempt a straight application,
+ and only fell back to the 3-way merge algorithm when the stright
+ application failed. Starting with this version, the command will
+ first try the 3-way merge algorithm and only when it fails (either
+ resulting with conflict or the base versions of blobs are missing),
+ falls back to the usual patch application.
+
+
+Updates since v2.31
+-------------------
+
+UI, Workflows & Features
+
+ * It does not make sense to make ".gitattributes", ".gitignore" and
+ ".mailmap" symlinks, as they are supposed to be usable from the
+ object store (think: bare repositories where HEAD:.mailmap etc. are
+ used). When these files are symbolic links, we used to read the
+ contents of the files pointed by them by mistake, which has been
+ corrected.
+
+ * "git stash show" learned to optionally show untracked part of the
+ stash.
+
+ * "git log --format='...'" learned "%(describe)" placeholder.
+
+ * "git repack" so far has been only capable of repacking everything
+ under the sun into a single pack (or split by size). A cleverer
+ strategy to reduce the cost of repacking a repository has been
+ introduced.
+
+ * The http codepath learned to let the credential layer to cache the
+ password used to unlock a certificate that has successfully been
+ used.
+
+ * "git commit --fixup=<commit>", which was to tweak the changes made
+ to the contents while keeping the original log message intact,
+ learned "--fixup=(amend|reword):<commit>", that can be used to
+ tweak both the message and the contents, and only the message,
+ respectively.
+
+ * "git send-email" learned to honor the core.hooksPath configuration.
+
+ * "git format-patch -v<n>" learned to allow a reroll count that is
+ not an integer.
+
+ * "git commit" learned "--trailer <key>[=<value>]" option; together
+ with the interpret-trailers command, this will make it easier to
+ support custom trailers.
+
+ * "git clone --reject-shallow" option fails the clone as soon as we
+ notice that we are cloning from a shallow repository.
+
+ * A configuration variable has been added to force tips of certain
+ refs to be given a reachability bitmap.
+
+ * "gitweb" learned "e-mail privacy" feature to redact strings that
+ look like e-mail addresses on various pages.
+
+ * "git apply --3way" has always been "to fall back to 3-way merge
+ only when straight application fails". Swap the order of falling
+ back so that 3-way is always attempted first (only when the option
+ is given, of course) and then straight patch application is used as
+ a fallback when it fails.
+
+ * "git apply" now takes "--3way" and "--cached" at the same time, and
+ work and record results only in the index.
+
+ * The command line completion (in contrib/) has learned that
+ CHERRY_PICK_HEAD is a possible pseudo-ref.
+
+ * Userdiff patterns for "Scheme" has been added.
+
+ * "git log" learned "--diff-merges=<style>" option, with an
+ associated configuration variable log.diffMerges.
+
+ * "git log --format=..." placeholders learned %ah/%ch placeholders to
+ request the --date=human output.
+
+ * Replace GIT_CONFIG_NOSYSTEM mechanism to decline from reading the
+ system-wide configuration file with GIT_CONFIG_SYSTEM that lets
+ users specify from which file to read the system-wide configuration
+ (setting it to an empty file would essentially be the same as
+ setting NOSYSTEM), and introduce GIT_CONFIG_GLOBAL to override the
+ per-user configuration in $HOME/.gitconfig.
+
+ * "git add" and "git rm" learned not to touch those paths that are
+ outside of sparse checkout.
+
+ * "git rev-list" learns the "--filter=object:type=<type>" option,
+ which can be used to exclude objects of the given kind from the
+ packfile generated by pack-objects.
+
+ * The command line completion (in contrib/) for "git stash" has been
+ updated.
+
+ * "git subtree" updates.
+
+ * It is now documented that "format-patch" skips merges.
+
+ * Options to "git pack-objects" that take numeric values like
+ --window and --depth should not accept negative values; the input
+ validation has been tightened.
+
+ * The way the command line specified by the trailer.<token>.command
+ configuration variable receives the end-user supplied value was
+ both error prone and misleading. An alternative to achieve the
+ same goal in a safer and more intuitive way has been added, as
+ the trailer.<token>.cmd configuration variable, to replace it.
+
+ * "git add -i --dry-run" does not dry-run, which was surprising. The
+ combination of options has taught to error out.
+
+ * "git push" learns to discover common ancestor with the receiving
+ end over protocol v2. This will hopefully make "git push" as
+ efficient as "git fetch" in avoiding objects from getting
+ transferred unnecessarily.
+
+ * "git mailinfo" (hence "git am") learned the "--quoted-cr" option to
+ control how lines ending with CRLF wrapped in base64 or qp are
+ handled.
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * Rename detection rework continues.
+
+ * GIT_TEST_FAIL_PREREQS is a mechanism to skip test pieces with
+ prerequisites to catch broken tests that depend on the side effects
+ of optional pieces, but did not work at all when negative
+ prerequisites were involved.
+ (merge 27d578d904 jk/fail-prereq-testfix later to maint).
+
+ * "git diff-index" codepath has been taught to trust fsmonitor status
+ to reduce number of lstat() calls.
+ (merge 7e5aa13d2c nk/diff-index-fsmonitor later to maint).
+
+ * Reorganize Makefile to allow building git.o and other essential
+ objects without extra stuff needed only for testing.
+
+ * Preparatory API changes for parallel checkout.
+
+ * A simple IPC interface gets introduced to build services like
+ fsmonitor on top.
+
+ * Fsck API clean-up.
+
+ * SECURITY.md that is facing individual contributors and end users
+ has been introduced. Also a procedure to follow when preparing
+ embargoed releases has been spelled out.
+ (merge 09420b7648 js/security-md later to maint).
+
+ * Optimize "rev-list --use-bitmap-index --objects" corner case that
+ uses negative tags as the stopping points.
+
+ * CMake update for vsbuild.
+
+ * An on-disk reverse-index to map the in-pack location of an object
+ back to its object name across multiple packfiles is introduced.
+
+ * Generate [ec]tags under $(QUIET_GEN).
+
+ * Clean-up codepaths that implements "git send-email --validate"
+ option and improves the message from it.
+
+ * The last remnant of gettext-poison has been removed.
+
+ * The test framework has been taught to optionally turn the default
+ merge strategy to "ort" throughout the system where we use
+ three-way merges internally, like cherry-pick, rebase etc.,
+ primarily to enhance its test coverage (the strategy has been
+ available as an explicit "-s ort" choice).
+
+ * A bit of code clean-up and a lot of test clean-up around userdiff
+ area.
+
+ * Handling of "promisor packs" that allows certain objects to be
+ missing and lazily retrievable has been optimized (a bit).
+
+ * When packet_write() fails, we gave an extra error message
+ unnecessarily, which has been corrected.
+
+ * The checkout machinery has been taught to perform the actual
+ write-out of the files in parallel when able.
+
+ * Show errno in the trace output in the error codepath that calls
+ read_raw_ref method.
+
+ * Effort to make the command line completion (in contrib/) safe with
+ "set -u" continues.
+
+ * Tweak a few tests for "log --format=..." that show timestamps in
+ various formats.
+
+ * The reflog expiry machinery has been taught to emit trace events.
+
+ * Over-the-wire protocol learns a new request type to ask for object
+ sizes given a list of object names.
+
+
+Fixes since v2.31
+-----------------
+
+ * The fsmonitor interface read from its input without making sure
+ there is something to read from. This bug is new in 2.31
+ timeframe.
+
+ * The data structure used by fsmonitor interface was not properly
+ duplicated during an in-core merge, leading to use-after-free etc.
+
+ * "git bisect" reimplemented more in C during 2.30 timeframe did not
+ take an annotated tag as a good/bad endpoint well. This regression
+ has been corrected.
+
+ * Fix macros that can silently inject unintended null-statements.
+
+ * CALLOC_ARRAY() macro replaces many uses of xcalloc().
+
+ * Update insn in Makefile comments to run fuzz-all target.
+
+ * Fix a corner case bug in "git mv" on case insensitive systems,
+ which was introduced in 2.29 timeframe.
+
+ * We had a code to diagnose and die cleanly when a required
+ clean/smudge filter is missing, but an assert before that
+ unnecessarily fired, hiding the end-user facing die() message.
+ (merge 6fab35f748 mt/cleanly-die-upon-missing-required-filter later to maint).
+
+ * Update C code that sets a few configuration variables when a remote
+ is configured so that it spells configuration variable names in the
+ canonical camelCase.
+ (merge 0f1da600e6 ab/remote-write-config-in-camel-case later to maint).
+
+ * A new configuration variable has been introduced to allow choosing
+ which version of the generation number gets used in the
+ commit-graph file.
+ (merge 702110aac6 ds/commit-graph-generation-config later to maint).
+
+ * Perf test update to work better in secondary worktrees.
+ (merge 36e834abc1 jk/perf-in-worktrees later to maint).
+
+ * Updates to memory allocation code around the use of pcre2 library.
+ (merge c1760352e0 ab/grep-pcre2-allocfix later to maint).
+
+ * "git -c core.bare=false clone --bare ..." would have segfaulted,
+ which has been corrected.
+ (merge 75555676ad bc/clone-bare-with-conflicting-config later to maint).
+
+ * When "git checkout" removes a path that does not exist in the
+ commit it is checking out, it wasn't careful enough not to follow
+ symbolic links, which has been corrected.
+ (merge fab78a0c3d mt/checkout-remove-nofollow later to maint).
+
+ * A few option description strings started with capital letters,
+ which were corrected.
+ (merge 5ee90326dc cc/downcase-opt-help later to maint).
+
+ * Plug or annotate remaining leaks that trigger while running the
+ very basic set of tests.
+ (merge 68ffe095a2 ah/plugleaks later to maint).
+
+ * The hashwrite() API uses a buffering mechanism to avoid calling
+ write(2) too frequently. This logic has been refactored to be
+ easier to understand.
+ (merge ddaf1f62e3 ds/clarify-hashwrite later to maint).
+
+ * "git cherry-pick/revert" with or without "--[no-]edit" did not spawn
+ the editor as expected (e.g. "revert --no-edit" after a conflict
+ still asked to edit the message), which has been corrected.
+ (merge 39edfd5cbc en/sequencer-edit-upon-conflict-fix later to maint).
+
+ * "git daemon" has been tightened against systems that take backslash
+ as directory separator.
+ (merge 9a7f1ce8b7 rs/daemon-sanitize-dir-sep later to maint).
+
+ * A NULL-dereference bug has been corrected in an error codepath in
+ "git for-each-ref", "git branch --list" etc.
+ (merge c685450880 jk/ref-filter-segfault-fix later to maint).
+
+ * Streamline the codepath to fix the UTF-8 encoding issues in the
+ argv[] and the prefix on macOS.
+ (merge c7d0e61016 tb/precompose-prefix-simplify later to maint).
+
+ * The command-line completion script (in contrib/) had a couple of
+ references that would have given a warning under the "-u" (nounset)
+ option.
+ (merge c5c0548d79 vs/completion-with-set-u later to maint).
+
+ * When "git pack-objects" makes a literal copy of a part of existing
+ packfile using the reachability bitmaps, its update to the progress
+ meter was broken.
+ (merge 8e118e8490 jk/pack-objects-bitmap-progress-fix later to maint).
+
+ * The dependencies for config-list.h and command-list.h were broken
+ when the former was split out of the latter, which has been
+ corrected.
+ (merge 56550ea718 sg/bugreport-fixes later to maint).
+
+ * "git push --quiet --set-upstream" was not quiet when setting the
+ upstream branch configuration, which has been corrected.
+ (merge f3cce896a8 ow/push-quiet-set-upstream later to maint).
+
+ * The prefetch task in "git maintenance" assumed that "git fetch"
+ from any remote would fetch all its local branches, which would
+ fetch too much if the user is interested in only a subset of
+ branches there.
+ (merge 32f67888d8 ds/maintenance-prefetch-fix later to maint).
+
+ * Clarify that pathnames recorded in Git trees are most often (but
+ not necessarily) encoded in UTF-8.
+ (merge 9364bf465d ab/pathname-encoding-doc later to maint).
+
+ * "git --config-env var=val cmd" weren't accepted (only
+ --config-env=var=val was).
+ (merge c331551ccf ps/config-env-option-with-separate-value later to maint).
+
+ * When the reachability bitmap is in effect, the "do not lose
+ recently created objects and those that are reachable from them"
+ safety to protect us from races were disabled by mistake, which has
+ been corrected.
+ (merge 2ba582ba4c jk/prune-with-bitmap-fix later to maint).
+
+ * Cygwin pathname handling fix.
+ (merge bccc37fdc7 ad/cygwin-no-backslashes-in-paths later to maint).
+
+ * "git rebase --[no-]reschedule-failed-exec" did not work well with
+ its configuration variable, which has been corrected.
+ (merge e5b32bffd1 ab/rebase-no-reschedule-failed-exec later to maint).
+
+ * Portability fix for command line completion script (in contrib/).
+ (merge f2acf763e2 si/zsh-complete-comment-fix later to maint).
+
+ * "git repack -A -d" in a partial clone unnecessarily loosened
+ objects in promisor pack.
+
+ * "git bisect skip" when custom words are used for new/old did not
+ work, which has been corrected.
+
+ * A few variants of informational message "Already up-to-date" has
+ been rephrased.
+ (merge ad9322da03 js/merge-already-up-to-date-message-reword later to maint).
+
+ * "git submodule update --quiet" did not propagate the quiet option
+ down to underlying "git fetch", which has been corrected.
+ (merge 62af4bdd42 nc/submodule-update-quiet later to maint).
+
+ * Document that our test can use "local" keyword.
+ (merge a84fd3bcc6 jc/test-allows-local later to maint).
+
+ * The word-diff mode has been taught to work better with a word
+ regexp that can match an empty string.
+ (merge 0324e8fc6b pw/word-diff-zero-width-matches later to maint).
+
+ * "git p4" learned to find branch points more efficiently.
+ (merge 6b79818bfb jk/p4-locate-branch-point-optim later to maint).
+
+ * When "git update-ref -d" removes a ref that is packed, it left
+ empty directories under $GIT_DIR/refs/ for
+ (merge 5f03e5126d wc/packed-ref-removal-cleanup later to maint).
+
+ * "git clean" and "git ls-files -i" had confusion around working on
+ or showing ignored paths inside an ignored directory, which has
+ been corrected.
+ (merge b548f0f156 en/dir-traversal later to maint).
+
+ * The handling of "%(push)" formatting element of "for-each-ref" and
+ friends was broken when the same codepath started handling
+ "%(push:<what>)", which has been corrected.
+ (merge 1e1c4c5eac zh/ref-filter-push-remote-fix later to maint).
+
+ * The bash prompt script (in contrib/) did not work under "set -u".
+ (merge 5c0cbdb107 en/prompt-under-set-u later to maint).
+
+ * The "chainlint" feature in the test framework is a handy way to
+ catch common mistakes in writing new tests, but tends to get
+ expensive. An knob to selectively disable it has been introduced
+ to help running tests that the developer has not modified.
+ (merge 2d86a96220 jk/test-chainlint-softer later to maint).
+
+ * The "rev-parse" command did not diagnose the lack of argument to
+ "--path-format" option, which was introduced in v2.31 era, which
+ has been corrected.
+ (merge 99fc555188 wm/rev-parse-path-format-wo-arg later to maint).
+
+ * Other code cleanup, docfix, build fix, etc.
+ (merge f451960708 dl/cat-file-doc-cleanup later to maint).
+ (merge 12604a8d0c sv/t9801-test-path-is-file-cleanup later to maint).
+ (merge ea7e63921c jr/doc-ignore-typofix later to maint).
+ (merge 23c781f173 ps/update-ref-trans-hook-doc later to maint).
+ (merge 42efa1231a jk/filter-branch-sha256 later to maint).
+ (merge 4c8e3dca6e tb/push-simple-uses-branch-merge-config later to maint).
+ (merge 6534d436a2 bs/asciidoctor-installation-hints later to maint).
+ (merge 47957485b3 ab/read-tree later to maint).
+ (merge 2be927f3d1 ab/diff-no-index-tests later to maint).
+ (merge 76593c09bb ab/detox-gettext-tests later to maint).
+ (merge 28e29ee38b jc/doc-format-patch-clarify later to maint).
+ (merge fc12b6fdde fm/user-manual-use-preface later to maint).
+ (merge dba94e3a85 cc/test-helper-bloom-usage-fix later to maint).
+ (merge 61a7660516 hn/reftable-tables-doc-update later to maint).
+ (merge 81ed96a9b2 jt/fetch-pack-request-fix later to maint).
+ (merge 151b6c2dd7 jc/doc-do-not-capitalize-clarification later to maint).
+ (merge 9160068ac6 js/access-nul-emulation-on-windows later to maint).
+ (merge 7a14acdbe6 po/diff-patch-doc later to maint).
+ (merge f91371b948 pw/patience-diff-clean-up later to maint).
+ (merge 3a7f0908b6 mt/clean-clean later to maint).
+ (merge d4e2d15a8b ab/streaming-simplify later to maint).
+ (merge 0e59f7ad67 ah/merge-ort-i18n later to maint).
+ (merge e6f68f62e0 ls/typofix later to maint).
diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches
index 0452db2e67..55287d72e0 100644
--- a/Documentation/SubmittingPatches
+++ b/Documentation/SubmittingPatches
@@ -117,10 +117,13 @@ If in doubt which identifier to use, run `git log --no-merges` on the
files you are modifying to see the current conventions.
[[summary-section]]
-It's customary to start the remainder of the first line after "area: "
-with a lower-case letter. E.g. "doc: clarify...", not "doc:
-Clarify...", or "githooks.txt: improve...", not "githooks.txt:
-Improve...".
+The title sentence after the "area:" prefix omits the full stop at the
+end, and its first word is not capitalized unless there is a reason to
+capitalize it other than because it is the first word in the sentence.
+E.g. "doc: clarify...", not "doc: Clarify...", or "githooks.txt:
+improve...", not "githooks.txt: Improve...". But "refs: HEAD is also
+treated as a ref" is correct, as we spell `HEAD` in all caps even when
+it appears in the middle of a sentence.
[[meaningful-message]]
The body should provide a meaningful commit message, which:
diff --git a/Documentation/config.txt b/Documentation/config.txt
index d08e83a148..bf82766a6a 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -46,7 +46,7 @@ Subsection names are case sensitive and can contain any characters except
newline and the null byte. Doublequote `"` and backslash can be included
by escaping them as `\"` and `\\`, respectively. Backslashes preceding
other characters are dropped when reading; for example, `\t` is read as
-`t` and `\0` is read as `0` Section headers cannot span multiple lines.
+`t` and `\0` is read as `0`. Section headers cannot span multiple lines.
Variables may belong directly to a section or to a given subsection. You
can have `[section]` if you have `[section "subsection"]`, but you don't
need to.
diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt
index acbd0c09aa..8b2849ff7b 100644
--- a/Documentation/config/advice.txt
+++ b/Documentation/config/advice.txt
@@ -119,4 +119,8 @@ advice.*::
addEmptyPathspec::
Advice shown if a user runs the add command without providing
the pathspec parameter.
+ updateSparsePath::
+ Advice shown when either linkgit:git-add[1] or linkgit:git-rm[1]
+ is asked to update index entries outside the current sparse
+ checkout.
--
diff --git a/Documentation/config/checkout.txt b/Documentation/config/checkout.txt
index 2cddf7b4b4..bfbca90f0e 100644
--- a/Documentation/config/checkout.txt
+++ b/Documentation/config/checkout.txt
@@ -21,3 +21,24 @@ checkout.guess::
Provides the default value for the `--guess` or `--no-guess`
option in `git checkout` and `git switch`. See
linkgit:git-switch[1] and linkgit:git-checkout[1].
+
+checkout.workers::
+ The number of parallel workers to use when updating the working tree.
+ The default is one, i.e. sequential execution. If set to a value less
+ than one, Git will use as many workers as the number of logical cores
+ available. This setting and `checkout.thresholdForParallelism` affect
+ all commands that perform checkout. E.g. checkout, clone, reset,
+ sparse-checkout, etc.
++
+Note: parallel checkout usually delivers better performance for repositories
+located on SSDs or over NFS. For repositories on spinning disks and/or machines
+with a small number of cores, the default sequential checkout often performs
+better. The size and compression level of a repository might also influence how
+well the parallel version performs.
+
+checkout.thresholdForParallelism::
+ When running parallel checkout with a small number of files, the cost
+ of subprocess spawning and inter-process communication might outweigh
+ the parallelization gains. This setting allows to define the minimum
+ number of files for which parallel checkout should be attempted. The
+ default is 100.
diff --git a/Documentation/config/clone.txt b/Documentation/config/clone.txt
index 47de36a5fe..7bcfbd18a5 100644
--- a/Documentation/config/clone.txt
+++ b/Documentation/config/clone.txt
@@ -2,3 +2,7 @@ clone.defaultRemoteName::
The name of the remote to create when cloning a repository. Defaults to
`origin`, and can be overridden by passing the `--origin` command-line
option to linkgit:git-clone[1].
+
+clone.rejectShallow::
+ Reject to clone a repository if it is a shallow one, can be overridden by
+ passing option `--reject-shallow` in command line. See linkgit:git-clone[1]
diff --git a/Documentation/config/commitgraph.txt b/Documentation/config/commitgraph.txt
index 4582c39fc4..30604e4a4c 100644
--- a/Documentation/config/commitgraph.txt
+++ b/Documentation/config/commitgraph.txt
@@ -1,3 +1,9 @@
+commitGraph.generationVersion::
+ Specifies the type of generation number version to use when writing
+ or reading the commit-graph file. If version 1 is specified, then
+ the corrected commit dates will not be written or read. Defaults to
+ 2.
+
commitGraph.maxNewFilters::
Specifies the default value for the `--max-new-filters` option of `git
commit-graph write` (c.f., linkgit:git-commit-graph[1]).
diff --git a/Documentation/config/index.txt b/Documentation/config/index.txt
index 7cb50b37e9..75f3a2d105 100644
--- a/Documentation/config/index.txt
+++ b/Documentation/config/index.txt
@@ -14,6 +14,11 @@ index.recordOffsetTable::
Defaults to 'true' if index.threads has been explicitly enabled,
'false' otherwise.
+index.sparse::
+ When enabled, write the index using sparse-directory entries. This
+ has no effect unless `core.sparseCheckout` and
+ `core.sparseCheckoutCone` are both enabled. Defaults to 'false'.
+
index.threads::
Specifies the number of threads to spawn when loading the index.
This is meant to reduce index load time on multiprocessor machines.
diff --git a/Documentation/config/log.txt b/Documentation/config/log.txt
index 208d5fdcaa..456eb07800 100644
--- a/Documentation/config/log.txt
+++ b/Documentation/config/log.txt
@@ -24,6 +24,11 @@ log.excludeDecoration::
the config option can be overridden by the `--decorate-refs`
option.
+log.diffMerges::
+ Set default diff format to be used for merge commits. See
+ `--diff-merges` in linkgit:git-log[1] for details.
+ Defaults to `separate`.
+
log.follow::
If `true`, `git log` will act as if the `--follow` option was used when
a single <path> is given. This has the same limitations as `--follow`,
diff --git a/Documentation/config/pack.txt b/Documentation/config/pack.txt
index 3da4ea98e2..c0844d8d8e 100644
--- a/Documentation/config/pack.txt
+++ b/Documentation/config/pack.txt
@@ -122,6 +122,21 @@ pack.useSparse::
commits contain certain types of direct renames. Default is
`true`.
+pack.preferBitmapTips::
+ When selecting which commits will receive bitmaps, prefer a
+ commit at the tip of any reference that is a suffix of any value
+ of this configuration over any other commits in the "selection
+ window".
++
+Note that setting this configuration to `refs/foo` does not mean that
+the commits at the tips of `refs/foo/bar` and `refs/foo/baz` will
+necessarily be selected. This is because commits are selected for
+bitmaps from within a series of windows of variable length.
++
+If a commit at the tip of any reference which is a suffix of any value
+of this configuration is seen in a window, it is immediately given
+preference over any other commit in that window.
+
pack.writeBitmaps (deprecated)::
This is a deprecated synonym for `repack.writeBitmaps`.
diff --git a/Documentation/config/push.txt b/Documentation/config/push.txt
index 21b256e0a4..f2667b2689 100644
--- a/Documentation/config/push.txt
+++ b/Documentation/config/push.txt
@@ -120,3 +120,10 @@ push.useForceIfIncludes::
`--force-if-includes` as an option to linkgit:git-push[1]
in the command line. Adding `--no-force-if-includes` at the
time of push overrides this configuration setting.
+
+push.negotiate::
+ If set to "true", attempt to reduce the size of the packfile
+ sent by rounds of negotiation in which the client and the
+ server attempt to find commits in common. If "false", Git will
+ rely solely on the server's ref advertisement to find commits
+ in common.
diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt
index 214f31b451..8c979cb20f 100644
--- a/Documentation/config/rebase.txt
+++ b/Documentation/config/rebase.txt
@@ -1,10 +1,3 @@
-rebase.useBuiltin::
- Unused configuration variable. Used in Git versions 2.20 and
- 2.21 as an escape hatch to enable the legacy shellscript
- implementation of rebase. Now the built-in rewrite of it in C
- is always used. Setting this will emit a warning, to alert any
- remaining users that setting this now does nothing.
-
rebase.backend::
Default backend to use for rebasing. Possible choices are
'apply' or 'merge'. In the future, if the merge backend gains
diff --git a/Documentation/config/stash.txt b/Documentation/config/stash.txt
index 00eb35434e..9ed775281f 100644
--- a/Documentation/config/stash.txt
+++ b/Documentation/config/stash.txt
@@ -5,6 +5,11 @@ stash.useBuiltin::
is always used. Setting this will emit a warning, to alert any
remaining users that setting this now does nothing.
+stash.showIncludeUntracked::
+ If this is set to true, the `git stash show` command will show
+ the untracked files of a stash entry. Defaults to false. See
+ description of 'show' command in linkgit:git-stash[1].
+
stash.showPatch::
If this is set to true, the `git stash show` command without an
option will show the stash entry in patch form. Defaults to false.
diff --git a/Documentation/config/uploadpack.txt b/Documentation/config/uploadpack.txt
index b0d761282c..32fad5bbe8 100644
--- a/Documentation/config/uploadpack.txt
+++ b/Documentation/config/uploadpack.txt
@@ -59,15 +59,16 @@ uploadpack.allowFilter::
uploadpackfilter.allow::
Provides a default value for unspecified object filters (see: the
- below configuration variable).
+ below configuration variable). If set to `true`, this will also
+ enable all filters which get added in the future.
Defaults to `true`.
uploadpackfilter.<filter>.allow::
Explicitly allow or ban the object filter corresponding to
`<filter>`, where `<filter>` may be one of: `blob:none`,
- `blob:limit`, `tree`, `sparse:oid`, or `combine`. If using
- combined filters, both `combine` and all of the nested filter
- kinds must be allowed. Defaults to `uploadpackfilter.allow`.
+ `blob:limit`, `object:type`, `tree`, `sparse:oid`, or `combine`.
+ If using combined filters, both `combine` and all of the nested
+ filter kinds must be allowed. Defaults to `uploadpackfilter.allow`.
uploadpackfilter.tree.maxDepth::
Only allow `--filter=tree:<n>` when `<n>` is no more than the value of
diff --git a/Documentation/diff-generate-patch.txt b/Documentation/diff-generate-patch.txt
index 2db8eacc3e..c78063d4f7 100644
--- a/Documentation/diff-generate-patch.txt
+++ b/Documentation/diff-generate-patch.txt
@@ -11,7 +11,7 @@ linkgit:git-diff-files[1]
with the `-p` option produces patch text.
You can customize the creation of patch text via the
`GIT_EXTERNAL_DIFF` and the `GIT_DIFF_OPTS` environment variables
-(see linkgit:git[1]).
+(see linkgit:git[1]), and the `diff` attribute (see linkgit:gitattributes[5]).
What the -p option produces is slightly different from the traditional
diff format:
@@ -74,6 +74,11 @@ separate lines indicate the old and the new mode.
rename from b
rename to a
+5. Hunk headers mention the name of the function to which the hunk
+ applies. See "Defining a custom hunk-header" in
+ linkgit:gitattributes[5] for details of how to tailor to this to
+ specific languages.
+
Combined diff format
--------------------
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index aa2b5c11f2..530d115914 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -34,7 +34,7 @@ endif::git-diff[]
endif::git-format-patch[]
ifdef::git-log[]
---diff-merges=(off|none|first-parent|1|separate|m|combined|c|dense-combined|cc)::
+--diff-merges=(off|none|on|first-parent|1|separate|m|combined|c|dense-combined|cc)::
--no-diff-merges::
Specify diff format to be used for merge commits. Default is
{diff-merges-default} unless `--first-parent` is in use, in which case
@@ -45,17 +45,24 @@ ifdef::git-log[]
Disable output of diffs for merge commits. Useful to override
implied value.
+
+--diff-merges=on:::
+--diff-merges=m:::
+-m:::
+ This option makes diff output for merge commits to be shown in
+ the default format. `-m` will produce the output only if `-p`
+ is given as well. The default format could be changed using
+ `log.diffMerges` configuration parameter, which default value
+ is `separate`.
++
--diff-merges=first-parent:::
--diff-merges=1:::
This option makes merge commits show the full diff with
respect to the first parent only.
+
--diff-merges=separate:::
---diff-merges=m:::
--m:::
This makes merge commits show the full diff with respect to
each of the parents. Separate log entry and diff is generated
- for each parent. `-m` doesn't produce any output without `-p`.
+ for each parent.
+
--diff-merges=combined:::
--diff-merges=c:::
@@ -293,11 +300,14 @@ explained for the configuration variable `core.quotePath` (see
linkgit:git-config[1]).
--name-only::
- Show only names of changed files.
+ Show only names of changed files. The file names are often encoded in UTF-8.
+ For more information see the discussion about encoding in the linkgit:git-log[1]
+ manual page.
--name-status::
Show only names and status of changed files. See the description
of the `--diff-filter` option on what the status letters mean.
+ Just like `--name-only` the file names are often encoded in UTF-8.
--submodule[=<format>]::
Specify how differences in submodules are shown. When specifying
diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt
index 07783deee3..9e7b4e189c 100644
--- a/Documentation/fetch-options.txt
+++ b/Documentation/fetch-options.txt
@@ -110,6 +110,11 @@ ifndef::git-pull[]
setting `fetch.writeCommitGraph`.
endif::git-pull[]
+--prefetch::
+ Modify the configured refspec to place all refs into the
+ `refs/prefetch/` namespace. See the `prefetch` task in
+ linkgit:git-maintenance[1].
+
-p::
--prune::
Before fetching, remove any remote-tracking references that no
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index decd8ae122..8714dfcb76 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -15,6 +15,7 @@ SYNOPSIS
[--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>]
[--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
[--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
+ [--quoted-cr=<action>]
[(<mbox> | <Maildir>)...]
'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
@@ -59,6 +60,9 @@ OPTIONS
--no-scissors::
Ignore scissors lines (see linkgit:git-mailinfo[1]).
+--quoted-cr=<action>::
+ This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).
+
-m::
--message-id::
Pass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),
diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt
index 91d9a8601c..aa1ae56a25 100644
--- a/Documentation/git-apply.txt
+++ b/Documentation/git-apply.txt
@@ -84,12 +84,13 @@ OPTIONS
-3::
--3way::
- When the patch does not apply cleanly, fall back on 3-way merge if
- the patch records the identity of blobs it is supposed to apply to,
- and we have those blobs available locally, possibly leaving the
+ Attempt 3-way merge if the patch records the identity of blobs it is supposed
+ to apply to and we have those blobs available locally, possibly leaving the
conflict markers in the files in the working tree for the user to
- resolve. This option implies the `--index` option, and is incompatible
- with the `--reject` and the `--cached` options.
+ resolve. This option implies the `--index` option unless the
+ `--cached` option is used, and is incompatible with the `--reject` option.
+ When used with the `--cached` option, any conflicts are left at higher stages
+ in the cache.
--build-fake-ancestor=<file>::
Newer 'git diff' output has embedded 'index information'
diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt
index 8e192d87db..4eb0421b3f 100644
--- a/Documentation/git-cat-file.txt
+++ b/Documentation/git-cat-file.txt
@@ -35,42 +35,42 @@ OPTIONS
-t::
Instead of the content, show the object type identified by
- <object>.
+ `<object>`.
-s::
Instead of the content, show the object size identified by
- <object>.
+ `<object>`.
-e::
- Exit with zero status if <object> exists and is a valid
- object. If <object> is of an invalid format exit with non-zero and
+ Exit with zero status if `<object>` exists and is a valid
+ object. If `<object>` is of an invalid format exit with non-zero and
emits an error on stderr.
-p::
- Pretty-print the contents of <object> based on its type.
+ Pretty-print the contents of `<object>` based on its type.
<type>::
- Typically this matches the real type of <object> but asking
+ Typically this matches the real type of `<object>` but asking
for a type that can trivially be dereferenced from the given
- <object> is also permitted. An example is to ask for a
- "tree" with <object> being a commit object that contains it,
- or to ask for a "blob" with <object> being a tag object that
+ `<object>` is also permitted. An example is to ask for a
+ "tree" with `<object>` being a commit object that contains it,
+ or to ask for a "blob" with `<object>` being a tag object that
points at it.
--textconv::
Show the content as transformed by a textconv filter. In this case,
- <object> has to be of the form <tree-ish>:<path>, or :<path> in
+ `<object>` has to be of the form `<tree-ish>:<path>`, or `:<path>` in
order to apply the filter to the content recorded in the index at
- <path>.
+ `<path>`.
--filters::
Show the content as converted by the filters configured in
- the current working tree for the given <path> (i.e. smudge filters,
- end-of-line conversion, etc). In this case, <object> has to be of
- the form <tree-ish>:<path>, or :<path>.
+ the current working tree for the given `<path>` (i.e. smudge filters,
+ end-of-line conversion, etc). In this case, `<object>` has to be of
+ the form `<tree-ish>:<path>`, or `:<path>`.
--path=<path>::
- For use with --textconv or --filters, to allow specifying an object
+ For use with `--textconv` or `--filters`, to allow specifying an object
name and a path separately, e.g. when it is difficult to figure out
the revision from which the blob came.
@@ -115,15 +115,15 @@ OPTIONS
repository.
--allow-unknown-type::
- Allow -s or -t to query broken/corrupt objects of unknown type.
+ Allow `-s` or `-t` to query broken/corrupt objects of unknown type.
--follow-symlinks::
- With --batch or --batch-check, follow symlinks inside the
+ With `--batch` or `--batch-check`, follow symlinks inside the
repository when requesting objects with extended SHA-1
expressions of the form tree-ish:path-in-tree. Instead of
providing output about the link itself, provide output about
the linked-to object. If a symlink points outside the
- tree-ish (e.g. a link to /foo or a root-level link to ../foo),
+ tree-ish (e.g. a link to `/foo` or a root-level link to `../foo`),
the portion of the link which is outside the tree will be
printed.
+
@@ -175,15 +175,15 @@ respectively print:
OUTPUT
------
-If `-t` is specified, one of the <type>.
+If `-t` is specified, one of the `<type>`.
-If `-s` is specified, the size of the <object> in bytes.
+If `-s` is specified, the size of the `<object>` in bytes.
-If `-e` is specified, no output, unless the <object> is malformed.
+If `-e` is specified, no output, unless the `<object>` is malformed.
-If `-p` is specified, the contents of <object> are pretty-printed.
+If `-p` is specified, the contents of `<object>` are pretty-printed.
-If <type> is specified, the raw (though uncompressed) contents of the <object>
+If `<type>` is specified, the raw (though uncompressed) contents of the `<object>`
will be returned.
BATCH OUTPUT
@@ -200,7 +200,7 @@ object, with placeholders of the form `%(atom)` expanded, followed by a
newline. The available atoms are:
`objectname`::
- The 40-hex object name of the object.
+ The full hex representation of the object name.
`objecttype`::
The type of the object (the same as `cat-file -t` reports).
@@ -215,8 +215,9 @@ newline. The available atoms are:
`deltabase`::
If the object is stored as a delta on-disk, this expands to the
- 40-hex sha1 of the delta base object. Otherwise, expands to the
- null sha1 (40 zeroes). See `CAVEATS` below.
+ full hex representation of the delta base object name.
+ Otherwise, expands to the null OID (all zeroes). See `CAVEATS`
+ below.
`rest`::
If this atom is used in the output string, input lines are split
@@ -235,14 +236,14 @@ newline.
For example, `--batch` without a custom format would produce:
------------
-<sha1> SP <type> SP <size> LF
+<oid> SP <type> SP <size> LF
<contents> LF
------------
Whereas `--batch-check='%(objectname) %(objecttype)'` would produce:
------------
-<sha1> SP <type> LF
+<oid> SP <type> LF
------------
If a name is specified on stdin that cannot be resolved to an object in
@@ -258,7 +259,7 @@ If a name is specified that might refer to more than one object (an ambiguous sh
<object> SP ambiguous LF
------------
-If --follow-symlinks is used, and a symlink in the repository points
+If `--follow-symlinks` is used, and a symlink in the repository points
outside the repository, then `cat-file` will ignore any custom format
and print:
@@ -267,11 +268,11 @@ symlink SP <size> LF
<symlink> LF
------------
-The symlink will either be absolute (beginning with a /), or relative
-to the tree root. For instance, if dir/link points to ../../foo, then
-<symlink> will be ../foo. <size> is the size of the symlink in bytes.
+The symlink will either be absolute (beginning with a `/`), or relative
+to the tree root. For instance, if dir/link points to `../../foo`, then
+`<symlink>` will be `../foo`. `<size>` is the size of the symlink in bytes.
-If --follow-symlinks is used, the following error messages will be
+If `--follow-symlinks` is used, the following error messages will be
displayed:
------------
diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index 02d9c19cec..3fe3810f1c 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -15,7 +15,7 @@ SYNOPSIS
[--dissociate] [--separate-git-dir <git dir>]
[--depth <depth>] [--[no-]single-branch] [--no-tags]
[--recurse-submodules[=<pathspec>]] [--[no-]shallow-submodules]
- [--[no-]remote-submodules] [--jobs <n>] [--sparse]
+ [--[no-]remote-submodules] [--jobs <n>] [--sparse] [--[no-]reject-shallow]
[--filter=<filter>] [--] <repository>
[<directory>]
@@ -149,6 +149,11 @@ objects from the source repository into a pack in the cloned repository.
--no-checkout::
No checkout of HEAD is performed after the clone is complete.
+--[no-]reject-shallow::
+ Fail if the source repository is a shallow repository.
+ The 'clone.rejectShallow' configuration variable can be used to
+ specify the default.
+
--bare::
Make a 'bare' Git repository. That is, instead of
creating `<directory>` and placing the administrative
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 17150fa7ea..340c5fbb48 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -9,12 +9,13 @@ SYNOPSIS
--------
[verse]
'git commit' [-a | --interactive | --patch] [-s] [-v] [-u<mode>] [--amend]
- [--dry-run] [(-c | -C | --fixup | --squash) <commit>]
+ [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>)]
[-F <file> | -m <msg>] [--reset-author] [--allow-empty]
[--allow-empty-message] [--no-verify] [-e] [--author=<author>]
[--date=<date>] [--cleanup=<mode>] [--[no-]status]
[-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
- [-S[<keyid>]] [--] [<pathspec>...]
+ [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]
+ [--] [<pathspec>...]
DESCRIPTION
-----------
@@ -86,11 +87,44 @@ OPTIONS
Like '-C', but with `-c` the editor is invoked, so that
the user can further edit the commit message.
---fixup=<commit>::
- Construct a commit message for use with `rebase --autosquash`.
- The commit message will be the subject line from the specified
- commit with a prefix of "fixup! ". See linkgit:git-rebase[1]
- for details.
+--fixup=[(amend|reword):]<commit>::
+ Create a new commit which "fixes up" `<commit>` when applied with
+ `git rebase --autosquash`. Plain `--fixup=<commit>` creates a
+ "fixup!" commit which changes the content of `<commit>` but leaves
+ its log message untouched. `--fixup=amend:<commit>` is similar but
+ creates an "amend!" commit which also replaces the log message of
+ `<commit>` with the log message of the "amend!" commit.
+ `--fixup=reword:<commit>` creates an "amend!" commit which
+ replaces the log message of `<commit>` with its own log message
+ but makes no changes to the content of `<commit>`.
++
+The commit created by plain `--fixup=<commit>` has a subject
+composed of "fixup!" followed by the subject line from <commit>,
+and is recognized specially by `git rebase --autosquash`. The `-m`
+option may be used to supplement the log message of the created
+commit, but the additional commentary will be thrown away once the
+"fixup!" commit is squashed into `<commit>` by
+`git rebase --autosquash`.
++
+The commit created by `--fixup=amend:<commit>` is similar but its
+subject is instead prefixed with "amend!". The log message of
+<commit> is copied into the log message of the "amend!" commit and
+opened in an editor so it can be refined. When `git rebase
+--autosquash` squashes the "amend!" commit into `<commit>`, the
+log message of `<commit>` is replaced by the refined log message
+from the "amend!" commit. It is an error for the "amend!" commit's
+log message to be empty unless `--allow-empty-message` is
+specified.
++
+`--fixup=reword:<commit>` is shorthand for `--fixup=amend:<commit>
+--only`. It creates an "amend!" commit with only a log message
+(ignoring any changes staged in the index). When squashed by `git
+rebase --autosquash`, it replaces the log message of `<commit>`
+without making any other changes.
++
+Neither "fixup!" nor "amend!" commits change authorship of
+`<commit>` when applied by `git rebase --autosquash`.
+See linkgit:git-rebase[1] for details.
--squash=<commit>::
Construct a commit message for use with `rebase --autosquash`.
@@ -166,6 +200,17 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.
include::signoff-option.txt[]
+--trailer <token>[(=|:)<value>]::
+ Specify a (<token>, <value>) pair that should be applied as a
+ trailer. (e.g. `git commit --trailer "Signed-off-by:C O Mitter \
+ <committer@example.com>" --trailer "Helped-by:C O Mitter \
+ <committer@example.com>"` will add the "Signed-off-by" trailer
+ and the "Helped-by" trailer to the commit message.)
+ The `trailer.*` configuration variables
+ (linkgit:git-interpret-trailers[1]) can be used to define if
+ a duplicated trailer is omitted, where in the run of trailers
+ each trailer would appear, and other details.
+
-n::
--no-verify::
This option bypasses the pre-commit and commit-msg hooks.
diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt
index 4b4cc5c5e8..5cddadafd2 100644
--- a/Documentation/git-config.txt
+++ b/Documentation/git-config.txt
@@ -340,6 +340,11 @@ GIT_CONFIG::
Using the "--global" option forces this to ~/.gitconfig. Using the
"--system" option forces this to $(prefix)/etc/gitconfig.
+GIT_CONFIG_GLOBAL::
+GIT_CONFIG_SYSTEM::
+ Take the configuration from the given files instead from global or
+ system-level configuration. See linkgit:git[1] for details.
+
GIT_CONFIG_NOSYSTEM::
Whether to skip reading settings from the system-wide
$(prefix)/etc/gitconfig file. See linkgit:git[1] for details.
diff --git a/Documentation/git-credential.txt b/Documentation/git-credential.txt
index 31c81c4c02..206e3c5f40 100644
--- a/Documentation/git-credential.txt
+++ b/Documentation/git-credential.txt
@@ -159,3 +159,7 @@ empty string.
+
Components which are missing from the URL (e.g., there is no
username in the example above) will be left unset.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt
index 1b1c71ad9d..f2e4a47ebe 100644
--- a/Documentation/git-cvsserver.txt
+++ b/Documentation/git-cvsserver.txt
@@ -24,6 +24,18 @@ Usage:
[verse]
'git-cvsserver' [<options>] [pserver|server] [<directory> ...]
+DESCRIPTION
+-----------
+
+This application is a CVS emulation layer for Git.
+
+It is highly functional. However, not all methods are implemented,
+and for those methods that are implemented,
+not all switches are implemented.
+
+Testing has been done using both the CLI CVS client, and the Eclipse CVS
+plugin. Most functionality works fine with both of these clients.
+
OPTIONS
-------
@@ -57,18 +69,6 @@ access still needs to be enabled by the `gitcvs.enabled` config option
unless `--export-all` was given, too.
-DESCRIPTION
------------
-
-This application is a CVS emulation layer for Git.
-
-It is highly functional. However, not all methods are implemented,
-and for those methods that are implemented,
-not all switches are implemented.
-
-Testing has been done using both the CLI CVS client, and the Eclipse CVS
-plugin. Most functionality works fine with both of these clients.
-
LIMITATIONS
-----------
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 3e49bf2210..fe2f69d36e 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -36,11 +36,28 @@ SYNOPSIS
DESCRIPTION
-----------
-Prepare each commit with its patch in
-one file per commit, formatted to resemble UNIX mailbox format.
+Prepare each non-merge commit with its "patch" in
+one "message" per commit, formatted to resemble a UNIX mailbox.
The output of this command is convenient for e-mail submission or
for use with 'git am'.
+A "message" generated by the command consists of three parts:
+
+* A brief metadata header that begins with `From <commit>`
+ with a fixed `Mon Sep 17 00:00:00 2001` datestamp to help programs
+ like "file(1)" to recognize that the file is an output from this
+ command, fields that record the author identity, the author date,
+ and the title of the change (taken from the first paragraph of the
+ commit log message).
+
+* The second and subsequent paragraphs of the commit log message.
+
+* The "patch", which is the "diff -p --stat" output (see
+ linkgit:git-diff[1]) between the commit and its parent.
+
+The log message and the patch is separated by a line with a
+three-dash line.
+
There are two ways to specify which commits to operate on.
1. A single commit, <since>, specifies that the commits leading
@@ -221,6 +238,11 @@ populated with placeholder text.
`--subject-prefix` option) has ` v<n>` appended to it. E.g.
`--reroll-count=4` may produce `v4-0001-add-makefile.patch`
file that has "Subject: [PATCH v4 1/20] Add makefile" in it.
+ `<n>` does not have to be an integer (e.g. "--reroll-count=4.4",
+ or "--reroll-count=4rev2" are allowed), but the downside of
+ using such a reroll-count is that the range-diff/interdiff
+ with the previous version does not state exactly which
+ version the new interation is compared against.
--to=<email>::
Add a `To:` header to the email headers. This is in addition
@@ -718,6 +740,14 @@ use it only when you know the recipient uses Git to apply your patch.
$ git format-patch -3
------------
+CAVEATS
+-------
+
+Note that `format-patch` will omit merge commits from the output, even
+if they are part of the requested range. A simple "patch" does not
+include enough information for the receiving end to reproduce the same
+merge commit.
+
SEE ALSO
--------
linkgit:git-am[1], linkgit:git-send-email[1]
diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt
index 4e0ba8234a..3d393fbac1 100644
--- a/Documentation/git-grep.txt
+++ b/Documentation/git-grep.txt
@@ -38,38 +38,6 @@ are lists of one or more search expressions separated by newline
characters. An empty string as search expression matches all lines.
-CONFIGURATION
--------------
-
-grep.lineNumber::
- If set to true, enable `-n` option by default.
-
-grep.column::
- If set to true, enable the `--column` option by default.
-
-grep.patternType::
- Set the default matching behavior. Using a value of 'basic', 'extended',
- 'fixed', or 'perl' will enable the `--basic-regexp`, `--extended-regexp`,
- `--fixed-strings`, or `--perl-regexp` option accordingly, while the
- value 'default' will return to the default matching behavior.
-
-grep.extendedRegexp::
- If set to true, enable `--extended-regexp` option by default. This
- option is ignored when the `grep.patternType` option is set to a value
- other than 'default'.
-
-grep.threads::
- Number of grep worker threads to use. If unset (or set to 0), Git will
- use as many threads as the number of logical cores available.
-
-grep.fullName::
- If set to true, enable `--full-name` option by default.
-
-grep.fallbackToNoIndex::
- If set to true, fall back to git grep --no-index if git grep
- is executed outside of a git repository. Defaults to false.
-
-
OPTIONS
-------
--cached::
@@ -363,6 +331,38 @@ with multiple threads might perform slower than single threaded if `--textconv`
is given and there're too many text conversions. So if you experience low
performance in this case, it might be desirable to use `--threads=1`.
+CONFIGURATION
+-------------
+
+grep.lineNumber::
+ If set to true, enable `-n` option by default.
+
+grep.column::
+ If set to true, enable the `--column` option by default.
+
+grep.patternType::
+ Set the default matching behavior. Using a value of 'basic', 'extended',
+ 'fixed', or 'perl' will enable the `--basic-regexp`, `--extended-regexp`,
+ `--fixed-strings`, or `--perl-regexp` option accordingly, while the
+ value 'default' will return to the default matching behavior.
+
+grep.extendedRegexp::
+ If set to true, enable `--extended-regexp` option by default. This
+ option is ignored when the `grep.patternType` option is set to a value
+ other than 'default'.
+
+grep.threads::
+ Number of grep worker threads to use. If unset (or set to 0), Git will
+ use as many threads as the number of logical cores available.
+
+grep.fullName::
+ If set to true, enable `--full-name` option by default.
+
+grep.fallbackToNoIndex::
+ If set to true, fall back to git grep --no-index if git grep
+ is executed outside of a git repository. Defaults to false.
+
+
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
index 96ec6499f0..956a01d184 100644
--- a/Documentation/git-interpret-trailers.txt
+++ b/Documentation/git-interpret-trailers.txt
@@ -232,25 +232,38 @@ trailer.<token>.ifmissing::
that option for trailers with the specified <token>.
trailer.<token>.command::
- This option can be used to specify a shell command that will
- be called to automatically add or modify a trailer with the
- specified <token>.
+ This option behaves in the same way as 'trailer.<token>.cmd', except
+ that it doesn't pass anything as argument to the specified command.
+ Instead the first occurrence of substring $ARG is replaced by the
+ value that would be passed as argument.
+
-When this option is specified, the behavior is as if a special
-'<token>=<value>' argument were added at the beginning of the command
-line, where <value> is taken to be the standard output of the
-specified command with any leading and trailing whitespace trimmed
-off.
+The 'trailer.<token>.command' option has been deprecated in favor of
+'trailer.<token>.cmd' due to the fact that $ARG in the user's command is
+only replaced once and that the original way of replacing $ARG is not safe.
+
-If the command contains the `$ARG` string, this string will be
-replaced with the <value> part of an existing trailer with the same
-<token>, if any, before the command is launched.
+When both 'trailer.<token>.cmd' and 'trailer.<token>.command' are given
+for the same <token>, 'trailer.<token>.cmd' is used and
+'trailer.<token>.command' is ignored.
+
+trailer.<token>.cmd::
+ This option can be used to specify a shell command that will be called:
+ once to automatically add a trailer with the specified <token>, and then
+ each time a '--trailer <token>=<value>' argument to modify the <value> of
+ the trailer that this option would produce.
+
-If some '<token>=<value>' arguments are also passed on the command
-line, when a 'trailer.<token>.command' is configured, the command will
-also be executed for each of these arguments. And the <value> part of
-these arguments, if any, will be used to replace the `$ARG` string in
-the command.
+When the specified command is first called to add a trailer
+with the specified <token>, the behavior is as if a special
+'--trailer <token>=<value>' argument was added at the beginning
+of the "git interpret-trailers" command, where <value>
+is taken to be the standard output of the command with any
+leading and trailing whitespace trimmed off.
++
+If some '--trailer <token>=<value>' arguments are also passed
+on the command line, the command is called again once for each
+of these arguments with the same <token>. And the <value> part
+of these arguments, if any, will be passed to the command as its
+first argument. This way the command can produce a <value> computed
+from the <value> passed in the '--trailer <token>=<value>' argument.
EXAMPLES
--------
@@ -333,6 +346,55 @@ subject
Fix #42
------------
+* Configure a 'help' trailer with a cmd use a script `glog-find-author`
+ which search specified author identity from git log in git repository
+ and show how it works:
++
+------------
+$ cat ~/bin/glog-find-author
+#!/bin/sh
+test -n "$1" && git log --author="$1" --pretty="%an <%ae>" -1 || true
+$ git config trailer.help.key "Helped-by: "
+$ git config trailer.help.ifExists "addIfDifferentNeighbor"
+$ git config trailer.help.cmd "~/bin/glog-find-author"
+$ git interpret-trailers --trailer="help:Junio" --trailer="help:Couder" <<EOF
+> subject
+>
+> message
+>
+> EOF
+subject
+
+message
+
+Helped-by: Junio C Hamano <gitster@pobox.com>
+Helped-by: Christian Couder <christian.couder@gmail.com>
+------------
+
+* Configure a 'ref' trailer with a cmd use a script `glog-grep`
+ to grep last relevant commit from git log in the git repository
+ and show how it works:
++
+------------
+$ cat ~/bin/glog-grep
+#!/bin/sh
+test -n "$1" && git log --grep "$1" --pretty=reference -1 || true
+$ git config trailer.ref.key "Reference-to: "
+$ git config trailer.ref.ifExists "replace"
+$ git config trailer.ref.cmd "~/bin/glog-grep"
+$ git interpret-trailers --trailer="ref:Add copyright notices." <<EOF
+> subject
+>
+> message
+>
+> EOF
+subject
+
+message
+
+Reference-to: 8bc9a0c769 (Add copyright notices., 2005-04-07)
+------------
+
* Configure a 'see' trailer with a command to show the subject of a
commit that is related, and show how it works:
+
diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt
index d343f040f5..3fcfd965fd 100644
--- a/Documentation/git-mailinfo.txt
+++ b/Documentation/git-mailinfo.txt
@@ -9,7 +9,9 @@ git-mailinfo - Extracts patch and authorship from a single e-mail message
SYNOPSIS
--------
[verse]
-'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n] [--[no-]scissors] <msg> <patch>
+'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n]
+ [--[no-]scissors] [--quoted-cr=<action>]
+ <msg> <patch>
DESCRIPTION
@@ -89,6 +91,23 @@ This can be enabled by default with the configuration option mailinfo.scissors.
--no-scissors::
Ignore scissors lines. Useful for overriding mailinfo.scissors settings.
+--quoted-cr=<action>::
+ Action when processes email messages sent with base64 or
+ quoted-printable encoding, and the decoded lines end with a CRLF
+ instead of a simple LF.
++
+The valid actions are:
++
+--
+* `nowarn`: Git will do nothing when such a CRLF is found.
+* `warn`: Git will issue a warning for each message if such a CRLF is
+ found.
+* `strip`: Git will convert those CRLF to LF.
+--
++
+The default action could be set by configuration option `mailinfo.quotedCR`.
+If no such configuration option has been set, `warn` will be used.
+
<msg>::
The commit log message extracted from e-mail, usually
except the title line which comes from e-mail Subject.
diff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt
index 80ddd33ceb..1e738ad398 100644
--- a/Documentation/git-maintenance.txt
+++ b/Documentation/git-maintenance.txt
@@ -92,10 +92,8 @@ commit-graph::
prefetch::
The `prefetch` task updates the object directory with the latest
objects from all registered remotes. For each remote, a `git fetch`
- command is run. The refmap is custom to avoid updating local or remote
- branches (those in `refs/heads` or `refs/remotes`). Instead, the
- remote refs are stored in `refs/prefetch/<remote>/`. Also, tags are
- not updated.
+ command is run. The configured refspec is modified to place all
+ requested refs within `refs/prefetch/`. Also, tags are not updated.
+
This is done to avoid disrupting the remote-tracking branches. The end users
expect these refs to stay unmoved unless they initiate a fetch. With prefetch
diff --git a/Documentation/git-mktag.txt b/Documentation/git-mktag.txt
index 17a2603a60..466a697519 100644
--- a/Documentation/git-mktag.txt
+++ b/Documentation/git-mktag.txt
@@ -11,14 +11,6 @@ SYNOPSIS
[verse]
'git mktag'
-OPTIONS
--------
-
---strict::
- By default mktag turns on the equivalent of
- linkgit:git-fsck[1] `--strict` mode. Use `--no-strict` to
- disable it.
-
DESCRIPTION
-----------
@@ -45,6 +37,14 @@ the appropriate `fsck.<msg-id>` varible:
git -c fsck.extraHeaderEntry=ignore mktag <my-tag-with-headers
+OPTIONS
+-------
+
+--strict::
+ By default mktag turns on the equivalent of
+ linkgit:git-fsck[1] `--strict` mode. Use `--no-strict` to
+ disable it.
+
Tag Format
----------
A tag signature file, to be fed to this command's standard input,
diff --git a/Documentation/git-multi-pack-index.txt b/Documentation/git-multi-pack-index.txt
index eb0caa0439..ffd601bc17 100644
--- a/Documentation/git-multi-pack-index.txt
+++ b/Documentation/git-multi-pack-index.txt
@@ -9,7 +9,8 @@ git-multi-pack-index - Write and verify multi-pack-indexes
SYNOPSIS
--------
[verse]
-'git multi-pack-index' [--object-dir=<dir>] [--[no-]progress] <subcommand>
+'git multi-pack-index' [--object-dir=<dir>] [--[no-]progress]
+ [--preferred-pack=<pack>] <subcommand>
DESCRIPTION
-----------
@@ -30,7 +31,16 @@ OPTIONS
The following subcommands are available:
write::
- Write a new MIDX file.
+ Write a new MIDX file. The following options are available for
+ the `write` sub-command:
++
+--
+ --preferred-pack=<pack>::
+ Optionally specify the tie-breaking pack used when
+ multiple packs contain the same object. If not given,
+ ties are broken in favor of the pack with the lowest
+ mtime.
+--
verify::
Verify the contents of the MIDX file.
diff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt
index f89e68b424..38e5257b2a 100644
--- a/Documentation/git-p4.txt
+++ b/Documentation/git-p4.txt
@@ -762,3 +762,7 @@ IMPLEMENTATION DETAILS
message indicating the p4 depot location and change number. This
line is used by later 'git p4 sync' operations to know which p4
changes are new.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt
index f85cb7ea93..25d9fbe37a 100644
--- a/Documentation/git-pack-objects.txt
+++ b/Documentation/git-pack-objects.txt
@@ -85,6 +85,16 @@ base-name::
reference was included in the resulting packfile. This
can be useful to send new tags to native Git clients.
+--stdin-packs::
+ Read the basenames of packfiles (e.g., `pack-1234abcd.pack`)
+ from the standard input, instead of object names or revision
+ arguments. The resulting pack contains all objects listed in the
+ included packs (those not beginning with `^`), excluding any
+ objects listed in the excluded packs (beginning with `^`).
++
+Incompatible with `--revs`, or options that imply `--revs` (such as
+`--all`), with the exception of `--unpacked`, which is compatible.
+
--window=<n>::
--depth=<n>::
These two options affect how the objects contained in
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index ab103c82cf..a953c7c387 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -600,7 +600,7 @@ EXAMPLES
`git push origin`::
Without additional configuration, pushes the current branch to
- the configured upstream (`remote.origin.merge` configuration
+ the configured upstream (`branch.<name>.merge` configuration
variable) if it has the same name as the current branch, and
errors out without pushing otherwise.
+
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index a0487b5cc5..55af6fd24e 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -200,12 +200,6 @@ Alternatively, you can undo the 'git rebase' with
git rebase --abort
-CONFIGURATION
--------------
-
-include::config/rebase.txt[]
-include::config/sequencer.txt[]
-
OPTIONS
-------
--onto <newbase>::
@@ -593,16 +587,17 @@ See also INCOMPATIBLE OPTIONS below.
--autosquash::
--no-autosquash::
- When the commit log message begins with "squash! ..." (or
- "fixup! ..."), and there is already a commit in the todo list that
- matches the same `...`, automatically modify the todo list of rebase
- -i so that the commit marked for squashing comes right after the
- commit to be modified, and change the action of the moved commit
- from `pick` to `squash` (or `fixup`). A commit matches the `...` if
- the commit subject matches, or if the `...` refers to the commit's
- hash. As a fall-back, partial matches of the commit subject work,
- too. The recommended way to create fixup/squash commits is by using
- the `--fixup`/`--squash` options of linkgit:git-commit[1].
+ When the commit log message begins with "squash! ..." or "fixup! ..."
+ or "amend! ...", and there is already a commit in the todo list that
+ matches the same `...`, automatically modify the todo list of
+ `rebase -i`, so that the commit marked for squashing comes right after
+ the commit to be modified, and change the action of the moved commit
+ from `pick` to `squash` or `fixup` or `fixup -C` respectively. A commit
+ matches the `...` if the commit subject matches, or if the `...` refers
+ to the commit's hash. As a fall-back, partial matches of the commit
+ subject work, too. The recommended way to create fixup/amend/squash
+ commits is by using the `--fixup`, `--fixup=amend:` or `--fixup=reword:`
+ and `--squash` options respectively of linkgit:git-commit[1].
+
If the `--autosquash` option is enabled by default using the
configuration variable `rebase.autoSquash`, this option can be
@@ -622,6 +617,14 @@ See also INCOMPATIBLE OPTIONS below.
--no-reschedule-failed-exec::
Automatically reschedule `exec` commands that failed. This only makes
sense in interactive mode (or when an `--exec` option was provided).
++
+Even though this option applies once a rebase is started, it's set for
+the whole rebase at the start based on either the
+`rebase.rescheduleFailedExec` configuration (see linkgit:git-config[1]
+or "CONFIGURATION" below) or whether this option is
+provided. Otherwise an explicit `--no-reschedule-failed-exec` at the
+start would be overridden by the presence of
+`rebase.rescheduleFailedExec=true` configuration.
INCOMPATIBLE OPTIONS
--------------------
@@ -887,9 +890,17 @@ If you want to fold two or more commits into one, replace the command
"pick" for the second and subsequent commits with "squash" or "fixup".
If the commits had different authors, the folded commit will be
attributed to the author of the first commit. The suggested commit
-message for the folded commit is the concatenation of the commit
-messages of the first commit and of those with the "squash" command,
-but omits the commit messages of commits with the "fixup" command.
+message for the folded commit is the concatenation of the first
+commit's message with those identified by "squash" commands, omitting the
+messages of commits identified by "fixup" commands, unless "fixup -c"
+is used. In that case the suggested commit message is only the message
+of the "fixup -c" commit, and an editor is opened allowing you to edit
+the message. The contents (patch) of the "fixup -c" commit are still
+incorporated into the folded commit. If there is more than one "fixup -c"
+commit, the message from the final one is used. You can also use
+"fixup -C" to get the same behavior as "fixup -c" except without opening
+an editor.
+
'git rebase' will stop when "pick" has been replaced with "edit" or
when a command fails due to merge errors. When you are done editing
@@ -1257,6 +1268,12 @@ merge tlsv1.3
merge cmake
------------
+CONFIGURATION
+-------------
+
+include::config/rebase.txt[]
+include::config/sequencer.txt[]
+
BUGS
----
The todo list presented by the deprecated `--preserve-merges --interactive`
diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt
index fbd4b4ae06..ef310f362e 100644
--- a/Documentation/git-repack.txt
+++ b/Documentation/git-repack.txt
@@ -165,6 +165,29 @@ depth is 4095.
Pass the `--delta-islands` option to `git-pack-objects`, see
linkgit:git-pack-objects[1].
+-g=<factor>::
+--geometric=<factor>::
+ Arrange resulting pack structure so that each successive pack
+ contains at least `<factor>` times the number of objects as the
+ next-largest pack.
++
+`git repack` ensures this by determining a "cut" of packfiles that need
+to be repacked into one in order to ensure a geometric progression. It
+picks the smallest set of packfiles such that as many of the larger
+packfiles (by count of objects contained in that pack) may be left
+intact.
++
+Unlike other repack modes, the set of objects to pack is determined
+uniquely by the set of packs being "rolled-up"; in other words, the
+packs determined to need to be combined in order to restore a geometric
+progression.
++
+When `--unpacked` is specified, loose objects are implicitly included in
+this "roll-up", without respect to their reachability. This is subject
+to change in the future. This option (implying a drastically different
+repack mode) is not guaranteed to work with all other combinations of
+option to `git repack`.
+
CONFIGURATION
-------------
diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt
index ab750367fd..26e9b28470 100644
--- a/Documentation/git-rm.txt
+++ b/Documentation/git-rm.txt
@@ -23,7 +23,9 @@ branch, and no updates to their contents can be staged in the index,
though that default behavior can be overridden with the `-f` option.
When `--cached` is given, the staged content has to
match either the tip of the branch or the file on disk,
-allowing the file to be removed from just the index.
+allowing the file to be removed from just the index. When
+sparse-checkouts are in use (see linkgit:git-sparse-checkout[1]),
+`git rm` will only remove paths within the sparse-checkout patterns.
OPTIONS
diff --git a/Documentation/git-sparse-checkout.txt b/Documentation/git-sparse-checkout.txt
index a0eeaeb02e..fdcf43f87c 100644
--- a/Documentation/git-sparse-checkout.txt
+++ b/Documentation/git-sparse-checkout.txt
@@ -45,6 +45,20 @@ To avoid interfering with other worktrees, it first enables the
When `--cone` is provided, the `core.sparseCheckoutCone` setting is
also set, allowing for better performance with a limited set of
patterns (see 'CONE PATTERN SET' below).
++
+Use the `--[no-]sparse-index` option to toggle the use of the sparse
+index format. This reduces the size of the index to be more closely
+aligned with your sparse-checkout definition. This can have significant
+performance advantages for commands such as `git status` or `git add`.
+This feature is still experimental. Some commands might be slower with
+a sparse index until they are properly integrated with the feature.
++
+**WARNING:** Using a sparse index requires modifying the index in a way
+that is not completely understood by external tools. If you have trouble
+with this compatibility, then run `git sparse-checkout init --no-sparse-index`
+to rewrite your index to not be sparse. Older versions of Git will not
+understand the sparse directory entries index extension and may fail to
+interact with your repository until it is disabled.
'set'::
Write a set of patterns to the sparse-checkout file, as given as
diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt
index f1197d641b..be6084ccef 100644
--- a/Documentation/git-stash.txt
+++ b/Documentation/git-stash.txt
@@ -9,7 +9,7 @@ SYNOPSIS
--------
[verse]
'git stash' list [<log-options>]
-'git stash' show [<diff-options>] [<stash>]
+'git stash' show [-u|--include-untracked|--only-untracked] [<diff-options>] [<stash>]
'git stash' drop [-q|--quiet] [<stash>]
'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]
'git stash' branch <branchname> [<stash>]
@@ -83,7 +83,7 @@ stash@{1}: On master: 9cc0589... Add git-stash
The command takes options applicable to the 'git log'
command to control what is shown and how. See linkgit:git-log[1].
-show [<diff-options>] [<stash>]::
+show [-u|--include-untracked|--only-untracked] [<diff-options>] [<stash>]::
Show the changes recorded in the stash entry as a diff between the
stashed contents and the commit back when the stash entry was first
@@ -91,8 +91,10 @@ show [<diff-options>] [<stash>]::
By default, the command shows the diffstat, but it will accept any
format known to 'git diff' (e.g., `git stash show -p stash@{1}`
to view the second most recent entry in patch form).
- You can use stash.showStat and/or stash.showPatch config variables
- to change the default behavior.
+ If no `<diff-option>` is provided, the default behavior will be given
+ by the `stash.showStat`, and `stash.showPatch` config variables. You
+ can also use `stash.showIncludeUntracked` to set whether
+ `--include-untracked` is enabled by default.
pop [--index] [-q|--quiet] [<stash>]::
@@ -160,10 +162,18 @@ up with `git clean`.
-u::
--include-untracked::
- This option is only valid for `push` and `save` commands.
+--no-include-untracked::
+ When used with the `push` and `save` commands,
+ all untracked files are also stashed and then cleaned up with
+ `git clean`.
++
+When used with the `show` command, show the untracked files in the stash
+entry as part of the diff.
+
+--only-untracked::
+ This option is only valid for the `show` command.
+
-All untracked files are also stashed and then cleaned up with
-`git clean`.
+Show only the untracked files in the stash entry as part of the diff.
--index::
This option is only valid for `pop` and `apply` commands.
diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt
index 67b143cc81..d5776ffcfd 100644
--- a/Documentation/git-svn.txt
+++ b/Documentation/git-svn.txt
@@ -1061,25 +1061,6 @@ with different name spaces. For example:
branches = stable/*:refs/remotes/svn/stable/*
branches = debug/*:refs/remotes/svn/debug/*
-BUGS
-----
-
-We ignore all SVN properties except svn:executable. Any unhandled
-properties are logged to $GIT_DIR/svn/<refname>/unhandled.log
-
-Renamed and copied directories are not detected by Git and hence not
-tracked when committing to SVN. I do not plan on adding support for
-this as it's quite difficult and time-consuming to get working for all
-the possible corner cases (Git doesn't do it, either). Committing
-renamed and copied files is fully supported if they're similar enough
-for Git to detect them.
-
-In SVN, it is possible (though discouraged) to commit changes to a tag
-(because a tag is just a directory copy, thus technically the same as a
-branch). When cloning an SVN repository, 'git svn' cannot know if such a
-commit to a tag will happen in the future. Thus it acts conservatively
-and imports all SVN tags as branches, prefixing the tag name with 'tags/'.
-
CONFIGURATION
-------------
@@ -1166,6 +1147,25 @@ $GIT_DIR/svn/\**/.rev_map.*::
if it is missing or not up to date. 'git svn reset' automatically
rewinds it.
+BUGS
+----
+
+We ignore all SVN properties except svn:executable. Any unhandled
+properties are logged to $GIT_DIR/svn/<refname>/unhandled.log
+
+Renamed and copied directories are not detected by Git and hence not
+tracked when committing to SVN. I do not plan on adding support for
+this as it's quite difficult and time-consuming to get working for all
+the possible corner cases (Git doesn't do it, either). Committing
+renamed and copied files is fully supported if they're similar enough
+for Git to detect them.
+
+In SVN, it is possible (though discouraged) to commit changes to a tag
+(because a tag is just a directory copy, thus technically the same as a
+branch). When cloning an SVN repository, 'git svn' cannot know if such a
+commit to a tag will happen in the future. Thus it acts conservatively
+and imports all SVN tags as branches, prefixing the tag name with 'tags/'.
+
SEE ALSO
--------
linkgit:git-rebase[1]
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 3a9c44987f..6dd241ef83 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -13,7 +13,7 @@ SYNOPSIS
[--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
[-p|--paginate|-P|--no-pager] [--no-replace-objects] [--bare]
[--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
- [--super-prefix=<path>] [--config-env <name>=<envvar>]
+ [--super-prefix=<path>] [--config-env=<name>=<envvar>]
<command> [<args>]
DESCRIPTION
@@ -670,6 +670,16 @@ for further details.
If this environment variable is set to `0`, git will not prompt
on the terminal (e.g., when asking for HTTP authentication).
+`GIT_CONFIG_GLOBAL`::
+`GIT_CONFIG_SYSTEM`::
+ Take the configuration from the given files instead from global or
+ system-level configuration files. If `GIT_CONFIG_SYSTEM` is set, the
+ system config file defined at build time (usually `/etc/gitconfig`)
+ will not be read. Likewise, if `GIT_CONFIG_GLOBAL` is set, neither
+ `$HOME/.gitconfig` nor `$XDG_CONFIG_HOME/git/config` will be read. Can
+ be set to `/dev/null` to skip reading configuration files of the
+ respective level.
+
`GIT_CONFIG_NOSYSTEM`::
Whether to skip reading settings from the system-wide
`$(prefix)/etc/gitconfig` file. This environment variable can
diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index e84e104f93..83fd4e19a4 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -845,6 +845,8 @@ patterns are available:
- `rust` suitable for source code in the Rust language.
+- `scheme` suitable for source code in the Scheme language.
+
- `tex` suitable for source code for LaTeX documents.
@@ -1174,7 +1176,8 @@ tag then no replacement will be done. The placeholders are the same
as those for the option `--pretty=format:` of linkgit:git-log[1],
except that they need to be wrapped like this: `$Format:PLACEHOLDERS$`
in the file. E.g. the string `$Format:%H$` will be replaced by the
-commit hash.
+commit hash. However, only one `%(describe)` placeholder is expanded
+per archive to avoid denial-of-service attacks.
Packing objects
@@ -1244,6 +1247,12 @@ to:
[attr]binary -diff -merge -text
------------
+NOTES
+-----
+
+Git does not follow symbolic links when accessing a `.gitattributes`
+file in the working tree. This keeps behavior consistent when the file
+is accessed from the index or a tree versus from the filesystem.
EXAMPLES
--------
diff --git a/Documentation/gitdiffcore.txt b/Documentation/gitdiffcore.txt
index 1c7269655f..0d57f86abc 100644
--- a/Documentation/gitdiffcore.txt
+++ b/Documentation/gitdiffcore.txt
@@ -187,7 +187,7 @@ mark a file pair as a rename and stop considering other candidates for
better matches. At most, one comparison is done per file in this
preliminary pass; so if there are several remaining ext.txt files
throughout the directory hierarchy after exact rename detection, this
-preliminary step will be skipped for those files.
+preliminary step may be skipped for those files.
Note. When the "-C" option is used with `--find-copies-harder`
option, 'git diff-{asterisk}' commands feed unmodified filepairs to
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 1f3b57d04d..b51959ff94 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -138,7 +138,7 @@ given); `template` (if a `-t` option was given or the
configuration option `commit.template` is set); `merge` (if the
commit is a merge or a `.git/MERGE_MSG` file exists); `squash`
(if a `.git/SQUASH_MSG` file exists); or `commit`, followed by
-a commit SHA-1 (if a `-c`, `-C` or `--amend` option was given).
+a commit object name (if a `-c`, `-C` or `--amend` option was given).
If the exit status is non-zero, `git commit` will abort.
@@ -231,19 +231,19 @@ named remote is not being used both values will be the same.
Information about what is to be pushed is provided on the hook's standard
input with lines of the form:
- <local ref> SP <local sha1> SP <remote ref> SP <remote sha1> LF
+ <local ref> SP <local object name> SP <remote ref> SP <remote object name> LF
For instance, if the command +git push origin master:foreign+ were run the
hook would receive a line like the following:
refs/heads/master 67890 refs/heads/foreign 12345
-although the full, 40-character SHA-1s would be supplied. If the foreign ref
-does not yet exist the `<remote SHA-1>` will be 40 `0`. If a ref is to be
-deleted, the `<local ref>` will be supplied as `(delete)` and the `<local
-SHA-1>` will be 40 `0`. If the local commit was specified by something other
-than a name which could be expanded (such as `HEAD~`, or a SHA-1) it will be
-supplied as it was originally given.
+although the full object name would be supplied. If the foreign ref does not
+yet exist the `<remote object name>` will be the all-zeroes object name. If a
+ref is to be deleted, the `<local ref>` will be supplied as `(delete)` and the
+`<local object name>` will be the all-zeroes object name. If the local commit
+was specified by something other than a name which could be expanded (such as
+`HEAD~`, or an object name) it will be supplied as it was originally given.
If this hook exits with a non-zero status, `git push` will abort without
pushing anything. Information about why the push is rejected may be sent
@@ -268,7 +268,7 @@ input a line of the format:
where `<old-value>` is the old object name stored in the ref,
`<new-value>` is the new object name to be stored in the ref and
`<ref-name>` is the full name of the ref.
-When creating a new ref, `<old-value>` is 40 `0`.
+When creating a new ref, `<old-value>` is the all-zeroes object name.
If the hook exits with non-zero status, none of the refs will be
updated. If the hook exits with zero, updating of individual refs can
@@ -473,7 +473,8 @@ reference-transaction
This hook is invoked by any Git command that performs reference
updates. It executes whenever a reference transaction is prepared,
-committed or aborted and may thus get called multiple times.
+committed or aborted and may thus get called multiple times. The hook
+does not cover symbolic references (but that may change in the future).
The hook takes exactly one argument, which is the current state the
given reference transaction is in:
@@ -492,6 +493,14 @@ receives on standard input a line of the format:
<old-value> SP <new-value> SP <ref-name> LF
+where `<old-value>` is the old object name passed into the reference
+transaction, `<new-value>` is the new object name to be stored in the
+ref and `<ref-name>` is the full name of the ref. When force updating
+the reference regardless of its current value or when the reference is
+to be created anew, `<old-value>` is the all-zeroes object name. To
+distinguish these cases, you can inspect the current value of
+`<ref-name>` via `git rev-parse`.
+
The exit status of the hook is ignored for any state except for the
"prepared" state. In the "prepared" state, a non-zero exit status will
cause the transaction to be aborted. The hook will not be called with
@@ -550,7 +559,7 @@ command-dependent arguments may be passed in the future.
The hook receives a list of the rewritten commits on stdin, in the
format
- <old-sha1> SP <new-sha1> [ SP <extra-info> ] LF
+ <old-object-name> SP <new-object-name> [ SP <extra-info> ] LF
The 'extra-info' is again command-dependent. If it is empty, the
preceding SP is also omitted. Currently, no commands pass any
@@ -566,7 +575,7 @@ rebase::
For the 'squash' and 'fixup' operation, all commits that were
squashed are listed as being rewritten to the squashed commit.
This means that there will be several lines sharing the same
- 'new-sha1'.
+ 'new-object-name'.
+
The commits are guaranteed to be listed in the order that they were
processed by rebase.
diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt
index d47b1ae296..53e7d5c914 100644
--- a/Documentation/gitignore.txt
+++ b/Documentation/gitignore.txt
@@ -149,11 +149,15 @@ not tracked by Git remain untracked.
To stop tracking a file that is currently tracked, use
'git rm --cached'.
+Git does not follow symbolic links when accessing a `.gitignore` file in
+the working tree. This keeps behavior consistent when the file is
+accessed from the index or a tree versus from the filesystem.
+
EXAMPLES
--------
- The pattern `hello.*` matches any file or folder
- whose name begins with `hello`. If one wants to restrict
+ whose name begins with `hello.`. If one wants to restrict
this only to the directory and not in its subdirectories,
one can prepend the pattern with a slash, i.e. `/hello.*`;
the pattern now matches `hello.txt`, `hello.c` but not
diff --git a/Documentation/gitmailmap.txt b/Documentation/gitmailmap.txt
index 3fb39f801f..06f4af93fe 100644
--- a/Documentation/gitmailmap.txt
+++ b/Documentation/gitmailmap.txt
@@ -55,6 +55,13 @@ this would also match the 'Commit Name <commit&#64;email.xx>' above:
Proper Name <proper@email.xx> CoMmIt NaMe <CoMmIt@EmAiL.xX>
--
+NOTES
+-----
+
+Git does not follow symbolic links when accessing a `.mailmap` file in
+the working tree. This keeps behavior consistent when the file is
+accessed from the index or a tree versus from the filesystem.
+
EXAMPLES
--------
diff --git a/Documentation/gitmodules.txt b/Documentation/gitmodules.txt
index 8e333dde1b..dcee09b500 100644
--- a/Documentation/gitmodules.txt
+++ b/Documentation/gitmodules.txt
@@ -98,6 +98,14 @@ submodule.<name>.shallow::
shallow clone (with a history depth of 1) unless the user explicitly
asks for a non-shallow clone.
+NOTES
+-----
+
+Git does not allow the `.gitmodules` file within a working tree to be a
+symbolic link, and will refuse to check out such a tree entry. This
+keeps behavior consistent when the file is accessed from the index or a
+tree versus from the filesystem, and helps Git reliably enforce security
+checks of the file contents.
EXAMPLES
--------
diff --git a/Documentation/gitnamespaces.txt b/Documentation/gitnamespaces.txt
index b614969ad2..1c8d2ecc35 100644
--- a/Documentation/gitnamespaces.txt
+++ b/Documentation/gitnamespaces.txt
@@ -62,3 +62,7 @@ git clone ext::'git --namespace=foo %s /tmp/prefixed.git'
----------
include::transfer-data-leaks.txt[]
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/gitweb.conf.txt b/Documentation/gitweb.conf.txt
index 7963a79ba9..34b1d6e224 100644
--- a/Documentation/gitweb.conf.txt
+++ b/Documentation/gitweb.conf.txt
@@ -751,6 +751,17 @@ default font sizes or lineheights are changed (e.g. via adding extra
CSS stylesheet in `@stylesheets`), it may be appropriate to change
these values.
+email-privacy::
+ Redact e-mail addresses from the generated HTML, etc. content.
+ This obscures e-mail addresses retrieved from the author/committer
+ and comment sections of the Git log.
+ It is meant to hinder web crawlers that harvest and abuse addresses.
+ Such crawlers may not respect robots.txt.
+ Note that users and user tools also see the addresses as redacted.
+ If Gitweb is not the final step in a workflow then subsequent steps
+ may misbehave because of the redacted information they receive.
+ Disabled by default.
+
highlight::
Server-side syntax highlight support in "blob" view. It requires
`$highlight_bin` program to be available (see the description of
diff --git a/Documentation/howto/coordinate-embargoed-releases.txt b/Documentation/howto/coordinate-embargoed-releases.txt
new file mode 100644
index 0000000000..601aae88e9
--- /dev/null
+++ b/Documentation/howto/coordinate-embargoed-releases.txt
@@ -0,0 +1,131 @@
+Content-type: text/asciidoc
+Abstract: When a critical vulnerability is discovered and fixed, we follow this
+ script to coordinate a public release.
+
+How we coordinate embargoed releases
+====================================
+
+To protect Git users from critical vulnerabilities, we do not just release
+fixed versions like regular maintenance releases. Instead, we coordinate
+releases with packagers, keeping the fixes under an embargo until the release
+date. That way, users will have a chance to upgrade on that date, no matter
+what Operating System or distribution they run.
+
+Open a Security Advisory draft
+------------------------------
+
+The first step is to https://github.com/git/git/security/advisories/new[open an
+advisory]. Technically, it is not necessary, but it is convenient and saves a
+bit of hassle. This advisory can also be used to obtain the CVE number and it
+will give us a private fork associated with it that can be used to collaborate
+on a fix.
+
+Release date of the embargoed version
+-------------------------------------
+
+If the vulnerability affects Windows users, we want to have our friends over at
+Visual Studio on board. This means we need to target a "Patch Tuesday" (i.e. a
+second Tuesday of the month), at the minimum three weeks from heads-up to
+coordinated release.
+
+If the vulnerability affects the server side, or can benefit from scans on the
+server side (i.e. if `git fsck` can detect an attack), it is important to give
+all involved Git repository hosting sites enough time to scan all of those
+repositories.
+
+Notifying the Linux distributions
+---------------------------------
+
+At most two weeks before release date, we need to send a notification to
+distros@vs.openwall.org, preferably less than 7 days before the release date.
+This will reach most (all?) Linux distributions. See an example below, and the
+guidelines for this mailing list at
+https://oss-security.openwall.org/wiki/mailing-lists/distros#how-to-use-the-lists[here].
+
+Once the version has been published, we send a note about that to oss-security.
+As an example, see https://www.openwall.com/lists/oss-security/2019/12/13/1[the
+v2.24.1 mail];
+https://oss-security.openwall.org/wiki/mailing-lists/oss-security[Here] are
+their guidelines.
+
+The mail to oss-security should also describe the exploit, and give credit to
+the reporter(s): security researchers still receive too little respect for the
+invaluable service they provide, and public credit goes a long way to keep them
+paid by their respective organizations.
+
+Technically, describing any exploit can be delayed up to 7 days, but we usually
+refrain from doing that, including it right away.
+
+As a courtesy we typically attach a Git bundle (as `.tar.xz` because the list
+will drop `.bundle` attachments) in the mail to distros@ so that the involved
+parties can take care of integrating/backporting them. This bundle is typically
+created using a command like this:
+
+ git bundle create cve-xxx.bundle ^origin/master vA.B.C vD.E.F
+ tar cJvf cve-xxx.bundle.tar.xz cve-xxx.bundle
+
+Example mail to distros@vs.openwall.org
+---------------------------------------
+
+....
+To: distros@vs.openwall.org
+Cc: git-security@googlegroups.com, <other people involved in the report/fix>
+Subject: [vs] Upcoming Git security fix release
+
+Team,
+
+The Git project will release new versions on <date> at 10am Pacific Time or
+soon thereafter. I have attached a Git bundle (embedded in a `.tar.xz` to avoid
+it being dropped) which you can fetch into a clone of
+https://github.com/git/git via `git fetch --tags /path/to/cve-xxx.bundle`,
+containing the tags for versions <versions>.
+
+You can verify with `git tag -v <tag>` that the versions were signed by
+the Git maintainer, using the same GPG key as e.g. v2.24.0.
+
+Please use these tags to prepare `git` packages for your various
+distributions, using the appropriate tagged versions. The added test cases
+help verify the correctness.
+
+The addressed issues are:
+
+<list of CVEs with a short description, typically copy/pasted from Git's
+release notes, usually demo exploit(s), too>
+
+Credit for finding the vulnerability goes to <reporter>, credit for fixing
+it goes to <developer>.
+
+Thanks,
+<name>
+
+....
+
+Example mail to oss-security@lists.openwall.com
+-----------------------------------------------
+
+....
+To: oss-security@lists.openwall.com
+Cc: git-security@googlegroups.com, <other people involved in the report/fix>
+Subject: git: <copy from security advisory>
+
+Team,
+
+The Git project released new versions on <date>, addressing <CVE>.
+
+All supported platforms are affected in one way or another, and all Git
+versions all the way back to <version> are affected. The fixed versions are:
+<versions>.
+
+Link to the announcement: <link to lore.kernel.org/git>
+
+We highly recommend to upgrade.
+
+The addressed issues are:
+* <list of CVEs and their explanations, along with demo exploits>
+
+Credit for finding the vulnerability goes to <reporter>, credit for fixing
+it goes to <developer>.
+
+Thanks,
+<name>
+....
diff --git a/Documentation/lint-gitlink.perl b/Documentation/lint-gitlink.perl
index 476cc30b83..b22a367844 100755
--- a/Documentation/lint-gitlink.perl
+++ b/Documentation/lint-gitlink.perl
@@ -1,71 +1,67 @@
#!/usr/bin/perl
-use File::Find;
-use Getopt::Long;
+use strict;
+use warnings;
-my $basedir = ".";
-GetOptions("basedir=s" => \$basedir)
- or die("Cannot parse command line arguments\n");
+# Parse arguments, a simple state machine for input like:
+#
+# howto/*.txt config/*.txt --section=1 git.txt git-add.txt [...] --to-lint git-add.txt a-file.txt [...]
+my %TXT;
+my %SECTION;
+my $section;
+my $lint_these = 0;
+for my $arg (@ARGV) {
+ if (my ($sec) = $arg =~ /^--section=(\d+)$/s) {
+ $section = $sec;
+ next;
+ }
-my $found_errors = 0;
+ my ($name) = $arg =~ /^(.*?)\.txt$/s;
+ unless (defined $section) {
+ $TXT{$name} = $arg;
+ next;
+ }
-sub report {
- my ($where, $what, $error) = @_;
- print "$where: $error: $what\n";
- $found_errors = 1;
+ $SECTION{$name} = $section;
}
-sub grab_section {
- my ($page) = @_;
- open my $fh, "<", "$basedir/$page.txt";
- my $firstline = <$fh>;
- chomp $firstline;
- close $fh;
- my ($section) = ($firstline =~ /.*\((\d)\)$/);
- return $section;
+my $exit_code = 0;
+sub report {
+ my ($pos, $line, $target, $msg) = @_;
+ substr($line, $pos) = "' <-- HERE";
+ $line =~ s/^\s+//;
+ print "$ARGV:$.: error: $target: $msg, shown with 'HERE' below:\n";
+ print "$ARGV:$.:\t'$line\n";
+ $exit_code = 1;
}
-sub lint {
- my ($file) = @_;
- open my $fh, "<", $file
- or return;
- while (<$fh>) {
- my $where = "$file:$.";
- while (s/linkgit:((.*?)\[(\d)\])//) {
- my ($target, $page, $section) = ($1, $2, $3);
+@ARGV = sort values %TXT;
+die "BUG: Nothing to process!" unless @ARGV;
+while (<>) {
+ my $line = $_;
+ while ($line =~ m/linkgit:((.*?)\[(\d)\])/g) {
+ my $pos = pos $line;
+ my ($target, $page, $section) = ($1, $2, $3);
- # De-AsciiDoc
- $page =~ s/{litdd}/--/g;
+ # De-AsciiDoc
+ $page =~ s/{litdd}/--/g;
- if ($page !~ /^git/) {
- report($where, $target, "nongit link");
- next;
- }
- if (! -f "$basedir/$page.txt") {
- report($where, $target, "no such source");
- next;
- }
- $real_section = grab_section($page);
- if ($real_section != $section) {
- report($where, $target,
- "wrong section (should be $real_section)");
- next;
- }
+ if (!exists $TXT{$page}) {
+ report($pos, $line, $target, "link outside of our own docs");
+ next;
+ }
+ if (!exists $SECTION{$page}) {
+ report($pos, $line, $target, "link outside of our sectioned docs");
+ next;
+ }
+ my $real_section = $SECTION{$page};
+ if ($section != $SECTION{$page}) {
+ report($pos, $line, $target, "wrong section (should be $real_section)");
+ next;
}
}
- close $fh;
-}
-
-sub lint_it {
- lint($File::Find::name) if -f && /\.txt$/;
-}
-
-if (!@ARGV) {
- find({ wanted => \&lint_it, no_chdir => 1 }, $basedir);
-} else {
- for (@ARGV) {
- lint($_);
- }
+ # this resets our $. for each file
+ close ARGV if eof;
}
-exit $found_errors;
+exit $exit_code;
diff --git a/Documentation/lint-man-end-blurb.perl b/Documentation/lint-man-end-blurb.perl
new file mode 100755
index 0000000000..d69312e5db
--- /dev/null
+++ b/Documentation/lint-man-end-blurb.perl
@@ -0,0 +1,24 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+my $exit_code = 0;
+sub report {
+ my ($target, $msg) = @_;
+ print "error: $target: $msg\n";
+ $exit_code = 1;
+}
+
+local $/;
+while (my $slurp = <>) {
+ report($ARGV, "has no 'Part of the linkgit:git[1] suite' end blurb")
+ unless $slurp =~ m[
+ ^GIT\n
+ ---\n
+ \QPart of the linkgit:git[1] suite\E \n
+ \z
+ ]mx;
+}
+
+exit $exit_code;
diff --git a/Documentation/lint-man-section-order.perl b/Documentation/lint-man-section-order.perl
new file mode 100755
index 0000000000..b05f9156dd
--- /dev/null
+++ b/Documentation/lint-man-section-order.perl
@@ -0,0 +1,105 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+my %SECTIONS;
+{
+ my $order = 0;
+ %SECTIONS = (
+ 'NAME' => {
+ required => 1,
+ order => $order++,
+ },
+ 'SYNOPSIS' => {
+ required => 1,
+ order => $order++,
+ },
+ 'DESCRIPTION' => {
+ required => 1,
+ order => $order++,
+ },
+ 'OPTIONS' => {
+ order => $order++,
+ required => 0,
+ },
+ 'CONFIGURATION' => {
+ order => $order++,
+ },
+ 'BUGS' => {
+ order => $order++,
+ },
+ 'SEE ALSO' => {
+ order => $order++,
+ },
+ 'GIT' => {
+ required => 1,
+ order => $order++,
+ },
+ );
+}
+my $SECTION_RX = do {
+ my ($names) = join "|", keys %SECTIONS;
+ qr/^($names)$/s;
+};
+
+my $exit_code = 0;
+sub report {
+ my ($msg) = @_;
+ print "$ARGV:$.: $msg\n";
+ $exit_code = 1;
+}
+
+my $last_was_section;
+my @actual_order;
+while (my $line = <>) {
+ chomp $line;
+ if ($line =~ $SECTION_RX) {
+ push @actual_order => $line;
+ $last_was_section = 1;
+ # Have no "last" section yet, processing NAME
+ next if @actual_order == 1;
+
+ my @expected_order = sort {
+ $SECTIONS{$a}->{order} <=> $SECTIONS{$b}->{order}
+ } @actual_order;
+
+ my $expected_last = $expected_order[-2];
+ my $actual_last = $actual_order[-2];
+ if ($actual_last ne $expected_last) {
+ report("section '$line' incorrectly ordered, comes after '$actual_last'");
+ }
+ next;
+ }
+ if ($last_was_section) {
+ my $last_section = $actual_order[-1];
+ if (length $last_section ne length $line) {
+ report("dashes under '$last_section' should match its length!");
+ }
+ if ($line !~ /^-+$/) {
+ report("dashes under '$last_section' should be '-' dashes!");
+ }
+ $last_was_section = 0;
+ }
+
+ if (eof) {
+ # We have both a hash and an array to consider, for
+ # convenience
+ my %actual_sections;
+ @actual_sections{@actual_order} = ();
+
+ for my $section (sort keys %SECTIONS) {
+ next if !$SECTIONS{$section}->{required} or exists $actual_sections{$section};
+ report("has no required '$section' section!");
+ }
+
+ # Reset per-file state
+ {
+ @actual_order = ();
+ # this resets our $. for each file
+ close ARGV;
+ }
+ }
+}
+
+exit $exit_code;
diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt
index 6b59e28d44..ef6bd420ae 100644
--- a/Documentation/pretty-formats.txt
+++ b/Documentation/pretty-formats.txt
@@ -190,6 +190,8 @@ The placeholders are:
'%ai':: author date, ISO 8601-like format
'%aI':: author date, strict ISO 8601 format
'%as':: author date, short format (`YYYY-MM-DD`)
+'%ah':: author date, human style (like the `--date=human` option of
+ linkgit:git-rev-list[1])
'%cn':: committer name
'%cN':: committer name (respecting .mailmap, see
linkgit:git-shortlog[1] or linkgit:git-blame[1])
@@ -206,8 +208,23 @@ The placeholders are:
'%ci':: committer date, ISO 8601-like format
'%cI':: committer date, strict ISO 8601 format
'%cs':: committer date, short format (`YYYY-MM-DD`)
+'%ch':: committer date, human style (like the `--date=human` option of
+ linkgit:git-rev-list[1])
'%d':: ref names, like the --decorate option of linkgit:git-log[1]
'%D':: ref names without the " (", ")" wrapping.
+'%(describe[:options])':: human-readable name, like
+ linkgit:git-describe[1]; empty string for
+ undescribable commits. The `describe` string
+ may be followed by a colon and zero or more
+ comma-separated options. Descriptions can be
+ inconsistent when tags are added or removed at
+ the same time.
++
+** 'match=<pattern>': Only consider tags matching the given
+ `glob(7)` pattern, excluding the "refs/tags/" prefix.
+** 'exclude=<pattern>': Do not consider tags matching the given
+ `glob(7)` pattern, excluding the "refs/tags/" prefix.
+
'%S':: ref name given on the command line by which the commit was reached
(like `git log --source`), only works with `git log`
'%e':: encoding
@@ -254,7 +271,7 @@ endif::git-rev-list[]
`trailers` string may be followed by a colon
and zero or more comma-separated options.
If any option is provided multiple times the
- last occurance wins.
+ last occurrence wins.
+
The boolean options accept an optional value `[=<BOOL>]`. The values
`true`, `false`, `on`, `off` etc. are all accepted. See the "boolean"
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index b1c8f86c6e..5bf2a85f69 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -892,6 +892,9 @@ or units. n may be zero. The suffixes k, m, and g can be used to name
units in KiB, MiB, or GiB. For example, 'blob:limit=1k' is the same
as 'blob:limit=1024'.
+
+The form '--filter=object:type=(tag|commit|tree|blob)' omits all objects
+which are not of the requested type.
++
The form '--filter=sparse:oid=<blob-ish>' uses a sparse-checkout
specification contained in the blob (or blob-expression) '<blob-ish>'
to omit blobs that would not be not required for a sparse checkout on
@@ -930,6 +933,11 @@ equivalent.
--no-filter::
Turn off any previous `--filter=` argument.
+--filter-provided-objects::
+ Filter the list of explicitly provided objects, which would otherwise
+ always be printed even if they did not match any of the filters. Only
+ useful with `--filter=`.
+
--filter-print-omitted::
Only useful with `--filter=`; prints a list of the objects omitted
by the filter. Object IDs are prefixed with a ``~'' character.
diff --git a/Documentation/technical/api-error-handling.txt b/Documentation/technical/api-error-handling.txt
index ceeedd485c..8be4f4d0d6 100644
--- a/Documentation/technical/api-error-handling.txt
+++ b/Documentation/technical/api-error-handling.txt
@@ -1,8 +1,11 @@
Error reporting in git
======================
-`die`, `usage`, `error`, and `warning` report errors of various
-kinds.
+`BUG`, `die`, `usage`, `error`, and `warning` report errors of
+various kinds.
+
+- `BUG` is for failed internal assertions that should never happen,
+ i.e. a bug in git itself.
- `die` is for fatal application errors. It prints a message to
the user and exits with status 128.
@@ -20,6 +23,9 @@ kinds.
without running into too many problems. Like `error`, it
returns -1 after reporting the situation to the caller.
+These reports will be logged via the trace2 facility. See the "error"
+event in link:api-trace2.txt[trace2 API].
+
Customizable error handlers
---------------------------
diff --git a/Documentation/technical/api-simple-ipc.txt b/Documentation/technical/api-simple-ipc.txt
new file mode 100644
index 0000000000..d79ad323e6
--- /dev/null
+++ b/Documentation/technical/api-simple-ipc.txt
@@ -0,0 +1,105 @@
+Simple-IPC API
+==============
+
+The Simple-IPC API is a collection of `ipc_` prefixed library routines
+and a basic communication protocol that allow an IPC-client process to
+send an application-specific IPC-request message to an IPC-server
+process and receive an application-specific IPC-response message.
+
+Communication occurs over a named pipe on Windows and a Unix domain
+socket on other platforms. IPC-clients and IPC-servers rendezvous at
+a previously agreed-to application-specific pathname (which is outside
+the scope of this design) that is local to the computer system.
+
+The IPC-server routines within the server application process create a
+thread pool to listen for connections and receive request messages
+from multiple concurrent IPC-clients. When received, these messages
+are dispatched up to the server application callbacks for handling.
+IPC-server routines then incrementally relay responses back to the
+IPC-client.
+
+The IPC-client routines within a client application process connect
+to the IPC-server and send a request message and wait for a response.
+When received, the response is returned back the caller.
+
+For example, the `fsmonitor--daemon` feature will be built as a server
+application on top of the IPC-server library routines. It will have
+threads watching for file system events and a thread pool waiting for
+client connections. Clients, such as `git status` will request a list
+of file system events since a point in time and the server will
+respond with a list of changed files and directories. The formats of
+the request and response are application-specific; the IPC-client and
+IPC-server routines treat them as opaque byte streams.
+
+
+Comparison with sub-process model
+---------------------------------
+
+The Simple-IPC mechanism differs from the existing `sub-process.c`
+model (Documentation/technical/long-running-process-protocol.txt) and
+used by applications like Git-LFS. In the LFS-style sub-process model
+the helper is started by the foreground process, communication happens
+via a pair of file descriptors bound to the stdin/stdout of the
+sub-process, the sub-process only serves the current foreground
+process, and the sub-process exits when the foreground process
+terminates.
+
+In the Simple-IPC model the server is a very long-running service. It
+can service many clients at the same time and has a private socket or
+named pipe connection to each active client. It might be started
+(on-demand) by the current client process or it might have been
+started by a previous client or by the OS at boot time. The server
+process is not associated with a terminal and it persists after
+clients terminate. Clients do not have access to the stdin/stdout of
+the server process and therefore must communicate over sockets or
+named pipes.
+
+
+Server startup and shutdown
+---------------------------
+
+How an application server based upon IPC-server is started is also
+outside the scope of the Simple-IPC design and is a property of the
+application using it. For example, the server might be started or
+restarted during routine maintenance operations, or it might be
+started as a system service during the system boot-up sequence, or it
+might be started on-demand by a foreground Git command when needed.
+
+Similarly, server shutdown is a property of the application using
+the simple-ipc routines. For example, the server might decide to
+shutdown when idle or only upon explicit request.
+
+
+Simple-IPC protocol
+-------------------
+
+The Simple-IPC protocol consists of a single request message from the
+client and an optional response message from the server. Both the
+client and server messages are unlimited in length and are terminated
+with a flush packet.
+
+The pkt-line routines (Documentation/technical/protocol-common.txt)
+are used to simplify buffer management during message generation,
+transmission, and reception. A flush packet is used to mark the end
+of the message. This allows the sender to incrementally generate and
+transmit the message. It allows the receiver to incrementally receive
+the message in chunks and to know when they have received the entire
+message.
+
+The actual byte format of the client request and server response
+messages are application specific. The IPC layer transmits and
+receives them as opaque byte buffers without any concern for the
+content within. It is the job of the calling application layer to
+understand the contents of the request and response messages.
+
+
+Summary
+-------
+
+Conceptually, the Simple-IPC protocol is similar to an HTTP REST
+request. Clients connect, make an application-specific and
+stateless request, receive an application-specific
+response, and disconnect. It is a one round trip facility for
+querying the server. The Simple-IPC routines hide the socket,
+named pipe, and thread pool details and allow the application
+layer to focus on the application at hand.
diff --git a/Documentation/technical/api-trace2.txt b/Documentation/technical/api-trace2.txt
index c65ffafc48..3f52f981a2 100644
--- a/Documentation/technical/api-trace2.txt
+++ b/Documentation/technical/api-trace2.txt
@@ -465,7 +465,7 @@ completed.)
------------
`"error"`::
- This event is emitted when one of the `error()`, `die()`,
+ This event is emitted when one of the `BUG()`, `error()`, `die()`,
`warning()`, or `usage()` functions are called.
+
------------
diff --git a/Documentation/technical/index-format.txt b/Documentation/technical/index-format.txt
index d363a71c37..65da0daaa5 100644
--- a/Documentation/technical/index-format.txt
+++ b/Documentation/technical/index-format.txt
@@ -44,6 +44,13 @@ Git index format
localization, no special casing of directory separator '/'). Entries
with the same name are sorted by their stage field.
+ An index entry typically represents a file. However, if sparse-checkout
+ is enabled in cone mode (`core.sparseCheckoutCone` is enabled) and the
+ `extensions.sparseIndex` extension is enabled, then the index may
+ contain entries for directories outside of the sparse-checkout definition.
+ These entries have mode `040000`, include the `SKIP_WORKTREE` bit, and
+ the path ends in a directory separator.
+
32-bit ctime seconds, the last time a file's metadata changed
this is stat(2) data
@@ -385,3 +392,15 @@ The remaining data of each directory block is grouped by type:
in this block of entries.
- 32-bit count of cache entries in this block
+
+== Sparse Directory Entries
+
+ When using sparse-checkout in cone mode, some entire directories within
+ the index can be summarized by pointing to a tree object instead of the
+ entire expanded list of paths within that tree. An index containing such
+ entries is a "sparse index". Index format versions 4 and less were not
+ implemented with such entries in mind. Thus, for these versions, an
+ index containing sparse directory entries will include this extension
+ with signature { 's', 'd', 'i', 'r' }. Like the split-index extension,
+ tools should avoid interacting with a sparse index unless they understand
+ this extension.
diff --git a/Documentation/technical/multi-pack-index.txt b/Documentation/technical/multi-pack-index.txt
index e8e377a59f..fb688976c4 100644
--- a/Documentation/technical/multi-pack-index.txt
+++ b/Documentation/technical/multi-pack-index.txt
@@ -43,8 +43,9 @@ Design Details
a change in format.
- The MIDX keeps only one record per object ID. If an object appears
- in multiple packfiles, then the MIDX selects the copy in the most-
- recently modified packfile.
+ in multiple packfiles, then the MIDX selects the copy in the
+ preferred packfile, otherwise selecting from the most-recently
+ modified packfile.
- If there exist packfiles in the pack directory not registered in
the MIDX, then those packfiles are loaded into the `packed_git`
diff --git a/Documentation/technical/pack-format.txt b/Documentation/technical/pack-format.txt
index 1faa949bf6..8d2f42f29e 100644
--- a/Documentation/technical/pack-format.txt
+++ b/Documentation/technical/pack-format.txt
@@ -379,3 +379,86 @@ CHUNK DATA:
TRAILER:
Index checksum of the above contents.
+
+== multi-pack-index reverse indexes
+
+Similar to the pack-based reverse index, the multi-pack index can also
+be used to generate a reverse index.
+
+Instead of mapping between offset, pack-, and index position, this
+reverse index maps between an object's position within the MIDX, and
+that object's position within a pseudo-pack that the MIDX describes
+(i.e., the ith entry of the multi-pack reverse index holds the MIDX
+position of ith object in pseudo-pack order).
+
+To clarify the difference between these orderings, consider a multi-pack
+reachability bitmap (which does not yet exist, but is what we are
+building towards here). Each bit needs to correspond to an object in the
+MIDX, and so we need an efficient mapping from bit position to MIDX
+position.
+
+One solution is to let bits occupy the same position in the oid-sorted
+index stored by the MIDX. But because oids are effectively random, their
+resulting reachability bitmaps would have no locality, and thus compress
+poorly. (This is the reason that single-pack bitmaps use the pack
+ordering, and not the .idx ordering, for the same purpose.)
+
+So we'd like to define an ordering for the whole MIDX based around
+pack ordering, which has far better locality (and thus compresses more
+efficiently). We can think of a pseudo-pack created by the concatenation
+of all of the packs in the MIDX. E.g., if we had a MIDX with three packs
+(a, b, c), with 10, 15, and 20 objects respectively, we can imagine an
+ordering of the objects like:
+
+ |a,0|a,1|...|a,9|b,0|b,1|...|b,14|c,0|c,1|...|c,19|
+
+where the ordering of the packs is defined by the MIDX's pack list,
+and then the ordering of objects within each pack is the same as the
+order in the actual packfile.
+
+Given the list of packs and their counts of objects, you can
+naïvely reconstruct that pseudo-pack ordering (e.g., the object at
+position 27 must be (c,1) because packs "a" and "b" consumed 25 of the
+slots). But there's a catch. Objects may be duplicated between packs, in
+which case the MIDX only stores one pointer to the object (and thus we'd
+want only one slot in the bitmap).
+
+Callers could handle duplicates themselves by reading objects in order
+of their bit-position, but that's linear in the number of objects, and
+much too expensive for ordinary bitmap lookups. Building a reverse index
+solves this, since it is the logical inverse of the index, and that
+index has already removed duplicates. But, building a reverse index on
+the fly can be expensive. Since we already have an on-disk format for
+pack-based reverse indexes, let's reuse it for the MIDX's pseudo-pack,
+too.
+
+Objects from the MIDX are ordered as follows to string together the
+pseudo-pack. Let `pack(o)` return the pack from which `o` was selected
+by the MIDX, and define an ordering of packs based on their numeric ID
+(as stored by the MIDX). Let `offset(o)` return the object offset of `o`
+within `pack(o)`. Then, compare `o1` and `o2` as follows:
+
+ - If one of `pack(o1)` and `pack(o2)` is preferred and the other
+ is not, then the preferred one sorts first.
++
+(This is a detail that allows the MIDX bitmap to determine which
+pack should be used by the pack-reuse mechanism, since it can ask
+the MIDX for the pack containing the object at bit position 0).
+
+ - If `pack(o1) ≠ pack(o2)`, then sort the two objects in descending
+ order based on the pack ID.
+
+ - Otherwise, `pack(o1) = pack(o2)`, and the objects are sorted in
+ pack-order (i.e., `o1` sorts ahead of `o2` exactly when `offset(o1)
+ < offset(o2)`).
+
+In short, a MIDX's pseudo-pack is the de-duplicated concatenation of
+objects in packs stored by the MIDX, laid out in pack order, and the
+packs arranged in MIDX order (with the preferred pack coming first).
+
+Finally, note that the MIDX's reverse index is not stored as a chunk in
+the multi-pack-index itself. This is done because the reverse index
+includes the checksum of the pack or MIDX to which it belongs, which
+makes it impossible to write in the MIDX. To avoid races when rewriting
+the MIDX, a MIDX reverse index includes the MIDX's checksum in its
+filename (e.g., `multi-pack-index-xyz.rev`).
diff --git a/Documentation/technical/parallel-checkout.txt b/Documentation/technical/parallel-checkout.txt
new file mode 100644
index 0000000000..e790258a1a
--- /dev/null
+++ b/Documentation/technical/parallel-checkout.txt
@@ -0,0 +1,270 @@
+Parallel Checkout Design Notes
+==============================
+
+The "Parallel Checkout" feature attempts to use multiple processes to
+parallelize the work of uncompressing the blobs, applying in-core
+filters, and writing the resulting contents to the working tree during a
+checkout operation. It can be used by all checkout-related commands,
+such as `clone`, `checkout`, `reset`, `sparse-checkout`, and others.
+
+These commands share the following basic structure:
+
+* Step 1: Read the current index file into memory.
+
+* Step 2: Modify the in-memory index based upon the command, and
+ temporarily mark all cache entries that need to be updated.
+
+* Step 3: Populate the working tree to match the new candidate index.
+ This includes iterating over all of the to-be-updated cache entries
+ and delete, create, or overwrite the associated files in the working
+ tree.
+
+* Step 4: Write the new index to disk.
+
+Step 3 is the focus of the "parallel checkout" effort described here.
+
+Sequential Implementation
+-------------------------
+
+For the purposes of discussion here, the current sequential
+implementation of Step 3 is divided in 3 parts, each one implemented in
+its own function:
+
+* Step 3a: `unpack-trees.c:check_updates()` contains a series of
+ sequential loops iterating over the `cache_entry`'s array. The main
+ loop in this function calls the Step 3b function for each of the
+ to-be-updated entries.
+
+* Step 3b: `entry.c:checkout_entry()` examines the existing working tree
+ for file conflicts, collisions, and unsaved changes. It removes files
+ and creates leading directories as necessary. It calls the Step 3c
+ function for each entry to be written.
+
+* Step 3c: `entry.c:write_entry()` loads the blob into memory, smudges
+ it if necessary, creates the file in the working tree, writes the
+ smudged contents, calls `fstat()` or `lstat()`, and updates the
+ associated `cache_entry` struct with the stat information gathered.
+
+It wouldn't be safe to perform Step 3b in parallel, as there could be
+race conditions between file creations and removals. Instead, the
+parallel checkout framework lets the sequential code handle Step 3b,
+and uses parallel workers to replace the sequential
+`entry.c:write_entry()` calls from Step 3c.
+
+Rejected Multi-Threaded Solution
+--------------------------------
+
+The most "straightforward" implementation would be to spread the set of
+to-be-updated cache entries across multiple threads. But due to the
+thread-unsafe functions in the ODB code, we would have to use locks to
+coordinate the parallel operation. An early prototype of this solution
+showed that the multi-threaded checkout would bring performance
+improvements over the sequential code, but there was still too much lock
+contention. A `perf` profiling indicated that around 20% of the runtime
+during a local Linux clone (on an SSD) was spent in locking functions.
+For this reason this approach was rejected in favor of using multiple
+child processes, which led to a better performance.
+
+Multi-Process Solution
+----------------------
+
+Parallel checkout alters the aforementioned Step 3 to use multiple
+`checkout--worker` background processes to distribute the work. The
+long-running worker processes are controlled by the foreground Git
+command using the existing run-command API.
+
+Overview
+~~~~~~~~
+
+Step 3b is only slightly altered; for each entry to be checked out, the
+main process performs the following steps:
+
+* M1: Check whether there is any untracked or unclean file in the
+ working tree which would be overwritten by this entry, and decide
+ whether to proceed (removing the file(s)) or not.
+
+* M2: Create the leading directories.
+
+* M3: Load the conversion attributes for the entry's path.
+
+* M4: Check, based on the entry's type and conversion attributes,
+ whether the entry is eligible for parallel checkout (more on this
+ later). If it is eligible, enqueue the entry and the loaded
+ attributes to later write the entry in parallel. If not, write the
+ entry right away, using the default sequential code.
+
+Note: we save the conversion attributes associated with each entry
+because the workers don't have access to the main process' index state,
+so they can't load the attributes by themselves (and the attributes are
+needed to properly smudge the entry). Additionally, this has a positive
+impact on performance as (1) we don't need to load the attributes twice
+and (2) the attributes machinery is optimized to handle paths in
+sequential order.
+
+After all entries have passed through the above steps, the main process
+checks if the number of enqueued entries is sufficient to spread among
+the workers. If not, it just writes them sequentially. Otherwise, it
+spawns the workers and distributes the queued entries uniformly in
+continuous chunks. This aims to minimize the chances of two workers
+writing to the same directory simultaneously, which could increase lock
+contention in the kernel.
+
+Then, for each assigned item, each worker:
+
+* W1: Checks if there is any non-directory file in the leading part of
+ the entry's path or if there already exists a file at the entry' path.
+ If so, mark the entry with `PC_ITEM_COLLIDED` and skip it (more on
+ this later).
+
+* W2: Creates the file (with O_CREAT and O_EXCL).
+
+* W3: Loads the blob into memory (inflating and delta reconstructing
+ it).
+
+* W4: Applies any required in-process filter, like end-of-line
+ conversion and re-encoding.
+
+* W5: Writes the result to the file descriptor opened at W2.
+
+* W6: Calls `fstat()` or lstat()` on the just-written path, and sends
+ the result back to the main process, together with the end status of
+ the operation and the item's identification number.
+
+Note that, when possible, steps W3 to W5 are delegated to the streaming
+machinery, removing the need to keep the entire blob in memory.
+
+If the worker fails to read the blob or to write it to the working tree,
+it removes the created file to avoid leaving empty files behind. This is
+the *only* time a worker is allowed to remove a file.
+
+As mentioned earlier, it is the responsibility of the main process to
+remove any file that blocks the checkout operation (or abort if the
+removal(s) would cause data loss and the user didn't ask to `--force`).
+This is crucial to avoid race conditions and also to properly detect
+path collisions at Step W1.
+
+After the workers finish writing the items and sending back the required
+information, the main process handles the results in two steps:
+
+- First, it updates the in-memory index with the `lstat()` information
+ sent by the workers. (This must be done first as this information
+ might me required in the following step.)
+
+- Then it writes the items which collided on disk (i.e. items marked
+ with `PC_ITEM_COLLIDED`). More on this below.
+
+Path Collisions
+---------------
+
+Path collisions happen when two different paths correspond to the same
+entry in the file system. E.g. the paths 'a' and 'A' would collide in a
+case-insensitive file system.
+
+The sequential checkout deals with collisions in the same way that it
+deals with files that were already present in the working tree before
+checkout. Basically, it checks if the path that it wants to write
+already exists on disk, makes sure the existing file doesn't have
+unsaved data, and then overwrites it. (To be more pedantic: it deletes
+the existing file and creates the new one.) So, if there are multiple
+colliding files to be checked out, the sequential code will write each
+one of them but only the last will actually survive on disk.
+
+Parallel checkout aims to reproduce the same behavior. However, we
+cannot let the workers racily write to the same file on disk. Instead,
+the workers detect when the entry that they want to check out would
+collide with an existing file, and mark it with `PC_ITEM_COLLIDED`.
+Later, the main process can sequentially feed these entries back to
+`checkout_entry()` without the risk of race conditions. On clone, this
+also has the effect of marking the colliding entries to later emit a
+warning for the user, like the classic sequential checkout does.
+
+The workers are able to detect both collisions among the entries being
+concurrently written and collisions between a parallel-eligible entry
+and an ineligible entry. The general idea for collision detection is
+quite straightforward: for each parallel-eligible entry, the main
+process must remove all files that prevent this entry from being written
+(before enqueueing it). This includes any non-directory file in the
+leading path of the entry. Later, when a worker gets assigned the entry,
+it looks again for the non-directories files and for an already existing
+file at the entry's path. If any of these checks finds something, the
+worker knows that there was a path collision.
+
+Because parallel checkout can distinguish path collisions from the case
+where the file was already present in the working tree before checkout,
+we could alternatively choose to skip the checkout of colliding entries.
+However, each entry that doesn't get written would have NULL `lstat()`
+fields on the index. This could cause performance penalties for
+subsequent commands that need to refresh the index, as they would have
+to go to the file system to see if the entry is dirty. Thus, if we have
+N entries in a colliding group and we decide to write and `lstat()` only
+one of them, every subsequent `git-status` will have to read, convert,
+and hash the written file N - 1 times. By checking out all colliding
+entries (like the sequential code does), we only pay the overhead once,
+during checkout.
+
+Eligible Entries for Parallel Checkout
+--------------------------------------
+
+As previously mentioned, not all entries passed to `checkout_entry()`
+will be considered eligible for parallel checkout. More specifically, we
+exclude:
+
+- Symbolic links; to avoid race conditions that, in combination with
+ path collisions, could cause workers to write files at the wrong
+ place. For example, if we were to concurrently check out a symlink
+ 'a' -> 'b' and a regular file 'A/f' in a case-insensitive file system,
+ we could potentially end up writing the file 'A/f' at 'a/f', due to a
+ race condition.
+
+- Regular files that require external filters (either "one shot" filters
+ or long-running process filters). These filters are black-boxes to Git
+ and may have their own internal locking or non-concurrent assumptions.
+ So it might not be safe to run multiple instances in parallel.
++
+Besides, long-running filters may use the delayed checkout feature to
+postpone the return of some filtered blobs. The delayed checkout queue
+and the parallel checkout queue are not compatible and should remain
+separate.
++
+Note: regular files that only require internal filters, like end-of-line
+conversion and re-encoding, are eligible for parallel checkout.
+
+Ineligible entries are checked out by the classic sequential codepath
+*before* spawning workers.
+
+Note: submodules's files are also eligible for parallel checkout (as
+long as they don't fall into any of the excluding categories mentioned
+above). But since each submodule is checked out in its own child
+process, we don't mix the superproject's and the submodules' files in
+the same parallel checkout process or queue.
+
+The API
+-------
+
+The parallel checkout API was designed with the goal of minimizing
+changes to the current users of the checkout machinery. This means that
+they don't have to call a different function for sequential or parallel
+checkout. As already mentioned, `checkout_entry()` will automatically
+insert the given entry in the parallel checkout queue when this feature
+is enabled and the entry is eligible; otherwise, it will just write the
+entry right away, using the sequential code. In general, callers of the
+parallel checkout API should look similar to this:
+
+----------------------------------------------
+int pc_workers, pc_threshold, err = 0;
+struct checkout state;
+
+get_parallel_checkout_configs(&pc_workers, &pc_threshold);
+
+/*
+ * This check is not strictly required, but it
+ * should save some time in sequential mode.
+ */
+if (pc_workers > 1)
+ init_parallel_checkout();
+
+for (each cache_entry ce to-be-updated)
+ err |= checkout_entry(ce, &state, NULL, NULL);
+
+err |= run_parallel_checkout(&state, pc_workers, pc_threshold, NULL, NULL);
+----------------------------------------------
diff --git a/Documentation/technical/protocol-v2.txt b/Documentation/technical/protocol-v2.txt
index a7c806a73e..a1e31367f4 100644
--- a/Documentation/technical/protocol-v2.txt
+++ b/Documentation/technical/protocol-v2.txt
@@ -346,6 +346,14 @@ explained below.
client should download from all given URIs. Currently, the
protocols supported are "http" and "https".
+If the 'wait-for-done' feature is advertised, the following argument
+can be included in the client's request.
+
+ wait-for-done
+ Indicates to the server that it should never send "ready", but
+ should wait for the client to say "done" before sending the
+ packfile.
+
The response of `fetch` is broken into a number of sections separated by
delimiter packets (0001), with each section beginning with its section
header. Most sections are sent only when the packfile is sent.
@@ -514,3 +522,34 @@ packet-line, and must not contain non-printable or whitespace characters. The
current implementation uses trace2 session IDs (see
link:api-trace2.html[api-trace2] for details), but this may change and users of
the session ID should not rely on this fact.
+
+object-info
+~~~~~~~~~~~
+
+`object-info` is the command to retrieve information about one or more objects.
+Its main purpose is to allow a client to make decisions based on this
+information without having to fully fetch objects. Object size is the only
+information that is currently supported.
+
+An `object-info` request takes the following arguments:
+
+ size
+ Requests size information to be returned for each listed object id.
+
+ oid <oid>
+ Indicates to the server an object which the client wants to obtain
+ information for.
+
+The response of `object-info` is a list of the the requested object ids
+and associated requested information, each separated by a single space.
+
+ output = info flush-pkt
+
+ info = PKT-LINE(attrs) LF)
+ *PKT-LINE(obj-info LF)
+
+ attrs = attr | attrs SP attrs
+
+ attr = "size"
+
+ obj-info = obj-id SP obj-size
diff --git a/Documentation/technical/reftable.txt b/Documentation/technical/reftable.txt
index 3ef169af27..d7c3b645cf 100644
--- a/Documentation/technical/reftable.txt
+++ b/Documentation/technical/reftable.txt
@@ -1011,8 +1011,13 @@ reftable stack, reload `tables.list`, and delete any tables no longer mentioned
in `tables.list`.
Irregular program exit may still leave about unused files. In this case, a
-cleanup operation can read `tables.list`, note its modification timestamp, and
-delete any unreferenced `*.ref` files that are older.
+cleanup operation should proceed as follows:
+
+* take a lock `tables.list.lock` to prevent concurrent modifications
+* refresh the reftable stack, by reading `tables.list`
+* for each `*.ref` file, remove it if
+** it is not mentioned in `tables.list`, and
+** its max update_index is not beyond the max update_index of the stack
Alternatives considered
diff --git a/Documentation/technical/sparse-index.txt b/Documentation/technical/sparse-index.txt
new file mode 100644
index 0000000000..3b24c1a219
--- /dev/null
+++ b/Documentation/technical/sparse-index.txt
@@ -0,0 +1,208 @@
+Git Sparse-Index Design Document
+================================
+
+The sparse-checkout feature allows users to focus a working directory on
+a subset of the files at HEAD. The cone mode patterns, enabled by
+`core.sparseCheckoutCone`, allow for very fast pattern matching to
+discover which files at HEAD belong in the sparse-checkout cone.
+
+Three important scale dimensions for a Git working directory are:
+
+* `HEAD`: How many files are present at `HEAD`?
+
+* Populated: How many files are within the sparse-checkout cone.
+
+* Modified: How many files has the user modified in the working directory?
+
+We will use big-O notation -- O(X) -- to denote how expensive certain
+operations are in terms of these dimensions.
+
+These dimensions are ordered by their magnitude: users (typically) modify
+fewer files than are populated, and we can only populate files at `HEAD`.
+
+Problems occur if there is an extreme imbalance in these dimensions. For
+example, if `HEAD` contains millions of paths but the populated set has
+only tens of thousands, then commands like `git status` and `git add` can
+be dominated by operations that require O(`HEAD`) operations instead of
+O(Populated). Primarily, the cost is in parsing and rewriting the index,
+which is filled primarily with files at `HEAD` that are marked with the
+`SKIP_WORKTREE` bit.
+
+The sparse-index intends to take these commands that read and modify the
+index from O(`HEAD`) to O(Populated). To do this, we need to modify the
+index format in a significant way: add "sparse directory" entries.
+
+With cone mode patterns, it is possible to detect when an entire
+directory will have its contents outside of the sparse-checkout definition.
+Instead of listing all of the files it contains as individual entries, a
+sparse-index contains an entry with the directory name, referencing the
+object ID of the tree at `HEAD` and marked with the `SKIP_WORKTREE` bit.
+If we need to discover the details for paths within that directory, we
+can parse trees to find that list.
+
+At time of writing, sparse-directory entries violate expectations about the
+index format and its in-memory data structure. There are many consumers in
+the codebase that expect to iterate through all of the index entries and
+see only files. In fact, these loops expect to see a reference to every
+staged file. One way to handle this is to parse trees to replace a
+sparse-directory entry with all of the files within that tree as the index
+is loaded. However, parsing trees is slower than parsing the index format,
+so that is a slower operation than if we left the index alone. The plan is
+to make all of these integrations "sparse aware" so this expansion through
+tree parsing is unnecessary and they use fewer resources than when using a
+full index.
+
+The implementation plan below follows four phases to slowly integrate with
+the sparse-index. The intention is to incrementally update Git commands to
+interact safely with the sparse-index without significant slowdowns. This
+may not always be possible, but the hope is that the primary commands that
+users need in their daily work are dramatically improved.
+
+Phase I: Format and initial speedups
+------------------------------------
+
+During this phase, Git learns to enable the sparse-index and safely parse
+one. Protections are put in place so that every consumer of the in-memory
+data structure can operate with its current assumption of every file at
+`HEAD`.
+
+At first, every index parse will call a helper method,
+`ensure_full_index()`, which scans the index for sparse-directory entries
+(pointing to trees) and replaces them with the full list of paths (with
+blob contents) by parsing tree objects. This will be slower in all cases.
+The only noticeable change in behavior will be that the serialized index
+file contains sparse-directory entries.
+
+To start, we use a new required index extension, `sdir`, to allow
+inserting sparse-directory entries into indexes with file format
+versions 2, 3, and 4. This prevents Git versions that do not understand
+the sparse-index from operating on one, while allowing tools that do not
+understand the sparse-index to operate on repositories as long as they do
+not interact with the index. A new format, index v5, will be introduced
+that includes sparse-directory entries by default. It might also
+introduce other features that have been considered for improving the
+index, as well.
+
+Next, consumers of the index will be guarded against operating on a
+sparse-index by inserting calls to `ensure_full_index()` or
+`expand_index_to_path()`. If a specific path is requested, then those will
+be protected from within the `index_file_exists()` and `index_name_pos()`
+API calls: they will call `ensure_full_index()` if necessary. The
+intention here is to preserve existing behavior when interacting with a
+sparse-checkout. We don't want a change to happen by accident, without
+tests. Many of these locations may not need any change before removing the
+guards, but we should not do so without tests to ensure the expected
+behavior happens.
+
+It may be desirable to _change_ the behavior of some commands in the
+presence of a sparse index or more generally in any sparse-checkout
+scenario. In such cases, these should be carefully communicated and
+tested. No such behavior changes are intended during this phase.
+
+During a scan of the codebase, not every iteration of the cache entries
+needs an `ensure_full_index()` check. The basic reasons include:
+
+1. The loop is scanning for entries with non-zero stage. These entries
+ are not collapsed into a sparse-directory entry.
+
+2. The loop is scanning for submodules. These entries are not collapsed
+ into a sparse-directory entry.
+
+3. The loop is part of the index API, especially around reading or
+ writing the format.
+
+4. The loop is checking for correct order of cache entries and that is
+ correct if and only if the sparse-directory entries are in the correct
+ location.
+
+5. The loop ignores entries with the `SKIP_WORKTREE` bit set, or is
+ otherwise already aware of sparse directory entries.
+
+6. The sparse-index is disabled at this point when using the split-index
+ feature, so no effort is made to protect the split-index API.
+
+Even after inserting these guards, we will keep expanding sparse-indexes
+for most Git commands using the `command_requires_full_index` repository
+setting. This setting will be on by default and disabled one builtin at a
+time until we have sufficient confidence that all of the index operations
+are properly guarded.
+
+To complete this phase, the commands `git status` and `git add` will be
+integrated with the sparse-index so that they operate with O(Populated)
+performance. They will be carefully tested for operations within and
+outside the sparse-checkout definition.
+
+Phase II: Careful integrations
+------------------------------
+
+This phase focuses on ensuring that all index extensions and APIs work
+well with a sparse-index. This requires significant increases to our test
+coverage, especially for operations that interact with the working
+directory outside of the sparse-checkout definition. Some of these
+behaviors may not be the desirable ones, such as some tests already
+marked for failure in `t1092-sparse-checkout-compatibility.sh`.
+
+The index extensions that may require special integrations are:
+
+* FS Monitor
+* Untracked cache
+
+While integrating with these features, we should look for patterns that
+might lead to better APIs for interacting with the index. Coalescing
+common usage patterns into an API call can reduce the number of places
+where sparse-directories need to be handled carefully.
+
+Phase III: Important command speedups
+-------------------------------------
+
+At this point, the patterns for testing and implementing sparse-directory
+logic should be relatively stable. This phase focuses on updating some of
+the most common builtins that use the index to operate as O(Populated).
+Here is a potential list of commands that could be valuable to integrate
+at this point:
+
+* `git commit`
+* `git checkout`
+* `git merge`
+* `git rebase`
+
+Hopefully, commands such as `git merge` and `git rebase` can benefit
+instead from merge algorithms that do not use the index as a data
+structure, such as the merge-ORT strategy. As these topics mature, we
+may enable the ORT strategy by default for repositories using the
+sparse-index feature.
+
+Along with `git status` and `git add`, these commands cover the majority
+of users' interactions with the working directory. In addition, we can
+integrate with these commands:
+
+* `git grep`
+* `git rm`
+
+These have been proposed as some whose behavior could change when in a
+repo with a sparse-checkout definition. It would be good to include this
+behavior automatically when using a sparse-index. Some clarity is needed
+to make the behavior switch clear to the user.
+
+This phase is the first where parallel work might be possible without too
+much conflicts between topics.
+
+Phase IV: The long tail
+-----------------------
+
+This last phase is less a "phase" and more "the new normal" after all of
+the previous work.
+
+To start, the `command_requires_full_index` option could be removed in
+favor of expanding only when hitting an API guard.
+
+There are many Git commands that could use special attention to operate as
+O(Populated), while some might be so rare that it is acceptable to leave
+them with additional overhead when a sparse-index is present.
+
+Here are some commands that might be useful to update:
+
+* `git sparse-checkout set`
+* `git am`
+* `git clean`
+* `git stash`
diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt
index fd480b8645..f9e54b8674 100644
--- a/Documentation/user-manual.txt
+++ b/Documentation/user-manual.txt
@@ -1,5 +1,8 @@
= Git User Manual
+[preface]
+== Introduction
+
Git is a fast distributed revision control system.
This manual is designed to be readable by someone with basic UNIX
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
index 8f6334d4e4..6fcb8fdf3b 100755
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
@@ -1,7 +1,7 @@
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v2.31.0
+DEF_VER=v2.32.0-rc2
LF='
'
diff --git a/INSTALL b/INSTALL
index 8474ad01bf..66389ce059 100644
--- a/INSTALL
+++ b/INSTALL
@@ -197,7 +197,9 @@ Issues of note:
Building and installing the pdf file additionally requires
dblatex. Version >= 0.2.7 is known to work.
- All formats require at least asciidoc 8.4.1.
+ All formats require at least asciidoc 8.4.1. Alternatively, you can
+ use Asciidoctor (requires Ruby) by passing USE_ASCIIDOCTOR=YesPlease
+ to make. You need at least Asciidoctor version 1.5.
There are also "make quick-install-doc", "make quick-install-man"
and "make quick-install-html" which install preformatted man pages
diff --git a/Makefile b/Makefile
index dfb0f1000f..c3565fc0f8 100644
--- a/Makefile
+++ b/Makefile
@@ -578,7 +578,9 @@ GENERATED_H =
EXTRA_CPPFLAGS =
FUZZ_OBJS =
FUZZ_PROGRAMS =
+GIT_OBJS =
LIB_OBJS =
+OBJECTS =
PROGRAM_OBJS =
PROGRAMS =
EXCLUDED_PROGRAMS =
@@ -587,6 +589,7 @@ SCRIPT_PYTHON =
SCRIPT_SH =
SCRIPT_LIB =
TEST_BUILTINS_OBJS =
+TEST_OBJS =
TEST_PROGRAMS_NEED_X =
THIRD_PARTY_SOURCES =
@@ -662,6 +665,8 @@ ETAGS_TARGET = TAGS
FUZZ_OBJS += fuzz-commit-graph.o
FUZZ_OBJS += fuzz-pack-headers.o
FUZZ_OBJS += fuzz-pack-idx.o
+.PHONY: fuzz-objs
+fuzz-objs: $(FUZZ_OBJS)
# Always build fuzz objects even if not testing, to prevent bit-rot.
all:: $(FUZZ_OBJS)
@@ -679,6 +684,8 @@ PROGRAM_OBJS += http-backend.o
PROGRAM_OBJS += imap-send.o
PROGRAM_OBJS += sh-i18n--envsubst.o
PROGRAM_OBJS += shell.o
+.PHONY: program-objs
+program-objs: $(PROGRAM_OBJS)
# Binary suffix, set to .exe for Windows builds
X =
@@ -686,6 +693,7 @@ X =
PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))
TEST_BUILTINS_OBJS += test-advise.o
+TEST_BUILTINS_OBJS += test-bitmap.o
TEST_BUILTINS_OBJS += test-bloom.o
TEST_BUILTINS_OBJS += test-chmtime.o
TEST_BUILTINS_OBJS += test-config.o
@@ -737,6 +745,7 @@ TEST_BUILTINS_OBJS += test-serve-v2.o
TEST_BUILTINS_OBJS += test-sha1.o
TEST_BUILTINS_OBJS += test-sha256.o
TEST_BUILTINS_OBJS += test-sigchain.o
+TEST_BUILTINS_OBJS += test-simple-ipc.o
TEST_BUILTINS_OBJS += test-strcmp-offset.o
TEST_BUILTINS_OBJS += test-string-list.o
TEST_BUILTINS_OBJS += test-submodule-config.o
@@ -744,6 +753,7 @@ TEST_BUILTINS_OBJS += test-submodule-nested-repo-config.o
TEST_BUILTINS_OBJS += test-subprocess.o
TEST_BUILTINS_OBJS += test-trace2.o
TEST_BUILTINS_OBJS += test-urlmatch-normalization.o
+TEST_BUILTINS_OBJS += test-userdiff.o
TEST_BUILTINS_OBJS += test-wildmatch.o
TEST_BUILTINS_OBJS += test-windows-named-pipe.o
TEST_BUILTINS_OBJS += test-write-cache.o
@@ -938,6 +948,7 @@ LIB_OBJS += pack-revindex.o
LIB_OBJS += pack-write.o
LIB_OBJS += packfile.o
LIB_OBJS += pager.o
+LIB_OBJS += parallel-checkout.o
LIB_OBJS += parse-options-cb.o
LIB_OBJS += parse-options.o
LIB_OBJS += patch-delta.o
@@ -952,6 +963,7 @@ LIB_OBJS += progress.o
LIB_OBJS += promisor-remote.o
LIB_OBJS += prompt.o
LIB_OBJS += protocol.o
+LIB_OBJS += protocol-caps.o
LIB_OBJS += prune-packed.o
LIB_OBJS += quote.o
LIB_OBJS += range-diff.o
@@ -985,6 +997,7 @@ LIB_OBJS += setup.o
LIB_OBJS += shallow.o
LIB_OBJS += sideband.o
LIB_OBJS += sigchain.o
+LIB_OBJS += sparse-index.o
LIB_OBJS += split-index.o
LIB_OBJS += stable-qsort.o
LIB_OBJS += strbuf.o
@@ -1053,6 +1066,7 @@ BUILTIN_OBJS += builtin/check-attr.o
BUILTIN_OBJS += builtin/check-ignore.o
BUILTIN_OBJS += builtin/check-mailmap.o
BUILTIN_OBJS += builtin/check-ref-format.o
+BUILTIN_OBJS += builtin/checkout--worker.o
BUILTIN_OBJS += builtin/checkout-index.o
BUILTIN_OBJS += builtin/checkout.o
BUILTIN_OBJS += builtin/clean.o
@@ -1672,6 +1686,32 @@ ifdef NO_UNIX_SOCKETS
BASIC_CFLAGS += -DNO_UNIX_SOCKETS
else
LIB_OBJS += unix-socket.o
+ LIB_OBJS += unix-stream-server.o
+endif
+
+# Simple IPC requires threads and platform-specific IPC support.
+# Only platforms that have both should include these source files
+# in the build.
+#
+# On Windows-based systems, Simple IPC requires threads and Windows
+# Named Pipes. These are always available, so Simple IPC support
+# is optional.
+#
+# On Unix-based systems, Simple IPC requires pthreads and Unix
+# domain sockets. So support is only enabled when both are present.
+#
+ifdef USE_WIN32_IPC
+ BASIC_CFLAGS += -DSUPPORTS_SIMPLE_IPC
+ LIB_OBJS += compat/simple-ipc/ipc-shared.o
+ LIB_OBJS += compat/simple-ipc/ipc-win32.o
+else
+ifndef NO_PTHREADS
+ifndef NO_UNIX_SOCKETS
+ BASIC_CFLAGS += -DSUPPORTS_SIMPLE_IPC
+ LIB_OBJS += compat/simple-ipc/ipc-shared.o
+ LIB_OBJS += compat/simple-ipc/ipc-unix-socket.o
+endif
+endif
endif
ifdef NO_ICONV
@@ -1965,6 +2005,7 @@ ETC_GITCONFIG_SQ = $(subst ','\'',$(ETC_GITCONFIG))
ETC_GITATTRIBUTES_SQ = $(subst ','\'',$(ETC_GITATTRIBUTES))
DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
+NO_GETTEXT_SQ = $(subst ','\'',$(NO_GETTEXT))
bindir_SQ = $(subst ','\'',$(bindir))
bindir_relative_SQ = $(subst ','\'',$(bindir_relative))
mandir_SQ = $(subst ','\'',$(mandir))
@@ -2186,13 +2227,13 @@ $(BUILT_INS): git$X
config-list.h: generate-configlist.sh
-config-list.h:
+config-list.h: Documentation/*config.txt Documentation/config/*.txt
$(QUIET_GEN)$(SHELL_PATH) ./generate-configlist.sh \
>$@+ && mv $@+ $@
command-list.h: generate-cmdlist.sh command-list.txt
-command-list.h: $(wildcard Documentation/git*.txt) Documentation/*config.txt Documentation/config/*.txt
+command-list.h: $(wildcard Documentation/git*.txt)
$(QUIET_GEN)$(SHELL_PATH) ./generate-cmdlist.sh \
$(patsubst %,--exclude-program %,$(EXCLUDED_PROGRAMS)) \
command-list.txt >$@+ && mv $@+ $@
@@ -2249,10 +2290,13 @@ perl_localedir_SQ = $(localedir_SQ)
ifndef NO_PERL
PERL_HEADER_TEMPLATE = perl/header_templates/fixed_prefix.template.pl
-PERL_DEFINES = $(PERL_PATH_SQ):$(PERLLIB_EXTRA_SQ):$(perllibdir_SQ)
-
-PERL_DEFINES := $(PERL_PATH_SQ) $(PERLLIB_EXTRA_SQ) $(perllibdir_SQ)
+PERL_DEFINES =
+PERL_DEFINES += $(PERL_PATH_SQ)
+PERL_DEFINES += $(PERLLIB_EXTRA_SQ)
+PERL_DEFINES += $(perllibdir_SQ)
PERL_DEFINES += $(RUNTIME_PREFIX)
+PERL_DEFINES += $(NO_PERL_CPAN_FALLBACKS)
+PERL_DEFINES += $(NO_GETTEXT)
# Support Perl runtime prefix. In this mode, a different header is installed
# into Perl scripts.
@@ -2378,16 +2422,30 @@ XDIFF_OBJS += xdiff/xmerge.o
XDIFF_OBJS += xdiff/xpatience.o
XDIFF_OBJS += xdiff/xprepare.o
XDIFF_OBJS += xdiff/xutils.o
+.PHONY: xdiff-objs
+xdiff-objs: $(XDIFF_OBJS)
TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))
-OBJECTS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \
- $(XDIFF_OBJS) \
- $(FUZZ_OBJS) \
- common-main.o \
- git.o
+.PHONY: test-objs
+test-objs: $(TEST_OBJS)
+
+GIT_OBJS += $(LIB_OBJS)
+GIT_OBJS += $(BUILTIN_OBJS)
+GIT_OBJS += common-main.o
+GIT_OBJS += git.o
+.PHONY: git-objs
+git-objs: $(GIT_OBJS)
+
+OBJECTS += $(GIT_OBJS)
+OBJECTS += $(PROGRAM_OBJS)
+OBJECTS += $(TEST_OBJS)
+OBJECTS += $(XDIFF_OBJS)
+OBJECTS += $(FUZZ_OBJS)
ifndef NO_CURL
OBJECTS += http.o http-walker.o remote-curl.o
endif
+.PHONY: objects
+objects: $(OBJECTS)
dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d)
dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS))))
@@ -2640,9 +2698,10 @@ endif
NO_PERL_CPAN_FALLBACKS_SQ = $(subst ','\'',$(NO_PERL_CPAN_FALLBACKS))
endif
-perl/build/lib/%.pm: perl/%.pm
+perl/build/lib/%.pm: perl/%.pm GIT-PERL-DEFINES
$(QUIET_GEN)mkdir -p $(dir $@) && \
sed -e 's|@@LOCALEDIR@@|$(perl_localedir_SQ)|g' \
+ -e 's|@@NO_GETTEXT@@|$(NO_GETTEXT_SQ)|g' \
-e 's|@@NO_PERL_CPAN_FALLBACKS@@|$(NO_PERL_CPAN_FALLBACKS_SQ)|g' \
< $< > $@
@@ -2669,12 +2728,14 @@ FIND_SOURCE_FILES = ( \
)
$(ETAGS_TARGET): FORCE
- $(RM) $(ETAGS_TARGET)
- $(FIND_SOURCE_FILES) | xargs etags -a -o $(ETAGS_TARGET)
+ $(QUIET_GEN)$(RM) "$(ETAGS_TARGET)+" && \
+ $(FIND_SOURCE_FILES) | xargs etags -a -o "$(ETAGS_TARGET)+" && \
+ mv "$(ETAGS_TARGET)+" "$(ETAGS_TARGET)"
tags: FORCE
- $(RM) tags
- $(FIND_SOURCE_FILES) | xargs ctags -a
+ $(QUIET_GEN)$(RM) tags+ && \
+ $(FIND_SOURCE_FILES) | xargs ctags -a -o tags+ && \
+ mv tags+ tags
cscope:
$(RM) cscope*
@@ -3299,11 +3360,11 @@ cover_db_html: cover_db
# are not necessarily appropriate for general builds, and that vary greatly
# depending on the compiler version used.
#
-# An example command to build against libFuzzer from LLVM 4.0.0:
+# An example command to build against libFuzzer from LLVM 11.0.0:
#
# make CC=clang CXX=clang++ \
-# CFLAGS="-fsanitize-coverage=trace-pc-guard -fsanitize=address" \
-# LIB_FUZZING_ENGINE=/usr/lib/llvm-4.0/lib/libFuzzer.a \
+# CFLAGS="-fsanitize=fuzzer-no-link,address" \
+# LIB_FUZZING_ENGINE="-fsanitize=fuzzer" \
# fuzz-all
#
FUZZ_CXXFLAGS ?= $(CFLAGS)
diff --git a/RelNotes b/RelNotes
index 3324fc058d..aece21e8a4 120000
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.31.0.txt \ No newline at end of file
+Documentation/RelNotes/2.32.0.txt \ No newline at end of file
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000000..c720c2ae7f
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,51 @@
+# Security Policy
+
+## Reporting a vulnerability
+
+Please send a detailed mail to git-security@googlegroups.com to
+report vulnerabilities in Git.
+
+Even when unsure whether the bug in question is an exploitable
+vulnerability, it is recommended to send the report to
+git-security@googlegroups.com (and obviously not to discuss the
+issue anywhere else).
+
+Vulnerabilities are expected to be discussed _only_ on that
+list, and not in public, until the official announcement on the
+Git mailing list on the release date.
+
+Examples for details to include:
+
+- Ideally a short description (or a script) to demonstrate an
+ exploit.
+- The affected platforms and scenarios (the vulnerability might
+ only affect setups with case-sensitive file systems, for
+ example).
+- The name and affiliation of the security researchers who are
+ involved in the discovery, if any.
+- Whether the vulnerability has already been disclosed.
+- How long an embargo would be required to be safe.
+
+## Supported Versions
+
+There are no official "Long Term Support" versions in Git.
+Instead, the maintenance track (i.e. the versions based on the
+most recently published feature release, also known as ".0"
+version) sees occasional updates with bug fixes.
+
+Fixes to vulnerabilities are made for the maintenance track for
+the latest feature release and merged up to the in-development
+branches. The Git project makes no formal guarantee for any
+older maintenance tracks to receive updates. In practice,
+though, critical vulnerability fixes are applied not only to the
+most recent track, but to at least a couple more maintenance
+tracks.
+
+This is typically done by making the fix on the oldest and still
+relevant maintenance track, and merging it upwards to newer and
+newer maintenance tracks.
+
+For example, v2.24.1 was released to address a couple of
+[CVEs](https://cve.mitre.org/), and at the same time v2.14.6,
+v2.15.4, v2.16.6, v2.17.3, v2.18.2, v2.19.3, v2.20.2, v2.21.1,
+v2.22.2 and v2.23.1 were released.
diff --git a/add-interactive.c b/add-interactive.c
index 9b8cdb4a31..36ebdbdf7e 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -413,7 +413,7 @@ struct file_item {
static void add_file_item(struct string_list *files, const char *name)
{
- struct file_item *item = xcalloc(sizeof(*item), 1);
+ struct file_item *item = xcalloc(1, sizeof(*item));
string_list_append(files, name)->util = item;
}
@@ -476,7 +476,7 @@ static void collect_changes_cb(struct diff_queue_struct *q,
add_file_item(s->files, name);
- entry = xcalloc(sizeof(*entry), 1);
+ CALLOC_ARRAY(entry, 1);
hashmap_entry_init(&entry->ent, hash);
entry->name = s->files->items[s->files->nr - 1].string;
entry->item = s->files->items[s->files->nr - 1].util;
@@ -1120,7 +1120,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
int res = 0;
for (i = 0; i < ARRAY_SIZE(command_list); i++) {
- struct command_item *util = xcalloc(sizeof(*util), 1);
+ struct command_item *util = xcalloc(1, sizeof(*util));
util->command = command_list[i].command;
string_list_append(&commands.items, command_list[i].string)
->util = util;
diff --git a/advice.c b/advice.c
index 164742305f..0b9c89c48a 100644
--- a/advice.c
+++ b/advice.c
@@ -2,6 +2,7 @@
#include "config.h"
#include "color.h"
#include "help.h"
+#include "string-list.h"
int advice_fetch_show_forced_updates = 1;
int advice_push_update_rejected = 1;
@@ -136,6 +137,7 @@ static struct {
[ADVICE_STATUS_HINTS] = { "statusHints", 1 },
[ADVICE_STATUS_U_OPTION] = { "statusUoption", 1 },
[ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { "submoduleAlternateErrorStrategyDie", 1 },
+ [ADVICE_UPDATE_SPARSE_PATH] = { "updateSparsePath", 1 },
[ADVICE_WAITING_FOR_EDITOR] = { "waitingForEditor", 1 },
};
@@ -284,6 +286,24 @@ void NORETURN die_conclude_merge(void)
die(_("Exiting because of unfinished merge."));
}
+void advise_on_updating_sparse_paths(struct string_list *pathspec_list)
+{
+ struct string_list_item *item;
+
+ 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"));
+ 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."));
+}
+
void detach_advice(const char *new_name)
{
const char *fmt =
diff --git a/advice.h b/advice.h
index bc2432980a..bd26c385d0 100644
--- a/advice.h
+++ b/advice.h
@@ -3,6 +3,8 @@
#include "git-compat-util.h"
+struct string_list;
+
extern int advice_fetch_show_forced_updates;
extern int advice_push_update_rejected;
extern int advice_push_non_ff_current;
@@ -71,6 +73,7 @@ extern int advice_add_empty_pathspec;
ADVICE_STATUS_HINTS,
ADVICE_STATUS_U_OPTION,
ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,
+ ADVICE_UPDATE_SPARSE_PATH,
ADVICE_WAITING_FOR_EDITOR,
};
@@ -92,6 +95,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 advise_on_updating_sparse_paths(struct string_list *pathspec_list);
void detach_advice(const char *new_name);
#endif /* ADVICE_H */
diff --git a/apply.c b/apply.c
index 668b16e989..853d3ed385 100644
--- a/apply.c
+++ b/apply.c
@@ -21,6 +21,7 @@
#include "quote.h"
#include "rerere.h"
#include "apply.h"
+#include "entry.h"
struct gitdiff_data {
struct strbuf *root;
@@ -133,8 +134,6 @@ int check_apply_state(struct apply_state *state, int force_apply)
if (state->apply_with_reject && state->threeway)
return error(_("--reject and --3way cannot be used together."));
- if (state->cached && state->threeway)
- return error(_("--cached and --3way cannot be used together."));
if (state->threeway) {
if (is_not_gitdir)
return error(_("--3way outside a repository"));
@@ -1781,7 +1780,7 @@ static int parse_single_patch(struct apply_state *state,
struct fragment *fragment;
int len;
- fragment = xcalloc(1, sizeof(*fragment));
+ CALLOC_ARRAY(fragment, 1);
fragment->linenr = state->linenr;
len = parse_fragment(state, line, size, patch, fragment);
if (len <= 0) {
@@ -1959,7 +1958,7 @@ static struct fragment *parse_binary_hunk(struct apply_state *state,
size -= llen;
}
- frag = xcalloc(1, sizeof(*frag));
+ CALLOC_ARRAY(frag, 1);
frag->patch = inflate_it(data, hunk_size, origlen);
frag->free_patch = 1;
if (!frag->patch)
@@ -3569,10 +3568,10 @@ static int try_threeway(struct apply_state *state,
write_object_file("", 0, blob_type, &pre_oid);
else if (get_oid(patch->old_oid_prefix, &pre_oid) ||
read_blob_object(&buf, &pre_oid, patch->old_mode))
- return error(_("repository lacks the necessary blob to fall back on 3-way merge."));
+ return error(_("repository lacks the necessary blob to perform 3-way merge."));
- if (state->apply_verbosity > verbosity_silent)
- fprintf(stderr, _("Falling back to three-way merge...\n"));
+ if (state->apply_verbosity > verbosity_silent && patch->direct_to_threeway)
+ fprintf(stderr, _("Performing three-way merge...\n"));
img = strbuf_detach(&buf, &len);
prepare_image(&tmp_image, img, len, 1);
@@ -3604,7 +3603,7 @@ static int try_threeway(struct apply_state *state,
if (status < 0) {
if (state->apply_verbosity > verbosity_silent)
fprintf(stderr,
- _("Failed to fall back on three-way merge...\n"));
+ _("Failed to perform three-way merge...\n"));
return status;
}
@@ -3637,10 +3636,13 @@ static int apply_data(struct apply_state *state, struct patch *patch,
if (load_preimage(state, &image, patch, st, ce) < 0)
return -1;
- if (patch->direct_to_threeway ||
- apply_fragments(state, &image, patch) < 0) {
+ if (!state->threeway || try_threeway(state, &image, patch, st, ce) < 0) {
+ if (state->apply_verbosity > verbosity_silent &&
+ state->threeway && !patch->direct_to_threeway)
+ fprintf(stderr, _("Falling back to direct application...\n"));
+
/* Note: with --reject, apply_fragments() returns 0 */
- if (!state->threeway || try_threeway(state, &image, patch, st, ce) < 0)
+ if (patch->direct_to_threeway || apply_fragments(state, &image, patch) < 0)
return -1;
}
patch->result = image.buf;
@@ -4646,7 +4648,12 @@ static int write_out_results(struct apply_state *state, struct patch *list)
}
string_list_clear(&cpath, 0);
- repo_rerere(state->repo, 0);
+ /*
+ * rerere relies on the partially merged result being in the working
+ * tree with conflict markers, but that isn't written with --cached.
+ */
+ if (!state->cached)
+ repo_rerere(state->repo, 0);
}
return errs;
@@ -4681,7 +4688,7 @@ static int apply_patch(struct apply_state *state,
struct patch *patch;
int nr;
- patch = xcalloc(1, sizeof(*patch));
+ CALLOC_ARRAY(patch, 1);
patch->inaccurate_eof = !!(options & APPLY_OPT_INACCURATE_EOF);
patch->recount = !!(options & APPLY_OPT_RECOUNT);
nr = parse_chunk(state, buf.buf + offset, buf.len - offset, patch);
@@ -5017,7 +5024,7 @@ int apply_parse_options(int argc, const char **argv,
OPT_BOOL(0, "apply", force_apply,
N_("also apply the patch (use with --stat/--summary/--check)")),
OPT_BOOL('3', "3way", &state->threeway,
- N_( "attempt three-way merge if a patch does not apply")),
+ N_( "attempt three-way merge, fall back on normal patch if that fails")),
OPT_FILENAME(0, "build-fake-ancestor", &state->fake_ancestor,
N_("build a temporary index based on embedded index information")),
/* Think twice before adding "--nul" synonym to this */
diff --git a/archive-tar.c b/archive-tar.c
index a971fdc0f6..05d2455870 100644
--- a/archive-tar.c
+++ b/archive-tar.c
@@ -371,7 +371,7 @@ static int tar_filter_config(const char *var, const char *value, void *data)
ar = find_tar_filter(name, namelen);
if (!ar) {
- ar = xcalloc(1, sizeof(*ar));
+ CALLOC_ARRAY(ar, 1);
ar->name = xmemdupz(name, namelen);
ar->write_archive = write_tar_filter_archive;
ar->flags = ARCHIVER_WANT_COMPRESSION_LEVELS |
diff --git a/archive.c b/archive.c
index 5919d9e505..ff2bb54f62 100644
--- a/archive.c
+++ b/archive.c
@@ -37,13 +37,10 @@ void init_archivers(void)
static void format_subst(const struct commit *commit,
const char *src, size_t len,
- struct strbuf *buf)
+ struct strbuf *buf, struct pretty_print_context *ctx)
{
char *to_free = NULL;
struct strbuf fmt = STRBUF_INIT;
- struct pretty_print_context ctx = {0};
- ctx.date_mode.type = DATE_NORMAL;
- ctx.abbrev = DEFAULT_ABBREV;
if (src == buf->buf)
to_free = strbuf_detach(buf, NULL);
@@ -61,7 +58,7 @@ static void format_subst(const struct commit *commit,
strbuf_add(&fmt, b + 8, c - b - 8);
strbuf_add(buf, src, b - src);
- format_commit_message(commit, fmt.buf, buf, &ctx);
+ format_commit_message(commit, fmt.buf, buf, ctx);
len -= c + 1 - src;
src = c + 1;
}
@@ -94,7 +91,7 @@ static void *object_file_to_archive(const struct archiver_args *args,
strbuf_attach(&buf, buffer, *sizep, *sizep + 1);
convert_to_working_tree(args->repo->index, path, buf.buf, buf.len, &buf, &meta);
if (commit)
- format_subst(commit, buf.buf, buf.len, &buf);
+ format_subst(commit, buf.buf, buf.len, &buf, args->pretty_ctx);
buffer = strbuf_detach(&buf, &size);
*sizep = size;
}
@@ -107,7 +104,6 @@ struct directory {
struct object_id oid;
int baselen, len;
unsigned mode;
- int stage;
char path[FLEX_ARRAY];
};
@@ -138,7 +134,7 @@ static int check_attr_export_subst(const struct attr_check *check)
}
static int write_archive_entry(const struct object_id *oid, const char *base,
- int baselen, const char *filename, unsigned mode, int stage,
+ int baselen, const char *filename, unsigned mode,
void *context)
{
static struct strbuf path = STRBUF_INIT;
@@ -197,7 +193,7 @@ static int write_archive_entry(const struct object_id *oid, const char *base,
static void queue_directory(const unsigned char *sha1,
struct strbuf *base, const char *filename,
- unsigned mode, int stage, struct archiver_context *c)
+ unsigned mode, struct archiver_context *c)
{
struct directory *d;
size_t len = st_add4(base->len, 1, strlen(filename), 1);
@@ -205,10 +201,9 @@ static void queue_directory(const unsigned char *sha1,
d->up = c->bottom;
d->baselen = base->len;
d->mode = mode;
- d->stage = stage;
c->bottom = d;
d->len = xsnprintf(d->path, len, "%.*s%s/", (int)base->len, base->buf, filename);
- hashcpy(d->oid.hash, sha1);
+ oidread(&d->oid, sha1);
}
static int write_directory(struct archiver_context *c)
@@ -224,14 +219,14 @@ static int write_directory(struct archiver_context *c)
write_directory(c) ||
write_archive_entry(&d->oid, d->path, d->baselen,
d->path + d->baselen, d->mode,
- d->stage, c) != READ_TREE_RECURSIVE;
+ c) != READ_TREE_RECURSIVE;
free(d);
return ret ? -1 : 0;
}
static int queue_or_write_archive_entry(const struct object_id *oid,
struct strbuf *base, const char *filename,
- unsigned mode, int stage, void *context)
+ unsigned mode, void *context)
{
struct archiver_context *c = context;
@@ -256,14 +251,14 @@ 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, stage, c);
+ mode, c);
return READ_TREE_RECURSIVE;
}
if (write_directory(c))
return -1;
return write_archive_entry(oid, base->buf, base->len, filename, mode,
- stage, context);
+ context);
}
struct extra_file_info {
@@ -280,9 +275,11 @@ int write_archive_entries(struct archiver_args *args,
int err;
struct strbuf path_in_archive = STRBUF_INIT;
struct strbuf content = STRBUF_INIT;
- struct object_id fake_oid = null_oid;
+ struct object_id fake_oid;
int i;
+ oidcpy(&fake_oid, null_oid());
+
if (args->baselen > 0 && args->base[args->baselen - 1] == '/') {
size_t len = args->baselen;
@@ -316,10 +313,10 @@ int write_archive_entries(struct archiver_args *args,
git_attr_set_direction(GIT_ATTR_INDEX);
}
- err = read_tree_recursive(args->repo, args->tree, "",
- 0, 0, &args->pathspec,
- queue_or_write_archive_entry,
- &context);
+ err = read_tree(args->repo, args->tree,
+ &args->pathspec,
+ queue_or_write_archive_entry,
+ &context);
if (err == READ_TREE_RECURSIVE)
err = 0;
while (context.bottom) {
@@ -378,7 +375,7 @@ struct path_exists_context {
static int reject_entry(const struct object_id *oid, struct strbuf *base,
const char *filename, unsigned mode,
- int stage, void *context)
+ void *context)
{
int ret = -1;
struct path_exists_context *ctx = context;
@@ -405,9 +402,9 @@ static int path_exists(struct archiver_args *args, const char *path)
ctx.args = args;
parse_pathspec(&ctx.pathspec, 0, 0, "", paths);
ctx.pathspec.recursive = 1;
- ret = read_tree_recursive(args->repo, args->tree, "",
- 0, 0, &ctx.pathspec,
- reject_entry, &ctx);
+ ret = read_tree(args->repo, args->tree,
+ &ctx.pathspec,
+ reject_entry, &ctx);
clear_pathspec(&ctx.pathspec);
return ret != 0;
}
@@ -633,12 +630,19 @@ int write_archive(int argc, const char **argv, const char *prefix,
const char *name_hint, int remote)
{
const struct archiver *ar = NULL;
+ struct pretty_print_describe_status describe_status = {0};
+ struct pretty_print_context ctx = {0};
struct archiver_args args;
int rc;
git_config_get_bool("uploadarchive.allowunreachable", &remote_allow_unreachable);
git_config(git_default_config, NULL);
+ describe_status.max_invocations = 1;
+ ctx.date_mode.type = DATE_NORMAL;
+ ctx.abbrev = DEFAULT_ABBREV;
+ ctx.describe_status = &describe_status;
+ args.pretty_ctx = &ctx;
args.repo = repo;
args.prefix = prefix;
string_list_init(&args.extra_files, 1);
diff --git a/archive.h b/archive.h
index 33551b7ee1..49fab71aaf 100644
--- a/archive.h
+++ b/archive.h
@@ -5,6 +5,7 @@
#include "pathspec.h"
struct repository;
+struct pretty_print_context;
struct archiver_args {
struct repository *repo;
@@ -22,6 +23,7 @@ struct archiver_args {
unsigned int convert : 1;
int compression_level;
struct string_list extra_files;
+ struct pretty_print_context *pretty_ctx;
};
/* main api */
diff --git a/attr.c b/attr.c
index 4ef85d668b..9e897e43f5 100644
--- a/attr.c
+++ b/attr.c
@@ -278,6 +278,10 @@ struct match_attr {
static const char blank[] = " \t\r\n";
+/* Flags usable in read_attr() and parse_attr_line() family of functions. */
+#define READ_ATTR_MACRO_OK (1<<0)
+#define READ_ATTR_NOFOLLOW (1<<1)
+
/*
* Parse a whitespace-delimited attribute state (i.e., "attr",
* "-attr", "!attr", or "attr=value") from the string starting at src.
@@ -331,7 +335,7 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,
}
static struct match_attr *parse_attr_line(const char *line, const char *src,
- int lineno, int macro_ok)
+ int lineno, unsigned flags)
{
int namelen;
int num_attr, i;
@@ -355,7 +359,7 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
if (strlen(ATTRIBUTE_MACRO_PREFIX) < namelen &&
starts_with(name, ATTRIBUTE_MACRO_PREFIX)) {
- if (!macro_ok) {
+ if (!(flags & READ_ATTR_MACRO_OK)) {
fprintf_ln(stderr, _("%s not allowed: %s:%d"),
name, src, lineno);
goto fail_return;
@@ -569,7 +573,7 @@ struct attr_check *attr_check_initl(const char *one, ...)
check = attr_check_alloc();
check->nr = cnt;
check->alloc = cnt;
- check->items = xcalloc(cnt, sizeof(struct attr_check_item));
+ CALLOC_ARRAY(check->items, cnt);
check->items[0].attr = git_attr(one);
va_start(params, one);
@@ -653,11 +657,11 @@ static void handle_attr_line(struct attr_stack *res,
const char *line,
const char *src,
int lineno,
- int macro_ok)
+ unsigned flags)
{
struct match_attr *a;
- a = parse_attr_line(line, src, lineno, macro_ok);
+ a = parse_attr_line(line, src, lineno, flags);
if (!a)
return;
ALLOC_GROW(res->attrs, res->num_matches + 1, res->alloc);
@@ -670,9 +674,10 @@ static struct attr_stack *read_attr_from_array(const char **list)
const char *line;
int lineno = 0;
- res = xcalloc(1, sizeof(*res));
+ CALLOC_ARRAY(res, 1);
while ((line = *(list++)) != NULL)
- handle_attr_line(res, line, "[builtin]", ++lineno, 1);
+ handle_attr_line(res, line, "[builtin]", ++lineno,
+ READ_ATTR_MACRO_OK);
return res;
}
@@ -698,29 +703,39 @@ void git_attr_set_direction(enum git_attr_direction new_direction)
direction = new_direction;
}
-static struct attr_stack *read_attr_from_file(const char *path, int macro_ok)
+static struct attr_stack *read_attr_from_file(const char *path, unsigned flags)
{
- FILE *fp = fopen_or_warn(path, "r");
+ int fd;
+ FILE *fp;
struct attr_stack *res;
char buf[2048];
int lineno = 0;
- if (!fp)
+ if (flags & READ_ATTR_NOFOLLOW)
+ fd = open_nofollow(path, O_RDONLY);
+ else
+ fd = open(path, O_RDONLY);
+
+ if (fd < 0) {
+ warn_on_fopen_errors(path);
return NULL;
- res = xcalloc(1, sizeof(*res));
+ }
+ fp = xfdopen(fd, "r");
+
+ CALLOC_ARRAY(res, 1);
while (fgets(buf, sizeof(buf), fp)) {
char *bufp = buf;
if (!lineno)
skip_utf8_bom(&bufp, strlen(bufp));
- handle_attr_line(res, bufp, path, ++lineno, macro_ok);
+ handle_attr_line(res, bufp, path, ++lineno, flags);
}
fclose(fp);
return res;
}
-static struct attr_stack *read_attr_from_index(const struct index_state *istate,
+static struct attr_stack *read_attr_from_index(struct index_state *istate,
const char *path,
- int macro_ok)
+ unsigned flags)
{
struct attr_stack *res;
char *buf, *sp;
@@ -733,7 +748,7 @@ static struct attr_stack *read_attr_from_index(const struct index_state *istate,
if (!buf)
return NULL;
- res = xcalloc(1, sizeof(*res));
+ CALLOC_ARRAY(res, 1);
for (sp = buf; *sp; ) {
char *ep;
int more;
@@ -741,27 +756,27 @@ static struct attr_stack *read_attr_from_index(const struct index_state *istate,
ep = strchrnul(sp, '\n');
more = (*ep == '\n');
*ep = '\0';
- handle_attr_line(res, sp, path, ++lineno, macro_ok);
+ handle_attr_line(res, sp, path, ++lineno, flags);
sp = ep + more;
}
free(buf);
return res;
}
-static struct attr_stack *read_attr(const struct index_state *istate,
- const char *path, int macro_ok)
+static struct attr_stack *read_attr(struct index_state *istate,
+ const char *path, unsigned flags)
{
struct attr_stack *res = NULL;
if (direction == GIT_ATTR_INDEX) {
- res = read_attr_from_index(istate, path, macro_ok);
+ res = read_attr_from_index(istate, path, flags);
} else if (!is_bare_repository()) {
if (direction == GIT_ATTR_CHECKOUT) {
- res = read_attr_from_index(istate, path, macro_ok);
+ res = read_attr_from_index(istate, path, flags);
if (!res)
- res = read_attr_from_file(path, macro_ok);
+ res = read_attr_from_file(path, flags);
} else if (direction == GIT_ATTR_CHECKIN) {
- res = read_attr_from_file(path, macro_ok);
+ res = read_attr_from_file(path, flags);
if (!res)
/*
* There is no checked out .gitattributes file
@@ -769,12 +784,12 @@ static struct attr_stack *read_attr(const struct index_state *istate,
* We allow operation in a sparsely checked out
* work tree, so read from it.
*/
- res = read_attr_from_index(istate, path, macro_ok);
+ res = read_attr_from_index(istate, path, flags);
}
}
if (!res)
- res = xcalloc(1, sizeof(*res));
+ CALLOC_ARRAY(res, 1);
return res;
}
@@ -840,10 +855,11 @@ static void push_stack(struct attr_stack **attr_stack_p,
}
}
-static void bootstrap_attr_stack(const struct index_state *istate,
+static void bootstrap_attr_stack(struct index_state *istate,
struct attr_stack **stack)
{
struct attr_stack *e;
+ unsigned flags = READ_ATTR_MACRO_OK;
if (*stack)
return;
@@ -854,31 +870,31 @@ static void bootstrap_attr_stack(const struct index_state *istate,
/* system-wide frame */
if (git_attr_system()) {
- e = read_attr_from_file(git_etc_gitattributes(), 1);
+ e = read_attr_from_file(git_etc_gitattributes(), flags);
push_stack(stack, e, NULL, 0);
}
/* home directory */
if (get_home_gitattributes()) {
- e = read_attr_from_file(get_home_gitattributes(), 1);
+ e = read_attr_from_file(get_home_gitattributes(), flags);
push_stack(stack, e, NULL, 0);
}
/* root directory */
- e = read_attr(istate, GITATTRIBUTES_FILE, 1);
+ e = read_attr(istate, GITATTRIBUTES_FILE, flags | READ_ATTR_NOFOLLOW);
push_stack(stack, e, xstrdup(""), 0);
/* info frame */
if (startup_info->have_repository)
- e = read_attr_from_file(git_path_info_attributes(), 1);
+ e = read_attr_from_file(git_path_info_attributes(), flags);
else
e = NULL;
if (!e)
- e = xcalloc(1, sizeof(struct attr_stack));
+ CALLOC_ARRAY(e, 1);
push_stack(stack, e, NULL, 0);
}
-static void prepare_attr_stack(const struct index_state *istate,
+static void prepare_attr_stack(struct index_state *istate,
const char *path, int dirlen,
struct attr_stack **stack)
{
@@ -956,7 +972,7 @@ static void prepare_attr_stack(const struct index_state *istate,
strbuf_add(&pathbuf, path + pathbuf.len, (len - pathbuf.len));
strbuf_addf(&pathbuf, "/%s", GITATTRIBUTES_FILE);
- next = read_attr(istate, pathbuf.buf, 0);
+ next = read_attr(istate, pathbuf.buf, READ_ATTR_NOFOLLOW);
/* reset the pathbuf to not include "/.gitattributes" */
strbuf_setlen(&pathbuf, len);
@@ -1078,7 +1094,7 @@ static void determine_macros(struct all_attrs_item *all_attrs,
* If check->check_nr is non-zero, only attributes in check[] are collected.
* Otherwise all attributes are collected.
*/
-static void collect_some_attrs(const struct index_state *istate,
+static void collect_some_attrs(struct index_state *istate,
const char *path,
struct attr_check *check)
{
@@ -1107,7 +1123,7 @@ static void collect_some_attrs(const struct index_state *istate,
fill(path, pathlen, basename_offset, check->stack, check->all_attrs, rem);
}
-void git_check_attr(const struct index_state *istate,
+void git_check_attr(struct index_state *istate,
const char *path,
struct attr_check *check)
{
@@ -1124,7 +1140,7 @@ void git_check_attr(const struct index_state *istate,
}
}
-void git_all_attrs(const struct index_state *istate,
+void git_all_attrs(struct index_state *istate,
const char *path, struct attr_check *check)
{
int i;
diff --git a/attr.h b/attr.h
index 404548f028..3732505eda 100644
--- a/attr.h
+++ b/attr.h
@@ -190,14 +190,14 @@ void attr_check_free(struct attr_check *check);
*/
const char *git_attr_name(const struct git_attr *);
-void git_check_attr(const struct index_state *istate,
+void git_check_attr(struct index_state *istate,
const char *path, struct attr_check *check);
/*
* Retrieve all attributes that apply to the specified path.
* check holds the attributes and their values.
*/
-void git_all_attrs(const struct index_state *istate,
+void git_all_attrs(struct index_state *istate,
const char *path, struct attr_check *check);
enum git_attr_direction {
diff --git a/bisect.c b/bisect.c
index ae48d19acf..af2863d044 100644
--- a/bisect.c
+++ b/bisect.c
@@ -423,7 +423,7 @@ void find_bisection(struct commit_list **commit_list, int *reaches,
show_list("bisection 2 sorted", 0, nr, list);
*all = nr;
- weights = xcalloc(on_list, sizeof(*weights));
+ CALLOC_ARRAY(weights, on_list);
/* Do the real work of finding bisection commit. */
best = do_find_bisection(list, nr, weights, bisect_flags);
diff --git a/blame.c b/blame.c
index a5044fcfaa..206c295660 100644
--- a/blame.c
+++ b/blame.c
@@ -242,7 +242,7 @@ static struct commit *fake_working_tree_commit(struct repository *r,
switch (st.st_mode & S_IFMT) {
case S_IFREG:
if (opt->flags.allow_textconv &&
- textconv_object(r, read_from, mode, &null_oid, 0, &buf_ptr, &buf_len))
+ textconv_object(r, read_from, mode, null_oid(), 0, &buf_ptr, &buf_len))
strbuf_attach(&buf, buf_ptr, buf_len, buf_len + 1);
else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size)
die_errno("cannot open or read '%s'", read_from);
@@ -951,13 +951,13 @@ static int *fuzzy_find_matching_lines(struct blame_origin *parent,
max_search_distance_b = ((2 * max_search_distance_a + 1) * length_b
- 1) / length_a;
- result = xcalloc(sizeof(int), length_b);
- second_best_result = xcalloc(sizeof(int), length_b);
- certainties = xcalloc(sizeof(int), length_b);
+ CALLOC_ARRAY(result, length_b);
+ CALLOC_ARRAY(second_best_result, length_b);
+ CALLOC_ARRAY(certainties, length_b);
/* See get_similarity() for details of similarities. */
similarity_count = length_b * (max_search_distance_a * 2 + 1);
- similarities = xcalloc(sizeof(int), similarity_count);
+ CALLOC_ARRAY(similarities, similarity_count);
for (i = 0; i < length_b; ++i) {
result[i] = -1;
@@ -995,7 +995,7 @@ static void fill_origin_fingerprints(struct blame_origin *o)
return;
o->num_lines = find_line_starts(&line_starts, o->file.ptr,
o->file.size);
- o->fingerprints = xcalloc(sizeof(struct fingerprint), o->num_lines);
+ CALLOC_ARRAY(o->fingerprints, o->num_lines);
get_line_fingerprints(o->fingerprints, o->file.ptr, line_starts,
0, o->num_lines);
free(line_starts);
@@ -1853,8 +1853,7 @@ static void blame_chunk(struct blame_entry ***dstq, struct blame_entry ***srcq,
diffp = NULL;
if (ignore_diffs && same - tlno > 0) {
- line_blames = xcalloc(sizeof(struct blame_line_tracker),
- same - tlno);
+ CALLOC_ARRAY(line_blames, same - tlno);
guess_line_blames(parent, target, tlno, offset, same,
parent_len, line_blames);
}
@@ -2216,7 +2215,7 @@ static struct blame_list *setup_blame_list(struct blame_entry *unblamed,
for (e = unblamed, num_ents = 0; e; e = e->next)
num_ents++;
if (num_ents) {
- blame_list = xcalloc(num_ents, sizeof(struct blame_list));
+ CALLOC_ARRAY(blame_list, num_ents);
for (e = unblamed, i = 0; e; e = e->next)
blame_list[i++].ent = e;
}
@@ -2428,7 +2427,7 @@ static void pass_blame(struct blame_scoreboard *sb, struct blame_origin *origin,
else if (num_sg < ARRAY_SIZE(sg_buf))
memset(sg_buf, 0, sizeof(sg_buf));
else
- sg_origin = xcalloc(num_sg, sizeof(*sg_origin));
+ CALLOC_ARRAY(sg_origin, num_sg);
/*
* The first pass looks for unrenamed path to optimize for
diff --git a/block-sha1/sha1.c b/block-sha1/sha1.c
index 8681031402..1bb6e7c069 100644
--- a/block-sha1/sha1.c
+++ b/block-sha1/sha1.c
@@ -70,7 +70,7 @@
* the input data, the next mix it from the 512-bit array.
*/
#define SHA_SRC(t) get_be32((unsigned char *) block + (t)*4)
-#define SHA_MIX(t) SHA_ROL(W((t)+13) ^ W((t)+8) ^ W((t)+2) ^ W(t), 1);
+#define SHA_MIX(t) SHA_ROL(W((t)+13) ^ W((t)+8) ^ W((t)+2) ^ W(t), 1)
#define SHA_ROUND(t, input, fn, constant, A, B, C, D, E) do { \
unsigned int TEMP = input(t); setW(t, TEMP); \
diff --git a/bloom.c b/bloom.c
index b176f28f53..5e297038bb 100644
--- a/bloom.c
+++ b/bloom.c
@@ -277,12 +277,13 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r,
*computed |= BLOOM_TRUNC_EMPTY;
filter->len = 1;
}
- filter->data = xcalloc(filter->len, sizeof(unsigned char));
+ CALLOC_ARRAY(filter->data, filter->len);
hashmap_for_each_entry(&pathmap, &iter, e, entry) {
struct bloom_key key;
fill_bloom_key(e->path, strlen(e->path), &key, settings);
add_key_to_filter(&key, filter, settings);
+ clear_bloom_key(&key);
}
cleanup:
diff --git a/branch.c b/branch.c
index 9c9dae1eae..7a88a4861e 100644
--- a/branch.c
+++ b/branch.c
@@ -294,7 +294,7 @@ void create_branch(struct repository *r,
if (explicit_tracking)
die(_(upstream_not_branch), start_name);
else
- real_ref = NULL;
+ FREE_AND_NULL(real_ref);
}
break;
default:
@@ -322,7 +322,7 @@ void create_branch(struct repository *r,
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf,
- &oid, forcing ? NULL : &null_oid,
+ &oid, forcing ? NULL : null_oid(),
0, msg, &err) ||
ref_transaction_commit(transaction, &err))
die("%s", err.buf);
@@ -344,6 +344,7 @@ void remove_merge_branch_state(struct repository *r)
unlink(git_path_merge_rr(r));
unlink(git_path_merge_msg(r));
unlink(git_path_merge_mode(r));
+ unlink(git_path_auto_merge(r));
save_autostash(git_path_merge_autostash(r));
}
diff --git a/builtin.h b/builtin.h
index b6ce981b73..16ecd5586f 100644
--- a/builtin.h
+++ b/builtin.h
@@ -123,6 +123,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix);
int cmd_bundle(int argc, const char **argv, const char *prefix);
int cmd_cat_file(int argc, const char **argv, const char *prefix);
int cmd_checkout(int argc, const char **argv, const char *prefix);
+int cmd_checkout__worker(int argc, const char **argv, const char *prefix);
int cmd_checkout_index(int argc, const char **argv, const char *prefix);
int cmd_check_attr(int argc, const char **argv, const char *prefix);
int cmd_check_ignore(int argc, const char **argv, const char *prefix);
diff --git a/builtin/add.c b/builtin/add.c
index ea762a41e3..b773b5a499 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -46,6 +46,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))
+ continue;
+
if (pathspec && !ce_path_match(&the_index, ce, pathspec, NULL))
continue;
@@ -141,9 +144,13 @@ 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))
+ continue;
if (ce_stage(ce))
continue; /* do not touch unmerged paths */
if (!S_ISREG(ce->ce_mode) && !S_ISLNK(ce->ce_mode))
@@ -172,24 +179,44 @@ static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec,
*dst++ = entry;
}
dir->nr = dst - dir->entries;
- add_pathspec_matches_against_index(pathspec, &the_index, seen);
+ add_pathspec_matches_against_index(pathspec, &the_index, seen,
+ PS_IGNORE_SKIP_WORKTREE);
return seen;
}
-static void refresh(int verbose, const struct pathspec *pathspec)
+static int refresh(int verbose, const struct pathspec *pathspec)
{
char *seen;
- int i;
+ int i, ret = 0;
+ char *skip_worktree_seen = NULL;
+ struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP;
+ int flags = REFRESH_IGNORE_SKIP_WORKTREE |
+ (verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET);
seen = xcalloc(pathspec->nr, 1);
- refresh_index(&the_index, verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET,
- pathspec, seen, _("Unstaged changes after refreshing the index:"));
+ refresh_index(&the_index, flags, pathspec, seen,
+ _("Unstaged changes after refreshing the index:"));
for (i = 0; i < pathspec->nr; i++) {
- if (!seen[i])
- die(_("pathspec '%s' did not match any files"),
- pathspec->items[i].match);
+ if (!seen[i]) {
+ if (matches_skip_worktree(pathspec, i, &skip_worktree_seen)) {
+ string_list_append(&only_match_skip_worktree,
+ pathspec->items[i].original);
+ } else {
+ die(_("pathspec '%s' did not match any files"),
+ pathspec->items[i].original);
+ }
+ }
+ }
+
+ if (only_match_skip_worktree.nr) {
+ advise_on_updating_sparse_paths(&only_match_skip_worktree);
+ ret = 1;
}
+
free(seen);
+ free(skip_worktree_seen);
+ string_list_clear(&only_match_skip_worktree, 0);
+ return ret;
}
int run_add_interactive(const char *revision, const char *patch_mode,
@@ -457,6 +484,8 @@ int cmd_add(int argc, const char **argv, const char *prefix)
if (patch_interactive)
add_interactive = 1;
if (add_interactive) {
+ if (show_only)
+ die(_("--dry-run is incompatible with --interactive/--patch"));
if (pathspec_from_file)
die(_("--pathspec-from-file is incompatible with --interactive/--patch"));
exit(interactive_add(argv + 1, prefix, patch_interactive));
@@ -565,15 +594,18 @@ int cmd_add(int argc, const char **argv, const char *prefix)
}
if (refresh_only) {
- refresh(verbose, &pathspec);
+ exit_status |= refresh(verbose, &pathspec);
goto finish;
}
if (pathspec.nr) {
int i;
+ char *skip_worktree_seen = NULL;
+ struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP;
if (!seen)
- seen = find_pathspecs_matching_against_index(&pathspec, &the_index);
+ seen = find_pathspecs_matching_against_index(&pathspec,
+ &the_index, PS_IGNORE_SKIP_WORKTREE);
/*
* file_exists() assumes exact match
@@ -587,12 +619,24 @@ int cmd_add(int argc, const char **argv, const char *prefix)
for (i = 0; i < pathspec.nr; i++) {
const char *path = pathspec.items[i].match;
+
if (pathspec.items[i].magic & PATHSPEC_EXCLUDE)
continue;
- if (!seen[i] && path[0] &&
- ((pathspec.items[i].magic &
- (PATHSPEC_GLOB | PATHSPEC_ICASE)) ||
- !file_exists(path))) {
+ if (seen[i])
+ continue;
+
+ if (matches_skip_worktree(&pathspec, i, &skip_worktree_seen)) {
+ string_list_append(&only_match_skip_worktree,
+ pathspec.items[i].original);
+ continue;
+ }
+
+ /* Don't complain at 'git add .' on empty repo */
+ if (!path[0])
+ continue;
+
+ if ((pathspec.items[i].magic & (PATHSPEC_GLOB | PATHSPEC_ICASE)) ||
+ !file_exists(path)) {
if (ignore_missing) {
int dtype = DT_UNKNOWN;
if (is_excluded(&dir, &the_index, path, &dtype))
@@ -603,7 +647,16 @@ int cmd_add(int argc, const char **argv, const char *prefix)
pathspec.items[i].original);
}
}
+
+
+ if (only_match_skip_worktree.nr) {
+ advise_on_updating_sparse_paths(&only_match_skip_worktree);
+ exit_status = 1;
+ }
+
free(seen);
+ free(skip_worktree_seen);
+ string_list_clear(&only_match_skip_worktree, 0);
}
plug_bulk_checkin();
diff --git a/builtin/am.c b/builtin/am.c
index 8355e3566f..0b2d886c81 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -116,6 +116,7 @@ struct am_state {
int keep; /* enum keep_type */
int message_id;
int scissors; /* enum scissors_type */
+ int quoted_cr; /* enum quoted_cr_action */
struct strvec git_apply_opts;
const char *resolvemsg;
int committer_date_is_author_date;
@@ -145,6 +146,7 @@ static void am_state_init(struct am_state *state)
git_config_get_bool("am.messageid", &state->message_id);
state->scissors = SCISSORS_UNSET;
+ state->quoted_cr = quoted_cr_unset;
strvec_init(&state->git_apply_opts);
@@ -165,6 +167,16 @@ static void am_state_release(struct am_state *state)
strvec_clear(&state->git_apply_opts);
}
+static int am_option_parse_quoted_cr(const struct option *opt,
+ const char *arg, int unset)
+{
+ BUG_ON_OPT_NEG(unset);
+
+ if (mailinfo_parse_quoted_cr_action(arg, opt->value) != 0)
+ return error(_("bad action '%s' for '%s'"), arg, "--quoted-cr");
+ return 0;
+}
+
/**
* Returns path relative to the am_state directory.
*/
@@ -397,6 +409,12 @@ static void am_load(struct am_state *state)
else
state->scissors = SCISSORS_UNSET;
+ read_state_file(&sb, state, "quoted-cr", 1);
+ if (!*sb.buf)
+ state->quoted_cr = quoted_cr_unset;
+ else if (mailinfo_parse_quoted_cr_action(sb.buf, &state->quoted_cr) != 0)
+ die(_("could not parse %s"), am_path(state, "quoted-cr"));
+
read_state_file(&sb, state, "apply-opt", 1);
strvec_clear(&state->git_apply_opts);
if (sq_dequote_to_strvec(sb.buf, &state->git_apply_opts) < 0)
@@ -1002,6 +1020,24 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
}
write_state_text(state, "scissors", str);
+ switch (state->quoted_cr) {
+ case quoted_cr_unset:
+ str = "";
+ break;
+ case quoted_cr_nowarn:
+ str = "nowarn";
+ break;
+ case quoted_cr_warn:
+ str = "warn";
+ break;
+ case quoted_cr_strip:
+ str = "strip";
+ break;
+ default:
+ BUG("invalid value for state->quoted_cr");
+ }
+ write_state_text(state, "quoted-cr", str);
+
sq_quote_argv(&sb, state->git_apply_opts.v);
write_state_text(state, "apply-opt", sb.buf);
@@ -1162,6 +1198,18 @@ static int parse_mail(struct am_state *state, const char *mail)
BUG("invalid value for state->scissors");
}
+ switch (state->quoted_cr) {
+ case quoted_cr_unset:
+ break;
+ case quoted_cr_nowarn:
+ case quoted_cr_warn:
+ case quoted_cr_strip:
+ mi.quoted_cr = state->quoted_cr;
+ break;
+ default:
+ BUG("invalid value for state->quoted_cr");
+ }
+
mi.input = xfopen(mail, "r");
mi.output = xfopen(am_path(state, "info"), "w");
if (mailinfo(&mi, am_path(state, "msg"), am_path(state, "patch")))
@@ -2242,6 +2290,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
0, PARSE_OPT_NONEG),
OPT_BOOL('c', "scissors", &state.scissors,
N_("strip everything before a scissors line")),
+ OPT_CALLBACK_F(0, "quoted-cr", &state.quoted_cr, N_("action"),
+ N_("pass it through git-mailinfo"),
+ PARSE_OPT_NONEG, am_option_parse_quoted_cr),
OPT_PASSTHRU_ARGV(0, "whitespace", &state.git_apply_opts, N_("action"),
N_("pass it through git-apply"),
0),
diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c
index d69e13335d..9d9540a0ab 100644
--- a/builtin/bisect--helper.c
+++ b/builtin/bisect--helper.c
@@ -874,12 +874,19 @@ static enum bisect_error bisect_state(struct bisect_terms *terms, const char **a
*/
for (; argc; argc--, argv++) {
+ struct commit *commit;
+
if (get_oid(*argv, &oid)){
error(_("Bad rev input: %s"), *argv);
oid_array_clear(&revs);
return BISECT_FAILED;
}
- oid_array_append(&revs, &oid);
+
+ commit = lookup_commit_reference(the_repository, &oid);
+ if (!commit)
+ die(_("Bad rev input (not a commit): %s"), *argv);
+
+ oid_array_append(&revs, &commit->object.oid);
}
if (strbuf_read_file(&buf, git_path_bisect_expected_rev(), 0) < the_hash_algo->hexsz ||
@@ -1119,6 +1126,7 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
break;
case BISECT_SKIP:
set_terms(&terms, "bad", "good");
+ get_terms(&terms);
res = bisect_skip(&terms, argv, argc);
break;
default:
diff --git a/builtin/branch.c b/builtin/branch.c
index bcc00bcf18..b23b1d1752 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -411,6 +411,8 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin
{
int i;
struct ref_array array;
+ struct strbuf out = STRBUF_INIT;
+ struct strbuf err = STRBUF_INIT;
int maxwidth = 0;
const char *remote_prefix = "";
char *to_free = NULL;
@@ -440,8 +442,8 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin
ref_array_sort(sorting, &array);
for (i = 0; i < array.nr; i++) {
- struct strbuf out = STRBUF_INIT;
- struct strbuf err = STRBUF_INIT;
+ strbuf_reset(&err);
+ strbuf_reset(&out);
if (format_ref_array_item(array.items[i], format, &out, &err))
die("%s", err.buf);
if (column_active(colopts)) {
@@ -452,10 +454,10 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin
fwrite(out.buf, 1, out.len, stdout);
putchar('\n');
}
- strbuf_release(&err);
- strbuf_release(&out);
}
+ strbuf_release(&err);
+ strbuf_release(&out);
ref_array_clear(&array);
free(to_free);
}
diff --git a/builtin/bugreport.c b/builtin/bugreport.c
index ad3cc9c02f..9915a5841d 100644
--- a/builtin/bugreport.c
+++ b/builtin/bugreport.c
@@ -129,6 +129,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix)
char *option_output = NULL;
char *option_suffix = "%Y-%m-%d-%H%M";
const char *user_relative_path = NULL;
+ char *prefixed_filename;
const struct option bugreport_options[] = {
OPT_STRING('o', "output-directory", &option_output, N_("path"),
@@ -142,9 +143,9 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix)
bugreport_usage, 0);
/* Prepare the path to put the result */
- strbuf_addstr(&report_path,
- prefix_filename(prefix,
- option_output ? option_output : ""));
+ prefixed_filename = prefix_filename(prefix,
+ option_output ? option_output : "");
+ strbuf_addstr(&report_path, prefixed_filename);
strbuf_complete(&report_path, '/');
strbuf_addstr(&report_path, "git-bugreport-");
@@ -189,6 +190,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix)
fprintf(stderr, _("Created new report at '%s'.\n"),
user_relative_path);
+ free(prefixed_filename);
UNLEAK(buffer);
UNLEAK(report_path);
return !!launch_editor(report_path.buf, NULL, NULL);
diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c
index 3c652748d5..81234552b7 100644
--- a/builtin/check-ignore.c
+++ b/builtin/check-ignore.c
@@ -100,7 +100,8 @@ static int check_ignore(struct dir_struct *dir,
* should not be ignored, in order to be consistent with
* 'git status', 'git add' etc.
*/
- seen = find_pathspecs_matching_against_index(&pathspec, &the_index);
+ seen = find_pathspecs_matching_against_index(&pathspec, &the_index,
+ PS_HEED_SKIP_WORKTREE);
for (i = 0; i < pathspec.nr; i++) {
full_path = pathspec.items[i].match;
pattern = NULL;
@@ -118,6 +119,7 @@ static int check_ignore(struct dir_struct *dir,
num_ignored++;
}
free(seen);
+ clear_pathspec(&pathspec);
return num_ignored;
}
diff --git a/builtin/checkout--worker.c b/builtin/checkout--worker.c
new file mode 100644
index 0000000000..289a9b8f89
--- /dev/null
+++ b/builtin/checkout--worker.c
@@ -0,0 +1,145 @@
+#include "builtin.h"
+#include "config.h"
+#include "entry.h"
+#include "parallel-checkout.h"
+#include "parse-options.h"
+#include "pkt-line.h"
+
+static void packet_to_pc_item(const char *buffer, int len,
+ struct parallel_checkout_item *pc_item)
+{
+ const struct pc_item_fixed_portion *fixed_portion;
+ const char *variant;
+ char *encoding;
+
+ if (len < sizeof(struct pc_item_fixed_portion))
+ BUG("checkout worker received too short item (got %dB, exp %dB)",
+ len, (int)sizeof(struct pc_item_fixed_portion));
+
+ fixed_portion = (struct pc_item_fixed_portion *)buffer;
+
+ if (len - sizeof(struct pc_item_fixed_portion) !=
+ fixed_portion->name_len + fixed_portion->working_tree_encoding_len)
+ BUG("checkout worker received corrupted item");
+
+ variant = buffer + sizeof(struct pc_item_fixed_portion);
+
+ /*
+ * Note: the main process uses zero length to communicate that the
+ * encoding is NULL. There is no use case that requires sending an
+ * actual empty string, since convert_attrs() never sets
+ * ca.working_tree_enconding to "".
+ */
+ if (fixed_portion->working_tree_encoding_len) {
+ encoding = xmemdupz(variant,
+ fixed_portion->working_tree_encoding_len);
+ variant += fixed_portion->working_tree_encoding_len;
+ } else {
+ encoding = NULL;
+ }
+
+ memset(pc_item, 0, sizeof(*pc_item));
+ pc_item->ce = make_empty_transient_cache_entry(fixed_portion->name_len, NULL);
+ pc_item->ce->ce_namelen = fixed_portion->name_len;
+ pc_item->ce->ce_mode = fixed_portion->ce_mode;
+ memcpy(pc_item->ce->name, variant, pc_item->ce->ce_namelen);
+ oidcpy(&pc_item->ce->oid, &fixed_portion->oid);
+
+ pc_item->id = fixed_portion->id;
+ pc_item->ca.crlf_action = fixed_portion->crlf_action;
+ pc_item->ca.ident = fixed_portion->ident;
+ pc_item->ca.working_tree_encoding = encoding;
+}
+
+static void report_result(struct parallel_checkout_item *pc_item)
+{
+ struct pc_item_result res;
+ size_t size;
+
+ res.id = pc_item->id;
+ res.status = pc_item->status;
+
+ if (pc_item->status == PC_ITEM_WRITTEN) {
+ res.st = pc_item->st;
+ size = sizeof(res);
+ } else {
+ size = PC_ITEM_RESULT_BASE_SIZE;
+ }
+
+ packet_write(1, (const char *)&res, size);
+}
+
+/* Free the worker-side malloced data, but not pc_item itself. */
+static void release_pc_item_data(struct parallel_checkout_item *pc_item)
+{
+ free((char *)pc_item->ca.working_tree_encoding);
+ discard_cache_entry(pc_item->ce);
+}
+
+static void worker_loop(struct checkout *state)
+{
+ struct parallel_checkout_item *items = NULL;
+ size_t i, nr = 0, alloc = 0;
+
+ while (1) {
+ int len = packet_read(0, NULL, NULL, packet_buffer,
+ sizeof(packet_buffer), 0);
+
+ if (len < 0)
+ BUG("packet_read() returned negative value");
+ else if (!len)
+ break;
+
+ ALLOC_GROW(items, nr + 1, alloc);
+ packet_to_pc_item(packet_buffer, len, &items[nr++]);
+ }
+
+ for (i = 0; i < nr; i++) {
+ struct parallel_checkout_item *pc_item = &items[i];
+ write_pc_item(pc_item, state);
+ report_result(pc_item);
+ release_pc_item_data(pc_item);
+ }
+
+ packet_flush(1);
+
+ free(items);
+}
+
+static const char * const checkout_worker_usage[] = {
+ N_("git checkout--worker [<options>]"),
+ NULL
+};
+
+int cmd_checkout__worker(int argc, const char **argv, const char *prefix)
+{
+ struct checkout state = CHECKOUT_INIT;
+ struct option checkout_worker_options[] = {
+ OPT_STRING(0, "prefix", &state.base_dir, N_("string"),
+ N_("when creating files, prepend <string>")),
+ OPT_END()
+ };
+
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(checkout_worker_usage,
+ checkout_worker_options);
+
+ git_config(git_default_config, NULL);
+ argc = parse_options(argc, argv, prefix, checkout_worker_options,
+ checkout_worker_usage, 0);
+ if (argc > 0)
+ usage_with_options(checkout_worker_usage, checkout_worker_options);
+
+ if (state.base_dir)
+ state.base_dir_len = strlen(state.base_dir);
+
+ /*
+ * Setting this on a worker won't actually update the index. We just
+ * need to tell the checkout machinery to lstat() the written entries,
+ * so that we can send this data back to the main process.
+ */
+ state.refresh_cache = 1;
+
+ worker_loop(&state);
+ return 0;
+}
diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c
index 023e49e271..e21620d964 100644
--- a/builtin/checkout-index.c
+++ b/builtin/checkout-index.c
@@ -11,6 +11,8 @@
#include "quote.h"
#include "cache-tree.h"
#include "parse-options.h"
+#include "entry.h"
+#include "parallel-checkout.h"
#define CHECKOUT_ALL 4
static int nul_term_line;
@@ -114,11 +116,13 @@ static int checkout_file(const char *name, const char *prefix)
return -1;
}
-static void checkout_all(const char *prefix, int prefix_length)
+static int checkout_all(const char *prefix, int prefix_length)
{
int i, errs = 0;
struct cache_entry *last_ce = NULL;
+ /* 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_stage(ce) != checkout_stage
@@ -141,11 +145,7 @@ static void checkout_all(const char *prefix, int prefix_length)
}
if (last_ce && to_tempfile)
write_tempfile_record(last_ce->name, prefix);
- if (errs)
- /* we have already done our error reporting.
- * exit with the same code as die().
- */
- exit(128);
+ return !!errs;
}
static const char * const builtin_checkout_index_usage[] = {
@@ -181,6 +181,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
int force = 0, quiet = 0, not_new = 0;
int index_opt = 0;
int err = 0;
+ int pc_workers, pc_threshold;
struct option builtin_checkout_index_options[] = {
OPT_BOOL('a', "all", &all,
N_("check out all files in the index")),
@@ -235,6 +236,10 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
}
+ get_parallel_checkout_configs(&pc_workers, &pc_threshold);
+ if (pc_workers > 1)
+ init_parallel_checkout();
+
/* Check out named files first */
for (i = 0; i < argc; i++) {
const char *arg = argv[i];
@@ -274,12 +279,16 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
strbuf_release(&buf);
}
+ if (all)
+ err |= checkout_all(prefix, prefix_length);
+
+ if (pc_workers > 1)
+ err |= run_parallel_checkout(&state, pc_workers, pc_threshold,
+ NULL, NULL);
+
if (err)
return 1;
- if (all)
- checkout_all(prefix, prefix_length);
-
if (is_lock_file_locked(&lock_file) &&
write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
die("Unable to write new index file");
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 2d6550bc3c..f4cd7747d3 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -26,6 +26,8 @@
#include "unpack-trees.h"
#include "wt-status.h"
#include "xdiff-interface.h"
+#include "entry.h"
+#include "parallel-checkout.h"
static const char * const checkout_usage[] = {
N_("git checkout [<options>] <branch>"),
@@ -105,8 +107,8 @@ static int post_checkout_hook(struct commit *old_commit, struct commit *new_comm
int changed)
{
return run_hook_le(NULL, "post-checkout",
- oid_to_hex(old_commit ? &old_commit->object.oid : &null_oid),
- oid_to_hex(new_commit ? &new_commit->object.oid : &null_oid),
+ oid_to_hex(old_commit ? &old_commit->object.oid : null_oid()),
+ oid_to_hex(new_commit ? &new_commit->object.oid : null_oid()),
changed ? "1" : "0", NULL);
/* "new_commit" can be NULL when checking out from the index before
a commit exists. */
@@ -114,7 +116,7 @@ static int post_checkout_hook(struct commit *old_commit, struct commit *new_comm
}
static int update_some(const struct object_id *oid, struct strbuf *base,
- const char *pathname, unsigned mode, int stage, void *context)
+ const char *pathname, unsigned mode, void *context)
{
int len;
struct cache_entry *ce;
@@ -155,8 +157,8 @@ static int update_some(const struct object_id *oid, struct strbuf *base,
static int read_tree_some(struct tree *tree, const struct pathspec *pathspec)
{
- read_tree_recursive(the_repository, tree, "", 0, 0,
- pathspec, update_some, NULL);
+ read_tree(the_repository, tree,
+ pathspec, update_some, NULL);
/* update the index with the given tree's info
* for all args, expanding wildcards, and exit
@@ -229,7 +231,8 @@ static int checkout_stage(int stage, const struct cache_entry *ce, int pos,
return error(_("path '%s' does not have their version"), ce->name);
}
-static int checkout_merged(int pos, const struct checkout *state, int *nr_checkouts)
+static int checkout_merged(int pos, const struct checkout *state,
+ int *nr_checkouts, struct mem_pool *ce_mem_pool)
{
struct cache_entry *ce = active_cache[pos];
const char *path = ce->name;
@@ -290,11 +293,10 @@ static int checkout_merged(int pos, const struct checkout *state, int *nr_checko
if (write_object_file(result_buf.ptr, result_buf.size, blob_type, &oid))
die(_("Unable to add merge result for '%s'"), path);
free(result_buf.ptr);
- ce = make_transient_cache_entry(mode, &oid, path, 2);
+ ce = make_transient_cache_entry(mode, &oid, path, 2, ce_mem_pool);
if (!ce)
die(_("make_cache_entry failed for path '%s'"), path);
status = checkout_entry(ce, state, NULL, nr_checkouts);
- discard_cache_entry(ce);
return status;
}
@@ -322,7 +324,7 @@ static void mark_ce_for_checkout_overlay(struct cache_entry *ce,
* If it comes from the tree-ish, we already know it
* matches the pathspec and could just stamp
* CE_MATCHED to it from update_some(). But we still
- * need ps_matched and read_tree_recursive (and
+ * need ps_matched and read_tree (and
* eventually tree_entry_interesting) cannot fill
* ps_matched yet. Once it can, we can avoid calling
* match_pathspec() for _all_ entries when
@@ -358,16 +360,27 @@ static int checkout_worktree(const struct checkout_opts *opts,
int nr_checkouts = 0, nr_unmerged = 0;
int errs = 0;
int pos;
+ int pc_workers, pc_threshold;
+ struct mem_pool ce_mem_pool;
state.force = 1;
state.refresh_cache = 1;
state.istate = &the_index;
+ mem_pool_init(&ce_mem_pool, 0);
+ get_parallel_checkout_configs(&pc_workers, &pc_threshold);
init_checkout_metadata(&state.meta, info->refname,
info->commit ? &info->commit->object.oid : &info->oid,
NULL);
enable_delayed_checkout(&state);
+
+ if (pc_workers > 1)
+ init_parallel_checkout();
+
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(&the_index);
+
for (pos = 0; pos < active_nr; pos++) {
struct cache_entry *ce = active_cache[pos];
if (ce->ce_flags & CE_MATCHED) {
@@ -383,10 +396,15 @@ static int checkout_worktree(const struct checkout_opts *opts,
&nr_checkouts, opts->overlay_mode);
else if (opts->merge)
errs |= checkout_merged(pos, &state,
- &nr_unmerged);
+ &nr_unmerged,
+ &ce_mem_pool);
pos = skip_same_name(ce, pos) - 1;
}
}
+ if (pc_workers > 1)
+ errs |= run_parallel_checkout(&state, pc_workers, pc_threshold,
+ NULL, NULL);
+ 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);
@@ -512,6 +530,8 @@ static int checkout_paths(const struct checkout_opts *opts,
* Make sure all pathspecs participated in locating the paths
* to be checked out.
*/
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(&the_index);
for (pos = 0; pos < active_nr; pos++)
if (opts->overlay_mode)
mark_ce_for_checkout_overlay(active_cache[pos],
@@ -601,6 +621,7 @@ static void show_local_changes(struct object *head,
diff_setup_done(&rev.diffopt);
add_pending_object(&rev, head, NULL);
run_diff_index(&rev, 0);
+ object_array_clear(&rev.pending);
}
static void describe_detached_head(const char *msg, struct commit *commit)
@@ -637,7 +658,7 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,
opts.src_index = &the_index;
opts.dst_index = &the_index;
init_checkout_metadata(&opts.meta, info->refname,
- info->commit ? &info->commit->object.oid : &null_oid,
+ info->commit ? &info->commit->object.oid : null_oid(),
NULL);
parse_tree(tree);
init_tree_desc(&tree_desc, tree->buffer, tree->size);
diff --git a/builtin/clean.c b/builtin/clean.c
index 687ab473c2..4944cf440b 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -189,10 +189,8 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
strbuf_complete(path, '/');
len = path->len;
- while ((e = readdir(dir)) != NULL) {
+ while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL) {
struct stat st;
- if (is_dot_or_dotdot(e->d_name))
- continue;
strbuf_setlen(path, len);
strbuf_addstr(path, e->d_name);
@@ -623,7 +621,7 @@ static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)
nr += chosen[i];
}
- result = xcalloc(st_add(nr, 1), sizeof(int));
+ CALLOC_ARRAY(result, st_add(nr, 1));
for (i = 0; i < stuff->nr && j < nr; i++) {
if (chosen[i])
result[j++] = i;
@@ -1003,7 +1001,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
for (i = 0; i < dir.nr; i++) {
struct dir_entry *ent = dir.entries[i];
- int matches = 0;
struct stat st;
const char *rel;
@@ -1013,8 +1010,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
if (lstat(ent->name, &st))
die_errno("Cannot lstat '%s'", ent->name);
- if (S_ISDIR(st.st_mode) && !remove_directories &&
- matches != MATCHED_EXACTLY)
+ if (S_ISDIR(st.st_mode) && !remove_directories)
continue;
rel = relative_path(ent->name, prefix, &buf);
diff --git a/builtin/clone.c b/builtin/clone.c
index 51e844a2de..eeb74c0217 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -50,6 +50,8 @@ static int option_no_checkout, option_bare, option_mirror, option_single_branch
static int option_local = -1, option_no_hardlinks, option_shared;
static int option_no_tags;
static int option_shallow_submodules;
+static int option_reject_shallow = -1; /* unspecified */
+static int config_reject_shallow = -1; /* unspecified */
static int deepen;
static char *option_template, *option_depth, *option_since;
static char *option_origin = NULL;
@@ -90,6 +92,8 @@ static struct option builtin_clone_options[] = {
OPT__VERBOSITY(&option_verbosity),
OPT_BOOL(0, "progress", &option_progress,
N_("force progress reporting")),
+ OPT_BOOL(0, "reject-shallow", &option_reject_shallow,
+ N_("don't clone shallow repository")),
OPT_BOOL('n', "no-checkout", &option_no_checkout,
N_("don't create a checkout")),
OPT_BOOL(0, "bare", &option_bare, N_("create a bare repository")),
@@ -816,7 +820,7 @@ static int checkout(int submodule_progress)
if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
die(_("unable to write new index file"));
- err |= run_hook_le(NULL, "post-checkout", oid_to_hex(&null_oid),
+ err |= run_hook_le(NULL, "post-checkout", oid_to_hex(null_oid()),
oid_to_hex(&oid), "1", NULL);
if (!err && (option_recurse_submodules.nr > 0)) {
@@ -858,6 +862,9 @@ static int git_clone_config(const char *k, const char *v, void *cb)
free(remote_name);
remote_name = xstrdup(v);
}
+ if (!strcmp(k, "clone.rejectshallow"))
+ config_reject_shallow = git_config_bool(k, v);
+
return git_default_config(k, v, cb);
}
@@ -963,11 +970,12 @@ static int path_exists(const char *path)
int cmd_clone(int argc, const char **argv, const char *prefix)
{
int is_bundle = 0, is_local;
+ int reject_shallow = 0;
const char *repo_name, *repo, *work_tree, *git_dir;
- char *path, *dir, *display_repo = NULL;
+ char *path = NULL, *dir, *display_repo = NULL;
int dest_exists, real_dest_exists = 0;
const struct ref *refs, *remote_head;
- const struct ref *remote_head_points_at;
+ struct ref *remote_head_points_at = NULL;
const struct ref *our_head_points_at;
struct ref *mapped_refs;
const struct ref *ref;
@@ -1017,9 +1025,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
repo_name = argv[0];
path = get_repo_path(repo_name, &is_bundle);
- if (path)
+ if (path) {
+ FREE_AND_NULL(path);
repo = absolute_pathdup(repo_name);
- else if (strchr(repo_name, ':')) {
+ } else if (strchr(repo_name, ':')) {
repo = repo_name;
display_repo = transport_anonymize_url(repo);
} else
@@ -1157,6 +1166,15 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
git_config(git_clone_config, NULL);
/*
+ * If option_reject_shallow is specified from CLI option,
+ * ignore config_reject_shallow from git_clone_config.
+ */
+ if (config_reject_shallow != -1)
+ reject_shallow = config_reject_shallow;
+ if (option_reject_shallow != -1)
+ reject_shallow = option_reject_shallow;
+
+ /*
* apply the remote name provided by --origin only after this second
* call to git_config, to ensure it overrides all config-based values.
*/
@@ -1216,6 +1234,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (filter_options.choice)
warning(_("--filter is ignored in local clones; use file:// instead."));
if (!access(mkpath("%s/shallow", path), F_OK)) {
+ if (reject_shallow)
+ die(_("source repository is shallow, reject to clone."));
if (option_local > 0)
warning(_("source repository is shallow, ignoring --local"));
is_local = 0;
@@ -1227,6 +1247,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
transport_set_option(transport, TRANS_OPT_KEEP, "yes");
+ if (reject_shallow)
+ transport_set_option(transport, TRANS_OPT_REJECT_SHALLOW, "1");
if (option_depth)
transport_set_option(transport, TRANS_OPT_DEPTH,
option_depth);
@@ -1393,6 +1415,11 @@ cleanup:
strbuf_release(&reflog_msg);
strbuf_release(&branch_top);
strbuf_release(&key);
+ free_refs(mapped_refs);
+ free_refs(remote_head_points_at);
+ free(dir);
+ free(path);
+ UNLEAK(repo);
junk_mode = JUNK_LEAVE_ALL;
strvec_clear(&transport_ls_refs_options.ref_prefixes);
diff --git a/builtin/column.c b/builtin/column.c
index e815e148aa..40d4b3bee2 100644
--- a/builtin/column.c
+++ b/builtin/column.c
@@ -27,10 +27,10 @@ int cmd_column(int argc, const char **argv, const char *prefix)
OPT_STRING(0, "command", &real_command, N_("name"), N_("lookup config vars")),
OPT_COLUMN(0, "mode", &colopts, N_("layout to use")),
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_INTEGER(0, "padding", &copts.padding, N_("Padding space between columns")),
+ 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_INTEGER(0, "padding", &copts.padding, N_("padding space between columns")),
OPT_END()
};
diff --git a/builtin/commit.c b/builtin/commit.c
index 739110c5a7..190d215d43 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -105,7 +105,8 @@ static const char *template_file;
*/
static const char *author_message, *author_message_buffer;
static char *edit_message, *use_message;
-static char *fixup_message, *squash_message;
+static char *fixup_message, *fixup_commit, *squash_message;
+static const char *fixup_prefix;
static int all, also, interactive, patch_interactive, only, amend, signoff;
static int edit_flag = -1; /* unspecified */
static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
@@ -113,6 +114,7 @@ static int config_commit_verbose = -1; /* unspecified */
static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
static char *sign_commit, *pathspec_from_file;
+static struct strvec trailer_args = STRVEC_INIT;
/*
* The default commit message cleanup mode will remove the lines
@@ -131,6 +133,14 @@ static struct strbuf message = STRBUF_INIT;
static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
+static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
+{
+ BUG_ON_OPT_NEG(unset);
+
+ strvec_pushl(&trailer_args, "--trailer", arg, NULL);
+ return 0;
+}
+
static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
{
enum wt_status_format *value = (enum wt_status_format *)opt->value;
@@ -251,6 +261,8 @@ static int list_paths(struct string_list *list, const char *with_tree,
free(max_prefix);
}
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(&the_index);
for (i = 0; i < active_nr; i++) {
const struct cache_entry *ce = active_cache[i];
struct string_list_item *item;
@@ -357,7 +369,8 @@ static const char *prepare_index(const char **argv, const char *prefix,
die(_("--pathspec-file-nul requires --pathspec-from-file"));
}
- if (!pathspec.nr && (also || (only && !amend && !allow_empty)))
+ if (!pathspec.nr && (also || (only && !allow_empty &&
+ (!amend || (fixup_message && strcmp(fixup_prefix, "amend"))))))
die(_("No paths with --include/--only does not make sense."));
if (read_cache_preload(&pathspec) < 0)
@@ -681,6 +694,22 @@ static void adjust_comment_line_char(const struct strbuf *sb)
comment_line_char = *p;
}
+static void prepare_amend_commit(struct commit *commit, struct strbuf *sb,
+ struct pretty_print_context *ctx)
+{
+ const char *buffer, *subject, *fmt;
+
+ buffer = get_commit_buffer(commit, NULL);
+ find_commit_subject(buffer, &subject);
+ /*
+ * If we amend the 'amend!' commit then we don't want to
+ * duplicate the subject line.
+ */
+ fmt = starts_with(subject, "amend!") ? "%b" : "%B";
+ format_commit_message(commit, fmt, sb, ctx);
+ unuse_commit_buffer(commit, buffer);
+}
+
static int prepare_to_commit(const char *index_file, const char *prefix,
struct commit *current_head,
struct wt_status *s,
@@ -745,15 +774,33 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
} else if (fixup_message) {
struct pretty_print_context ctx = {0};
struct commit *commit;
- commit = lookup_commit_reference_by_name(fixup_message);
+ char *fmt;
+ commit = lookup_commit_reference_by_name(fixup_commit);
if (!commit)
- die(_("could not lookup commit %s"), fixup_message);
+ die(_("could not lookup commit %s"), fixup_commit);
ctx.output_encoding = get_commit_output_encoding();
- format_commit_message(commit, "fixup! %s\n\n",
- &sb, &ctx);
- if (have_option_m)
- strbuf_addbuf(&sb, &message);
+ fmt = xstrfmt("%s! %%s\n\n", fixup_prefix);
+ format_commit_message(commit, fmt, &sb, &ctx);
+ free(fmt);
hook_arg1 = "message";
+
+ /*
+ * Only `-m` commit message option is checked here, as
+ * it supports `--fixup` to append the commit message.
+ *
+ * The other commit message options `-c`/`-C`/`-F` are
+ * incompatible with all the forms of `--fixup` and
+ * have already errored out while parsing the `git commit`
+ * options.
+ */
+ if (have_option_m && !strcmp(fixup_prefix, "fixup"))
+ strbuf_addbuf(&sb, &message);
+
+ if (!strcmp(fixup_prefix, "amend")) {
+ if (have_option_m)
+ die(_("cannot combine -m with --fixup:%s"), fixup_message);
+ prepare_amend_commit(commit, &sb, &ctx);
+ }
} else if (!stat(git_path_merge_msg(the_repository), &statbuf)) {
size_t merge_msg_start;
@@ -931,6 +978,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
if (get_oid(parent, &oid)) {
int i, ita_nr = 0;
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(&the_index);
for (i = 0; i < active_nr; i++)
if (ce_intent_to_add(active_cache[i]))
ita_nr++;
@@ -958,6 +1007,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
fclose(s->fp);
+ if (trailer_args.nr) {
+ struct child_process run_trailer = CHILD_PROCESS_INIT;
+
+ strvec_pushl(&run_trailer.args, "interpret-trailers",
+ "--in-place", git_path_commit_editmsg(), NULL);
+ strvec_pushv(&run_trailer.args, trailer_args.v);
+ run_trailer.git_cmd = 1;
+ if (run_command(&run_trailer))
+ die(_("unable to pass trailers to --trailers"));
+ strvec_clear(&trailer_args);
+ }
+
/*
* Reject an attempt to record a non-merge empty commit without
* explicit --allow-empty. In the cherry-pick case, it may be
@@ -1152,6 +1213,19 @@ static void finalize_deferred_config(struct wt_status *s)
s->ahead_behind_flags = AHEAD_BEHIND_FULL;
}
+static void check_fixup_reword_options(int argc, const char *argv[]) {
+ if (whence != FROM_COMMIT) {
+ if (whence == FROM_MERGE)
+ die(_("You are in the middle of a merge -- cannot reword."));
+ else if (is_from_cherry_pick(whence))
+ die(_("You are in the middle of a cherry-pick -- cannot reword."));
+ }
+ if (argc)
+ die(_("cannot combine reword option of --fixup with path '%s'"), *argv);
+ if (patch_interactive || interactive || all || also || only)
+ die(_("reword option of --fixup is mutually exclusive with --patch/--interactive/--all/--include/--only"));
+}
+
static int parse_and_validate_options(int argc, const char *argv[],
const struct option *options,
const char * const usage[],
@@ -1170,7 +1244,7 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (force_author && renew_authorship)
die(_("Using both --reset-author and --author does not make sense"));
- if (logfile || have_option_m || use_message || fixup_message)
+ if (logfile || have_option_m || use_message)
use_editor = 0;
if (0 <= edit_flag)
use_editor = edit_flag;
@@ -1227,6 +1301,42 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (also + only + all + interactive > 1)
die(_("Only one of --include/--only/--all/--interactive/--patch can be used."));
+
+ if (fixup_message) {
+ /*
+ * We limit --fixup's suboptions to only alpha characters.
+ * If the first character after a run of alpha is colon,
+ * then the part before the colon may be a known suboption
+ * name like `amend` or `reword`, or a misspelt suboption
+ * name. In either case, we treat it as
+ * --fixup=<suboption>:<arg>.
+ *
+ * Otherwise, we are dealing with --fixup=<commit>.
+ */
+ char *p = fixup_message;
+ while (isalpha(*p))
+ p++;
+ if (p > fixup_message && *p == ':') {
+ *p = '\0';
+ fixup_commit = p + 1;
+ if (!strcmp("amend", fixup_message) ||
+ !strcmp("reword", fixup_message)) {
+ fixup_prefix = "amend";
+ allow_empty = 1;
+ if (*fixup_message == 'r') {
+ check_fixup_reword_options(argc, argv);
+ only = 1;
+ }
+ } else {
+ die(_("unknown option: --fixup=%s:%s"), fixup_message, fixup_commit);
+ }
+ } else {
+ fixup_commit = fixup_message;
+ fixup_prefix = "fixup";
+ use_editor = 0;
+ }
+ }
+
cleanup_mode = get_cleanup_mode(cleanup_arg, use_editor);
handle_untracked_files_arg(s);
@@ -1504,9 +1614,14 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
OPT_CALLBACK('m', "message", &message, N_("message"), N_("commit message"), opt_parse_m),
OPT_STRING('c', "reedit-message", &edit_message, N_("commit"), N_("reuse and edit message from specified commit")),
OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")),
- OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
+ /*
+ * TRANSLATORS: Leave "[(amend|reword):]" as-is,
+ * and only translate <commit>.
+ */
+ OPT_STRING(0, "fixup", &fixup_message, N_("[(amend|reword):]commit"), N_("use autosquash formatted message to fixup or amend/reword specified commit")),
OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+ OPT_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG, opt_pass_trailer),
OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
@@ -1663,6 +1778,19 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
exit(1);
}
+ if (fixup_message && starts_with(sb.buf, "amend! ") &&
+ !allow_empty_message) {
+ struct strbuf body = STRBUF_INIT;
+ size_t len = commit_subject_length(sb.buf);
+ strbuf_addstr(&body, sb.buf + len);
+ if (message_is_empty(&body, cleanup_mode)) {
+ rollback_index_files();
+ fprintf(stderr, _("Aborting commit due to empty commit message body.\n"));
+ exit(1);
+ }
+ strbuf_release(&body);
+ }
+
if (amend) {
const char *exclude_gpgsig[3] = { "gpgsig", "gpgsig-sha256", NULL };
extra = read_commit_extra_headers(current_head, exclude_gpgsig);
diff --git a/builtin/config.c b/builtin/config.c
index f71fa39b38..865fddd6ce 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -671,9 +671,9 @@ int cmd_config(int argc, const char **argv, const char *prefix)
}
if (use_global_config) {
- char *user_config = expand_user_path("~/.gitconfig", 0);
- char *xdg_config = xdg_config_home("config");
+ char *user_config, *xdg_config;
+ git_global_config(&user_config, &xdg_config);
if (!user_config)
/*
* It is unknown if HOME/.gitconfig exists, so
@@ -695,7 +695,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
}
}
else if (use_system_config) {
- given_config_source.file = git_etc_gitconfig();
+ given_config_source.file = git_system_config();
given_config_source.scope = CONFIG_SCOPE_SYSTEM;
} else if (use_local_config) {
given_config_source.file = git_pathdup("config");
diff --git a/builtin/credential-cache--daemon.c b/builtin/credential-cache--daemon.c
index c61f123a3b..4c6c89ab0d 100644
--- a/builtin/credential-cache--daemon.c
+++ b/builtin/credential-cache--daemon.c
@@ -203,9 +203,10 @@ static int serve_cache_loop(int fd)
static void serve_cache(const char *socket_path, int debug)
{
+ struct unix_stream_listen_opts opts = UNIX_STREAM_LISTEN_OPTS_INIT;
int fd;
- fd = unix_stream_listen(socket_path);
+ fd = unix_stream_listen(socket_path, &opts);
if (fd < 0)
die_errno("unable to bind to '%s'", socket_path);
diff --git a/builtin/credential-cache.c b/builtin/credential-cache.c
index 9b3f709905..76a6ba3722 100644
--- a/builtin/credential-cache.c
+++ b/builtin/credential-cache.c
@@ -14,7 +14,7 @@
static int send_request(const char *socket, const struct strbuf *out)
{
int got_data = 0;
- int fd = unix_stream_connect(socket);
+ int fd = unix_stream_connect(socket, 0);
if (fd < 0)
return -1;
diff --git a/builtin/describe.c b/builtin/describe.c
index 40482d8e9f..e912ba50d7 100644
--- a/builtin/describe.c
+++ b/builtin/describe.c
@@ -502,7 +502,7 @@ static void describe_blob(struct object_id oid, struct strbuf *dst)
{
struct rev_info revs;
struct strvec args = STRVEC_INIT;
- struct process_commit_data pcd = { null_oid, oid, dst, &revs};
+ struct process_commit_data pcd = { *null_oid(), oid, dst, &revs};
strvec_pushl(&args, "internal: The first arg is not parsed",
"--objects", "--in-commit-order", "--reverse", "HEAD",
diff --git a/builtin/diff.c b/builtin/diff.c
index 617b9a4101..2d87c37a17 100644
--- a/builtin/diff.c
+++ b/builtin/diff.c
@@ -98,7 +98,7 @@ static int builtin_diff_b_f(struct rev_info *revs,
stuff_change(&revs->diffopt,
blob[0]->mode, canon_mode(st.st_mode),
- &blob[0]->item->oid, &null_oid,
+ &blob[0]->item->oid, null_oid(),
1, 0,
blob[0]->path ? blob[0]->path : path,
path);
diff --git a/builtin/difftool.c b/builtin/difftool.c
index 6e18e623fd..89334b77fb 100644
--- a/builtin/difftool.c
+++ b/builtin/difftool.c
@@ -23,6 +23,7 @@
#include "lockfile.h"
#include "object-store.h"
#include "dir.h"
+#include "entry.h"
static int trust_exit_code;
@@ -322,7 +323,7 @@ static int checkout_path(unsigned mode, struct object_id *oid,
struct cache_entry *ce;
int ret;
- ce = make_transient_cache_entry(mode, oid, path, 0);
+ ce = make_transient_cache_entry(mode, oid, path, 0, NULL);
ret = checkout_entry(ce, state, NULL, NULL);
discard_cache_entry(ce);
@@ -584,6 +585,9 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1);
rc = run_command_v_opt(helper_argv, flags);
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(&wtindex);
+
/*
* If the diff includes working copy files and those
* files were modified during the diff, then the changes
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index 85a76e0ef8..3c20f164f0 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -870,7 +870,7 @@ static void handle_tag(const char *name, struct tag *tag)
p = rewrite_commit((struct commit *)tagged);
if (!p) {
printf("reset %s\nfrom %s\n\n",
- name, oid_to_hex(&null_oid));
+ name, oid_to_hex(null_oid()));
free(buf);
return;
}
@@ -884,7 +884,7 @@ static void handle_tag(const char *name, struct tag *tag)
if (tagged->type == OBJ_TAG) {
printf("reset %s\nfrom %s\n\n",
- name, oid_to_hex(&null_oid));
+ name, oid_to_hex(null_oid()));
}
skip_prefix(name, "refs/tags/", &name);
printf("tag %s\n", name);
@@ -1016,7 +1016,7 @@ static void handle_tags_and_duplicates(struct string_list *extras)
* it.
*/
printf("reset %s\nfrom %s\n\n",
- name, oid_to_hex(&null_oid));
+ name, oid_to_hex(null_oid()));
continue;
}
@@ -1035,7 +1035,7 @@ static void handle_tags_and_duplicates(struct string_list *extras)
if (!reference_excluded_commits) {
/* delete the ref */
printf("reset %s\nfrom %s\n\n",
- name, oid_to_hex(&null_oid));
+ name, oid_to_hex(null_oid()));
continue;
}
/* set ref to commit using oid, not mark */
@@ -1146,7 +1146,7 @@ static void handle_deletes(void)
continue;
printf("reset %s\nfrom %s\n\n",
- refspec->dst, oid_to_hex(&null_oid));
+ refspec->dst, oid_to_hex(null_oid()));
}
}
diff --git a/builtin/fast-import.c b/builtin/fast-import.c
index dd4d09cece..20406f6775 100644
--- a/builtin/fast-import.c
+++ b/builtin/fast-import.c
@@ -940,7 +940,7 @@ static int store_object(
the_hash_algo->init_fn(&c);
the_hash_algo->update_fn(&c, hdr, hdrlen);
the_hash_algo->update_fn(&c, dat->buf, dat->len);
- the_hash_algo->final_fn(oid.hash, &c);
+ the_hash_algo->final_oid_fn(&oid, &c);
if (oidout)
oidcpy(oidout, &oid);
@@ -1136,7 +1136,7 @@ static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark)
}
}
git_deflate_end(&s);
- the_hash_algo->final_fn(oid.hash, &c);
+ the_hash_algo->final_oid_fn(&oid, &c);
if (oidout)
oidcpy(oidout, &oid);
@@ -1276,8 +1276,8 @@ static void load_tree(struct tree_entry *root)
e->versions[0].mode = e->versions[1].mode;
e->name = to_atom(c, strlen(c));
c += e->name->str_len + 1;
- hashcpy(e->versions[0].oid.hash, (unsigned char *)c);
- hashcpy(e->versions[1].oid.hash, (unsigned char *)c);
+ oidread(&e->versions[0].oid, (unsigned char *)c);
+ oidread(&e->versions[1].oid, (unsigned char *)c);
c += the_hash_algo->rawsz;
}
free(buf);
@@ -3322,7 +3322,7 @@ static void option_rewrite_submodules(const char *arg, struct string_list *list)
die(_("Expected format name:filename for submodule rewrite option"));
*f = '\0';
f++;
- ms = xcalloc(1, sizeof(*ms));
+ CALLOC_ARRAY(ms, 1);
fp = fopen(f, "r");
if (!fp)
@@ -3519,9 +3519,9 @@ int cmd_fast_import(int argc, const char **argv, const char *prefix)
alloc_objects(object_entry_alloc);
strbuf_init(&command_buf, 0);
- atom_table = xcalloc(atom_table_sz, sizeof(struct atom_str*));
- branch_table = xcalloc(branch_table_sz, sizeof(struct branch*));
- avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*));
+ CALLOC_ARRAY(atom_table, atom_table_sz);
+ CALLOC_ARRAY(branch_table, branch_table_sz);
+ CALLOC_ARRAY(avail_tree_table, avail_tree_table_sz);
marks = mem_pool_calloc(&fi_mem_pool, 1, sizeof(struct mark_set));
hashmap_init(&object_table, object_entry_hashcmp, NULL, 0);
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 0b90de87c7..dfde96a435 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -48,6 +48,7 @@ enum {
static int fetch_prune_config = -1; /* unspecified */
static int fetch_show_forced_updates = 1;
static uint64_t forced_updates_ms = 0;
+static int prefetch = 0;
static int prune = -1; /* unspecified */
#define PRUNE_BY_DEFAULT 0 /* do we prune by default? */
@@ -82,6 +83,7 @@ static struct string_list server_options = STRING_LIST_INIT_DUP;
static struct string_list negotiation_tip = STRING_LIST_INIT_NODUP;
static int fetch_write_commit_graph = -1;
static int stdin_refspecs = 0;
+static int negotiate_only;
static int git_fetch_config(const char *k, const char *v, void *cb)
{
@@ -158,6 +160,8 @@ static struct option builtin_fetch_options[] = {
N_("do not fetch all tags (--no-tags)"), TAGS_UNSET),
OPT_INTEGER('j', "jobs", &max_jobs,
N_("number of submodules fetched in parallel")),
+ OPT_BOOL(0, "prefetch", &prefetch,
+ N_("modify the refspec to place all refs within refs/prefetch/")),
OPT_BOOL('p', "prune", &prune,
N_("prune remote-tracking branches no longer on remote")),
OPT_BOOL('P', "prune-tags", &prune_tags,
@@ -202,6 +206,8 @@ static struct option builtin_fetch_options[] = {
TRANSPORT_FAMILY_IPV6),
OPT_STRING_LIST(0, "negotiation-tip", &negotiation_tip, N_("revision"),
N_("report that we have only objects reachable from this object")),
+ OPT_BOOL(0, "negotiate-only", &negotiate_only,
+ N_("do not fetch a packfile; instead, print ancestors of negotiation tips")),
OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
OPT_BOOL(0, "auto-maintenance", &enable_auto_gc,
N_("run 'maintenance --auto' after fetching")),
@@ -436,6 +442,56 @@ static void find_non_local_tags(const struct ref *refs,
oidset_clear(&fetch_oids);
}
+static void filter_prefetch_refspec(struct refspec *rs)
+{
+ int i;
+
+ if (!prefetch)
+ return;
+
+ for (i = 0; i < rs->nr; i++) {
+ struct strbuf new_dst = STRBUF_INIT;
+ char *old_dst;
+ const char *sub = NULL;
+
+ if (rs->items[i].negative)
+ continue;
+ if (!rs->items[i].dst ||
+ (rs->items[i].src &&
+ !strncmp(rs->items[i].src, "refs/tags/", 10))) {
+ int j;
+
+ free(rs->items[i].src);
+ free(rs->items[i].dst);
+
+ for (j = i + 1; j < rs->nr; j++) {
+ rs->items[j - 1] = rs->items[j];
+ rs->raw[j - 1] = rs->raw[j];
+ }
+ rs->nr--;
+ i--;
+ continue;
+ }
+
+ old_dst = rs->items[i].dst;
+ strbuf_addstr(&new_dst, "refs/prefetch/");
+
+ /*
+ * If old_dst starts with "refs/", then place
+ * sub after that prefix. Otherwise, start at
+ * the beginning of the string.
+ */
+ if (!skip_prefix(old_dst, "refs/", &sub))
+ sub = old_dst;
+ strbuf_addstr(&new_dst, sub);
+
+ rs->items[i].dst = strbuf_detach(&new_dst, NULL);
+ rs->items[i].force = 1;
+
+ free(old_dst);
+ }
+}
+
static struct ref *get_ref_map(struct remote *remote,
const struct ref *remote_refs,
struct refspec *rs,
@@ -452,6 +508,10 @@ static struct ref *get_ref_map(struct remote *remote,
struct hashmap existing_refs;
int existing_refs_populated = 0;
+ filter_prefetch_refspec(rs);
+ if (remote)
+ filter_prefetch_refspec(&remote->fetch);
+
if (rs->nr) {
struct refspec *fetch_refspec;
@@ -520,7 +580,7 @@ static struct ref *get_ref_map(struct remote *remote,
if (has_merge &&
!strcmp(branch->remote_name, remote->name))
add_merge_config(&ref_map, remote_refs, branch, &tail);
- } else {
+ } else if (!prefetch) {
ref_map = get_remote_ref(remote_refs, "HEAD");
if (!ref_map)
die(_("Couldn't find remote ref HEAD"));
@@ -1986,7 +2046,29 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
}
}
- if (remote) {
+ if (negotiate_only) {
+ struct oidset acked_commits = OIDSET_INIT;
+ struct oidset_iter iter;
+ const struct object_id *oid;
+
+ if (!remote)
+ die(_("must supply remote when using --negotiate-only"));
+ gtransport = prepare_transport(remote, 1);
+ if (gtransport->smart_options) {
+ gtransport->smart_options->acked_commits = &acked_commits;
+ } else {
+ warning(_("Protocol does not support --negotiate-only, exiting."));
+ return 1;
+ }
+ if (server_options.nr)
+ gtransport->server_options = &server_options;
+ result = transport_fetch_refs(gtransport, NULL);
+
+ oidset_iter_init(&acked_commits, &iter);
+ while ((oid = oidset_iter_next(&iter)))
+ printf("%s\n", oid_to_hex(oid));
+ oidset_clear(&acked_commits);
+ } else if (remote) {
if (filter_options.choice || has_promisor_remote())
fetch_one_setup_partial(remote);
result = fetch_one(remote, argc, argv, prune_tags_ok, stdin_refspecs);
diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index cb9c81a046..89cb6307d4 100644
--- a/builtin/for-each-ref.c
+++ b/builtin/for-each-ref.c
@@ -22,6 +22,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
struct ref_array array;
struct ref_filter filter;
struct ref_format format = REF_FORMAT_INIT;
+ struct strbuf output = STRBUF_INIT;
+ struct strbuf err = STRBUF_INIT;
struct option opts[] = {
OPT_BIT('s', "shell", &format.quote_style,
@@ -80,8 +82,20 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
if (!maxcount || array.nr < maxcount)
maxcount = array.nr;
- for (i = 0; i < maxcount; i++)
- show_ref_array_item(array.items[i], &format);
+ for (i = 0; i < maxcount; i++) {
+ strbuf_reset(&err);
+ strbuf_reset(&output);
+ if (format_ref_array_item(array.items[i], &format, &output, &err))
+ die("%s", err.buf);
+ fwrite(output.buf, 1, output.len, stdout);
+ putchar('\n');
+ }
+
+ strbuf_release(&err);
+ strbuf_release(&output);
ref_array_clear(&array);
+ free_commit_list(filter.with_commit);
+ free_commit_list(filter.no_commit);
+ UNLEAK(sorting);
return 0;
}
diff --git a/builtin/fsck.c b/builtin/fsck.c
index d606c4444d..b42b6fe21f 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -726,7 +726,7 @@ static int fsck_cache_tree(struct cache_tree *it)
static void mark_object_for_connectivity(const struct object_id *oid)
{
- struct object *obj = lookup_unknown_object(oid);
+ struct object *obj = lookup_unknown_object(the_repository, oid);
obj->flags |= HAS_OBJ;
}
@@ -882,6 +882,8 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
verify_index_checksum = 1;
verify_ce_order = 1;
read_cache();
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(&the_index);
for (i = 0; i < active_nr; i++) {
unsigned int mode;
struct blob *blob;
diff --git a/builtin/gc.c b/builtin/gc.c
index ef7226d7bc..f05d2f0a1a 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -873,55 +873,40 @@ static int maintenance_task_commit_graph(struct maintenance_run_opts *opts)
return 0;
}
-static int fetch_remote(const char *remote, struct maintenance_run_opts *opts)
+static int fetch_remote(struct remote *remote, void *cbdata)
{
+ struct maintenance_run_opts *opts = cbdata;
struct child_process child = CHILD_PROCESS_INIT;
+ if (remote->skip_default_update)
+ return 0;
+
child.git_cmd = 1;
- strvec_pushl(&child.args, "fetch", remote, "--prune", "--no-tags",
+ strvec_pushl(&child.args, "fetch", remote->name,
+ "--prefetch", "--prune", "--no-tags",
"--no-write-fetch-head", "--recurse-submodules=no",
- "--refmap=", NULL);
+ NULL);
if (opts->quiet)
strvec_push(&child.args, "--quiet");
- strvec_pushf(&child.args, "+refs/heads/*:refs/prefetch/%s/*", remote);
-
return !!run_command(&child);
}
-static int append_remote(struct remote *remote, void *cbdata)
-{
- struct string_list *remotes = (struct string_list *)cbdata;
-
- string_list_append(remotes, remote->name);
- return 0;
-}
-
static int maintenance_task_prefetch(struct maintenance_run_opts *opts)
{
- int result = 0;
- struct string_list_item *item;
- struct string_list remotes = STRING_LIST_INIT_DUP;
-
git_config_set_multivar_gently("log.excludedecoration",
"refs/prefetch/",
"refs/prefetch/",
CONFIG_FLAGS_FIXED_VALUE |
CONFIG_FLAGS_MULTI_REPLACE);
- if (for_each_remote(append_remote, &remotes)) {
- error(_("failed to fill remotes"));
- result = 1;
- goto cleanup;
+ if (for_each_remote(fetch_remote, opts)) {
+ error(_("failed to prefetch remotes"));
+ return 1;
}
- for_each_string_list_item(item, &remotes)
- result |= fetch_remote(item->string, opts);
-
-cleanup:
- string_list_clear(&remotes, 0);
- return result;
+ return 0;
}
static int maintenance_task_gc(struct maintenance_run_opts *opts)
@@ -1924,6 +1909,7 @@ static int crontab_update_schedule(int run_maintenance, int fd, const char *cmd)
else if (!in_old_region)
fprintf(cron_in, "%s\n", line.buf);
}
+ strbuf_release(&line);
if (run_maintenance) {
struct strbuf line_format = STRBUF_INIT;
@@ -1986,8 +1972,10 @@ static int update_background_schedule(int enable)
cmd = sep + 1;
}
- if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0)
- 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;
+ }
if (!strcmp(scheduler, "launchctl"))
result = launchctl_update_schedule(enable, get_lock_file_fd(&lk), cmd);
@@ -1999,6 +1987,9 @@ static int update_background_schedule(int enable)
die("unknown background scheduler: %s", scheduler);
rollback_lock_file(&lk);
+
+cleanup:
+ free(lock_path);
free(testing);
return result;
}
diff --git a/builtin/grep.c b/builtin/grep.c
index 4e91a253ac..ab8822e68f 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -211,7 +211,7 @@ static void start_threads(struct grep_opt *opt)
strbuf_init(&todo[i].out, 0);
}
- threads = xcalloc(num_threads, sizeof(*threads));
+ CALLOC_ARRAY(threads, num_threads);
for (i = 0; i < num_threads; i++) {
int err;
struct grep_opt *o = grep_opt_dup(opt);
@@ -421,7 +421,7 @@ static int grep_submodule(struct grep_opt *opt,
struct grep_opt subopt;
int hit;
- sub = submodule_from_path(superproject, &null_oid, path);
+ sub = submodule_from_path(superproject, null_oid(), path);
if (!is_submodule_active(superproject, path))
return 0;
@@ -504,6 +504,8 @@ static int grep_cache(struct grep_opt *opt,
if (repo_read_index(repo) < 0)
die(_("index file corrupt"));
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(repo->index);
for (nr = 0; nr < repo->index->cache_nr; nr++) {
const struct cache_entry *ce = repo->index->cache[nr];
@@ -1181,6 +1183,5 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
run_pager(&opt, prefix);
clear_pathspec(&pathspec);
free_grep_patterns(&opt);
- grep_destroy();
return !hit;
}
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index 11f0fafd33..3fbc5d7077 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -185,7 +185,7 @@ static void init_thread(void)
if (show_stat)
pthread_mutex_init(&deepest_delta_mutex, NULL);
pthread_key_create(&key, NULL);
- thread_data = xcalloc(nr_threads, sizeof(*thread_data));
+ 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)
@@ -489,7 +489,7 @@ static void *unpack_entry_data(off_t offset, unsigned long size,
bad_object(offset, _("inflate returned %d"), status);
git_inflate_end(&stream);
if (oid)
- the_hash_algo->final_fn(oid->hash, &c);
+ the_hash_algo->final_oid_fn(oid, &c);
return buf == fixed_buf ? NULL : buf;
}
@@ -524,7 +524,7 @@ static void *unpack_raw_entry(struct object_entry *obj,
switch (obj->type) {
case OBJ_REF_DELTA:
- hashcpy(ref_oid->hash, fill(the_hash_algo->rawsz));
+ oidread(ref_oid, fill(the_hash_algo->rawsz));
use(the_hash_algo->rawsz);
break;
case OBJ_OFS_DELTA:
@@ -1358,7 +1358,7 @@ static struct object_entry *append_obj_to_pack(struct hashfile *f,
obj[1].idx.offset += write_compressed(f, buf, size);
obj[0].idx.crc32 = crc32_end(f);
hashflush(f);
- hashcpy(obj->idx.oid.hash, sha1);
+ oidread(&obj->idx.oid, sha1);
return obj;
}
@@ -1675,7 +1675,7 @@ static void show_pack_info(int stat_only)
unsigned long *chain_histogram = NULL;
if (deepest_delta)
- chain_histogram = xcalloc(deepest_delta, sizeof(unsigned long));
+ CALLOC_ARRAY(chain_histogram, deepest_delta);
for (i = 0; i < nr_objects; i++) {
struct object_entry *obj = &objects[i];
@@ -1897,10 +1897,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
curr_pack = open_pack_file(pack_name);
parse_pack_header();
- objects = xcalloc(st_add(nr_objects, 1), sizeof(struct object_entry));
+ CALLOC_ARRAY(objects, st_add(nr_objects, 1));
if (show_stat)
- obj_stat = xcalloc(st_add(nr_objects, 1), sizeof(struct object_stat));
- ofs_deltas = xcalloc(nr_objects, sizeof(struct ofs_delta_entry));
+ CALLOC_ARRAY(obj_stat, st_add(nr_objects, 1));
+ CALLOC_ARRAY(ofs_deltas, nr_objects);
parse_pack_objects(pack_hash);
if (report_end_of_input)
write_in_full(2, "\0", 1);
diff --git a/builtin/init-db.c b/builtin/init-db.c
index dcc45bef51..2167796ff2 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -25,7 +25,6 @@
static int init_is_bare_repository = 0;
static int init_shared_repository = -1;
-static const char *init_db_template_dir;
static void copy_templates_1(struct strbuf *path, struct strbuf *template_path,
DIR *dir)
@@ -94,7 +93,7 @@ static void copy_templates_1(struct strbuf *path, struct strbuf *template_path,
}
}
-static void copy_templates(const char *template_dir)
+static void copy_templates(const char *template_dir, const char *init_template_dir)
{
struct strbuf path = STRBUF_INIT;
struct strbuf template_path = STRBUF_INIT;
@@ -107,7 +106,7 @@ static void copy_templates(const char *template_dir)
if (!template_dir)
template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT);
if (!template_dir)
- template_dir = init_db_template_dir;
+ template_dir = init_template_dir;
if (!template_dir)
template_dir = to_free = system_path(DEFAULT_GIT_TEMPLATE_DIR);
if (!template_dir[0]) {
@@ -154,17 +153,6 @@ free_return:
clear_repository_format(&template_format);
}
-static int git_init_db_config(const char *k, const char *v, void *cb)
-{
- if (!strcmp(k, "init.templatedir"))
- return git_config_pathname(&init_db_template_dir, k, v);
-
- if (starts_with(k, "core."))
- return platform_core_config(k, v, cb);
-
- return 0;
-}
-
/*
* If the git_dir is not directly inside the working tree, then git will not
* find it by default, and we need to set the worktree explicitly.
@@ -212,10 +200,8 @@ static int create_default_files(const char *template_path,
int reinit;
int filemode;
struct strbuf err = STRBUF_INIT;
-
- /* Just look for `init.templatedir` */
- init_db_template_dir = NULL; /* re-set in case it was set before */
- git_config(git_init_db_config, NULL);
+ const char *init_template_dir = NULL;
+ const char *work_tree = get_git_work_tree();
/*
* First copy the templates -- we might have the default
@@ -226,7 +212,9 @@ static int create_default_files(const char *template_path,
* values (since we've just potentially changed what's available on
* disk).
*/
- copy_templates(template_path);
+ git_config_get_pathname("init.templatedir", &init_template_dir);
+ copy_templates(template_path, init_template_dir);
+ free((char *)init_template_dir);
git_config_clear();
reset_shared_repository();
git_config(git_default_config, NULL);
@@ -235,7 +223,7 @@ static int create_default_files(const char *template_path,
* We must make sure command-line options continue to override any
* values we might have just re-read from the config.
*/
- is_bare_repository_cfg = init_is_bare_repository;
+ is_bare_repository_cfg = init_is_bare_repository || !work_tree;
if (init_shared_repository != -1)
set_shared_repository(init_shared_repository);
@@ -299,7 +287,6 @@ static int create_default_files(const char *template_path,
if (is_bare_repository())
git_config_set("core.bare", "true");
else {
- const char *work_tree = get_git_work_tree();
git_config_set("core.bare", "false");
/* allow template config file to override the default */
if (log_all_ref_updates == LOG_REFS_UNSET)
@@ -422,8 +409,8 @@ int init_db(const char *git_dir, const char *real_git_dir,
}
startup_info->have_repository = 1;
- /* Just look for `core.hidedotfiles` */
- git_config(git_init_db_config, NULL);
+ /* Ensure `core.hidedotfiles` is processed */
+ git_config(platform_core_config, NULL);
safe_create_dir(git_dir, 0);
@@ -575,8 +562,10 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
if (real_git_dir && !is_absolute_path(real_git_dir))
real_git_dir = real_pathdup(real_git_dir, 1);
- if (template_dir && *template_dir && !is_absolute_path(template_dir))
+ if (template_dir && *template_dir && !is_absolute_path(template_dir)) {
template_dir = absolute_pathdup(template_dir);
+ UNLEAK(template_dir);
+ }
if (argc == 1) {
int mkdir_tried = 0;
diff --git a/builtin/log.c b/builtin/log.c
index f67b67d80e..6102893fcc 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -481,6 +481,8 @@ static int git_log_config(const char *var, const char *value, void *cb)
decoration_style = 0; /* maybe warn? */
return 0;
}
+ if (!strcmp(var, "log.diffmerges"))
+ return diff_merges_config(value);
if (!strcmp(var, "log.showroot")) {
default_show_root = git_config_bool(var, value);
return 0;
@@ -599,7 +601,7 @@ static int show_tag_object(const struct object_id *oid, struct rev_info *rev)
static int show_tree_object(const struct object_id *oid,
struct strbuf *base,
- const char *pathname, unsigned mode, int stage, void *context)
+ const char *pathname, unsigned mode, void *context)
{
FILE *file = context;
fprintf(file, "%s%s\n", pathname, S_ISDIR(mode) ? "/" : "");
@@ -681,9 +683,9 @@ int cmd_show(int argc, const char **argv, const char *prefix)
diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
name,
diff_get_color_opt(&rev.diffopt, DIFF_RESET));
- read_tree_recursive(the_repository, (struct tree *)o, "",
- 0, 0, &match_all, show_tree_object,
- rev.diffopt.file);
+ read_tree(the_repository, (struct tree *)o,
+ &match_all, show_tree_object,
+ rev.diffopt.file);
rev.shown_one = 1;
break;
case OBJ_COMMIT:
@@ -1662,13 +1664,19 @@ static void print_bases(struct base_tree_info *bases, FILE *file)
oidclr(&bases->base_commit);
}
-static const char *diff_title(struct strbuf *sb, int reroll_count,
- const char *generic, const char *rerolled)
+static const char *diff_title(struct strbuf *sb,
+ const char *reroll_count,
+ const char *generic,
+ const char *rerolled)
{
- if (reroll_count <= 0)
+ int v;
+
+ /* RFC may be v0, so allow -v1 to diff against v0 */
+ if (reroll_count && !strtol_i(reroll_count, 10, &v) &&
+ v >= 1)
+ strbuf_addf(sb, rerolled, v - 1);
+ else
strbuf_addstr(sb, generic);
- else /* RFC may be v0, so allow -v1 to diff against v0 */
- strbuf_addf(sb, rerolled, reroll_count - 1);
return sb->buf;
}
@@ -1717,7 +1725,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
struct strbuf buf = STRBUF_INIT;
int use_patch_format = 0;
int quiet = 0;
- int reroll_count = -1;
+ const char *reroll_count = NULL;
char *cover_from_description_arg = NULL;
char *branch_name = NULL;
char *base_commit = NULL;
@@ -1751,7 +1759,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
N_("use <sfx> instead of '.patch'")),
OPT_INTEGER(0, "start-number", &start_number,
N_("start numbering patches at <n> instead of 1")),
- OPT_INTEGER('v', "reroll-count", &reroll_count,
+ OPT_STRING('v', "reroll-count", &reroll_count, N_("reroll-count"),
N_("mark the series as Nth re-roll")),
OPT_INTEGER(0, "filename-max-length", &fmt_patch_name_max,
N_("max length of output filename")),
@@ -1862,9 +1870,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
if (cover_from_description_arg)
cover_from_description_mode = parse_cover_from_description(cover_from_description_arg);
- if (0 < reroll_count) {
+ if (reroll_count) {
struct strbuf sprefix = STRBUF_INIT;
- strbuf_addf(&sprefix, "%s v%d",
+
+ strbuf_addf(&sprefix, "%s v%s",
rev.subject_prefix, reroll_count);
rev.reroll_count = reroll_count;
rev.subject_prefix = strbuf_detach(&sprefix, NULL);
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index f6f9e483b2..45cc3b23dd 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -12,6 +12,7 @@
#include "dir.h"
#include "builtin.h"
#include "tree.h"
+#include "cache-tree.h"
#include "parse-options.h"
#include "resolve-undo.h"
#include "string-list.h"
@@ -56,7 +57,7 @@ static const char *tag_modified = "";
static const char *tag_skip_worktree = "";
static const char *tag_resolve_undo = "";
-static void write_eolinfo(const struct index_state *istate,
+static void write_eolinfo(struct index_state *istate,
const struct cache_entry *ce, const char *path)
{
if (show_eol) {
@@ -121,7 +122,7 @@ static void print_debug(const struct cache_entry *ce)
}
}
-static void show_dir_entry(const struct index_state *istate,
+static void show_dir_entry(struct index_state *istate,
const char *tag, struct dir_entry *ent)
{
int len = max_prefix_len;
@@ -138,7 +139,7 @@ static void show_dir_entry(const struct index_state *istate,
write_name(ent->name);
}
-static void show_other_files(const struct index_state *istate,
+static void show_other_files(struct index_state *istate,
const struct dir_struct *dir)
{
int i;
@@ -151,7 +152,7 @@ static void show_other_files(const struct index_state *istate,
}
}
-static void show_killed_files(const struct index_state *istate,
+static void show_killed_files(struct index_state *istate,
const struct dir_struct *dir)
{
int i;
@@ -209,7 +210,7 @@ static void show_submodule(struct repository *superproject,
{
struct repository subrepo;
const struct submodule *sub = submodule_from_path(superproject,
- &null_oid, path);
+ null_oid(), path);
if (repo_submodule_init(&subrepo, superproject, sub))
return;
@@ -253,7 +254,7 @@ static void show_ce(struct repository *repo, struct dir_struct *dir,
}
}
-static void show_ru_info(const struct index_state *istate)
+static void show_ru_info(struct index_state *istate)
{
struct string_list_item *item;
@@ -316,6 +317,8 @@ static void show_files(struct repository *repo, struct dir_struct *dir)
if (!(show_cached || show_stage || show_deleted || show_modified))
return;
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(repo->index);
for (i = 0; i < repo->index->cache_nr; i++) {
const struct cache_entry *ce = repo->index->cache[i];
struct stat st;
@@ -420,6 +423,53 @@ static int get_common_prefix_len(const char *common_prefix)
return common_prefix_len;
}
+static int read_one_entry_opt(struct index_state *istate,
+ const struct object_id *oid,
+ struct strbuf *base,
+ const char *pathname,
+ unsigned mode, int opt)
+{
+ int len;
+ struct cache_entry *ce;
+
+ if (S_ISDIR(mode))
+ return READ_TREE_RECURSIVE;
+
+ len = strlen(pathname);
+ ce = make_empty_cache_entry(istate, base->len + len);
+
+ ce->ce_mode = create_ce_mode(mode);
+ ce->ce_flags = create_ce_flags(1);
+ ce->ce_namelen = base->len + len;
+ memcpy(ce->name, base->buf, base->len);
+ memcpy(ce->name + base->len, pathname, len+1);
+ oidcpy(&ce->oid, oid);
+ return add_index_entry(istate, ce, opt);
+}
+
+static int read_one_entry(const struct object_id *oid, struct strbuf *base,
+ const char *pathname, unsigned mode,
+ void *context)
+{
+ struct index_state *istate = context;
+ return read_one_entry_opt(istate, oid, base, pathname,
+ mode,
+ ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK);
+}
+
+/*
+ * This is used when the caller knows there is no existing entries at
+ * the stage that will conflict with the entry being added.
+ */
+static int read_one_entry_quick(const struct object_id *oid, struct strbuf *base,
+ const char *pathname, unsigned mode,
+ void *context)
+{
+ struct index_state *istate = context;
+ return read_one_entry_opt(istate, oid, base, pathname,
+ mode, ADD_CACHE_JUST_APPEND);
+}
+
/*
* Read the tree specified with --with-tree option
* (typically, HEAD) into stage #1 and then
@@ -436,6 +486,8 @@ void overlay_tree_on_index(struct index_state *istate,
struct pathspec pathspec;
struct cache_entry *last_stage0 = NULL;
int i;
+ read_tree_fn_t fn = NULL;
+ int err;
if (get_oid(tree_name, &oid))
die("tree-ish %s not found.", tree_name);
@@ -444,6 +496,8 @@ void overlay_tree_on_index(struct index_state *istate,
die("bad tree-ish %s", tree_name);
/* Hoist the unmerged entries up to stage #3 to make room */
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(istate);
for (i = 0; i < istate->cache_nr; i++) {
struct cache_entry *ce = istate->cache[i];
if (!ce_stage(ce))
@@ -458,9 +512,32 @@ void overlay_tree_on_index(struct index_state *istate,
PATHSPEC_PREFER_CWD, prefix, matchbuf);
} else
memset(&pathspec, 0, sizeof(pathspec));
- if (read_tree(the_repository, tree, 1, &pathspec, istate))
+
+ /*
+ * See if we have cache entry at the stage. If so,
+ * do it the original slow way, otherwise, append and then
+ * sort at the end.
+ */
+ for (i = 0; !fn && i < istate->cache_nr; i++) {
+ const struct cache_entry *ce = istate->cache[i];
+ if (ce_stage(ce) == 1)
+ fn = read_one_entry;
+ }
+
+ if (!fn)
+ fn = read_one_entry_quick;
+ err = read_tree(the_repository, tree, &pathspec, fn, istate);
+ if (err)
die("unable to read tree entries %s", tree_name);
+ /*
+ * Sort the cache entry -- we need to nuke the cache tree, though.
+ */
+ if (fn == read_one_entry_quick) {
+ cache_tree_free(&istate->cache_tree);
+ QSORT(istate->cache, istate->cache_nr, cmp_cache_name_compare);
+ }
+
for (i = 0; i < istate->cache_nr; i++) {
struct cache_entry *ce = istate->cache[i];
switch (ce_stage(ce)) {
@@ -530,7 +607,7 @@ static int option_parse_exclude_standard(const struct option *opt,
int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
{
int require_work_tree = 0, show_tag = 0, i;
- const char *max_prefix;
+ char *max_prefix;
struct dir_struct dir;
struct pattern_list *pl;
struct string_list exclude_list = STRING_LIST_INIT_NODUP;
@@ -675,6 +752,9 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
if (pathspec.nr && error_unmatch)
ps_matched = xcalloc(pathspec.nr, 1);
+ if ((dir.flags & DIR_SHOW_IGNORED) && !show_others && !show_cached)
+ die("ls-files -i must be used with either -o or -c");
+
if ((dir.flags & DIR_SHOW_IGNORED) && !exc_given)
die("ls-files --ignored needs some exclude pattern");
@@ -708,5 +788,6 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
}
dir_clear(&dir);
+ free(max_prefix);
return 0;
}
diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c
index ef604752a0..1794548c71 100644
--- a/builtin/ls-remote.c
+++ b/builtin/ls-remote.c
@@ -88,7 +88,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
if (argc > 1) {
int i;
- pattern = xcalloc(argc, sizeof(const char *));
+ CALLOC_ARRAY(pattern, argc);
for (i = 1; i < argc; i++) {
pattern[i - 1] = xstrfmt("*/%s", argv[i]);
}
@@ -124,8 +124,6 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
int hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));
repo_set_hash_algo(the_repository, hash_algo);
}
- if (transport_disconnect(transport))
- return 1;
if (!dest && !quiet)
fprintf(stderr, "From %s\n", *remote->url);
@@ -151,5 +149,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
}
ref_array_clear(&ref_array);
+ if (transport_disconnect(transport))
+ return 1;
return status;
}
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 7cad3f24eb..3a442631c7 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -62,7 +62,7 @@ static int show_recursive(const char *base, int baselen, const char *pathname)
}
static int show_tree(const struct object_id *oid, struct strbuf *base,
- const char *pathname, unsigned mode, int stage, void *context)
+ const char *pathname, unsigned mode, void *context)
{
int retval = 0;
int baselen;
@@ -185,6 +185,6 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
tree = parse_tree_indirect(&oid);
if (!tree)
die("not a tree object");
- return !!read_tree_recursive(the_repository, tree, "", 0, 0,
- &pathspec, show_tree, NULL);
+ return !!read_tree(the_repository, tree,
+ &pathspec, show_tree, NULL);
}
diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c
index cfb667a594..01d16ef9e5 100644
--- a/builtin/mailinfo.c
+++ b/builtin/mailinfo.c
@@ -7,54 +7,103 @@
#include "utf8.h"
#include "strbuf.h"
#include "mailinfo.h"
+#include "parse-options.h"
-static const char mailinfo_usage[] =
- "git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] <msg> <patch> < mail >info";
+static const char * const mailinfo_usage[] = {
+ /* TRANSLATORS: keep <> in "<" mail ">" info. */
+ N_("git mailinfo [<options>] <msg> <patch> < mail >info"),
+ NULL,
+};
+
+struct metainfo_charset
+{
+ enum {
+ CHARSET_DEFAULT,
+ CHARSET_NO_REENCODE,
+ CHARSET_EXPLICIT,
+ } policy;
+ const char *charset;
+};
+
+static int parse_opt_explicit_encoding(const struct option *opt,
+ const char *arg, int unset)
+{
+ struct metainfo_charset *meta_charset = opt->value;
+
+ BUG_ON_OPT_NEG(unset);
+
+ meta_charset->policy = CHARSET_EXPLICIT;
+ meta_charset->charset = arg;
+
+ return 0;
+}
+
+static int parse_opt_quoted_cr(const struct option *opt, const char *arg, int unset)
+{
+ BUG_ON_OPT_NEG(unset);
+
+ if (mailinfo_parse_quoted_cr_action(arg, opt->value) != 0)
+ return error(_("bad action '%s' for '%s'"), arg, "--quoted-cr");
+ return 0;
+}
int cmd_mailinfo(int argc, const char **argv, const char *prefix)
{
- const char *def_charset;
+ struct metainfo_charset meta_charset;
struct mailinfo mi;
int status;
char *msgfile, *patchfile;
+ struct option options[] = {
+ OPT_BOOL('k', NULL, &mi.keep_subject, N_("keep subject")),
+ OPT_BOOL('b', NULL, &mi.keep_non_patch_brackets_in_subject,
+ N_("keep non patch brackets in subject")),
+ OPT_BOOL('m', "message-id", &mi.add_message_id,
+ N_("copy Message-ID to the end of commit message")),
+ OPT_SET_INT_F('u', NULL, &meta_charset.policy,
+ N_("re-code metadata to i18n.commitEncoding"),
+ CHARSET_DEFAULT, PARSE_OPT_NONEG),
+ OPT_SET_INT_F('n', NULL, &meta_charset.policy,
+ N_("disable charset re-coding of metadata"),
+ CHARSET_NO_REENCODE, PARSE_OPT_NONEG),
+ OPT_CALLBACK_F(0, "encoding", &meta_charset, N_("encoding"),
+ N_("re-code metadata to this encoding"),
+ PARSE_OPT_NONEG, parse_opt_explicit_encoding),
+ OPT_BOOL(0, "scissors", &mi.use_scissors, N_("use scissors")),
+ OPT_CALLBACK_F(0, "quoted-cr", &mi.quoted_cr, N_("<action>"),
+ N_("action when quoted CR is found"),
+ PARSE_OPT_NONEG, parse_opt_quoted_cr),
+ OPT_HIDDEN_BOOL(0, "inbody-headers", &mi.use_inbody_headers,
+ N_("use headers in message's body")),
+ OPT_END()
+ };
+
setup_mailinfo(&mi);
+ meta_charset.policy = CHARSET_DEFAULT;
- def_charset = get_commit_output_encoding();
- mi.metainfo_charset = def_charset;
-
- while (1 < argc && argv[1][0] == '-') {
- if (!strcmp(argv[1], "-k"))
- mi.keep_subject = 1;
- else if (!strcmp(argv[1], "-b"))
- mi.keep_non_patch_brackets_in_subject = 1;
- else if (!strcmp(argv[1], "-m") || !strcmp(argv[1], "--message-id"))
- mi.add_message_id = 1;
- else if (!strcmp(argv[1], "-u"))
- mi.metainfo_charset = def_charset;
- else if (!strcmp(argv[1], "-n"))
- mi.metainfo_charset = NULL;
- else if (starts_with(argv[1], "--encoding="))
- mi.metainfo_charset = argv[1] + 11;
- else if (!strcmp(argv[1], "--scissors"))
- mi.use_scissors = 1;
- else if (!strcmp(argv[1], "--no-scissors"))
- mi.use_scissors = 0;
- else if (!strcmp(argv[1], "--no-inbody-headers"))
- mi.use_inbody_headers = 0;
- else
- usage(mailinfo_usage);
- argc--; argv++;
- }
+ argc = parse_options(argc, argv, prefix, options, mailinfo_usage, 0);
- if (argc != 3)
- usage(mailinfo_usage);
+ if (argc != 2)
+ usage_with_options(mailinfo_usage, options);
+
+ switch (meta_charset.policy) {
+ case CHARSET_DEFAULT:
+ mi.metainfo_charset = get_commit_output_encoding();
+ break;
+ case CHARSET_NO_REENCODE:
+ mi.metainfo_charset = NULL;
+ break;
+ case CHARSET_EXPLICIT:
+ break;
+ default:
+ BUG("invalid meta_charset.policy");
+ }
mi.input = stdin;
mi.output = stdout;
- msgfile = prefix_filename(prefix, argv[1]);
- patchfile = prefix_filename(prefix, argv[2]);
+ msgfile = prefix_filename(prefix, argv[0]);
+ patchfile = prefix_filename(prefix, argv[1]);
status = !!mailinfo(&mi, msgfile, patchfile);
clear_mailinfo(&mi);
diff --git a/builtin/merge-index.c b/builtin/merge-index.c
index 38ea6ad6ca..c0383fe9df 100644
--- a/builtin/merge-index.c
+++ b/builtin/merge-index.c
@@ -58,6 +58,8 @@ static void merge_one_path(const char *path)
static void merge_all(void)
{
int i;
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(&the_index);
for (i = 0; i < active_nr; i++) {
const struct cache_entry *ce = active_cache[i];
if (!ce_stage(ce))
@@ -80,6 +82,9 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)
read_cache();
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(&the_index);
+
i = 1;
if (!strcmp(argv[i], "-o")) {
one_shot = 1;
diff --git a/builtin/merge.c b/builtin/merge.c
index eb00b273e6..eddb8ae70d 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -210,7 +210,7 @@ static struct strategy *get_strategy(const char *name)
exit(1);
}
- ret = xcalloc(1, sizeof(struct strategy));
+ CALLOC_ARRAY(ret, 1);
ret->name = xstrdup(name);
ret->attr = NO_TRIVIAL;
return ret;
@@ -393,10 +393,14 @@ static void restore_state(const struct object_id *head,
}
/* This is called when no merge was necessary. */
-static void finish_up_to_date(const char *msg)
+static void finish_up_to_date(void)
{
- if (verbosity >= 0)
- printf("%s%s\n", squash ? _(" (nothing to squash)") : "", msg);
+ if (verbosity >= 0) {
+ if (squash)
+ puts(_("Already up to date. (nothing to squash)"));
+ else
+ puts(_("Already up to date."));
+ }
remove_merge_branch_state(the_repository);
}
@@ -1522,7 +1526,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* If head can reach all the merge then we are up to date.
* but first the most common case of merging one remote.
*/
- finish_up_to_date(_("Already up to date."));
+ finish_up_to_date();
goto done;
} else if (fast_forward != FF_NO && !remoteheads->next &&
!common->next &&
@@ -1610,7 +1614,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
}
}
if (up_to_date) {
- finish_up_to_date(_("Already up to date. Yeeah!"));
+ finish_up_to_date();
goto done;
}
}
diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c
index 5bf88cd2a8..5d3ea445fd 100644
--- a/builtin/multi-pack-index.c
+++ b/builtin/multi-pack-index.c
@@ -4,67 +4,181 @@
#include "parse-options.h"
#include "midx.h"
#include "trace2.h"
+#include "object-store.h"
+#define BUILTIN_MIDX_WRITE_USAGE \
+ N_("git multi-pack-index [<options>] write [--preferred-pack=<pack>]")
+
+#define BUILTIN_MIDX_VERIFY_USAGE \
+ N_("git multi-pack-index [<options>] verify")
+
+#define BUILTIN_MIDX_EXPIRE_USAGE \
+ N_("git multi-pack-index [<options>] expire")
+
+#define BUILTIN_MIDX_REPACK_USAGE \
+ N_("git multi-pack-index [<options>] repack [--batch-size=<size>]")
+
+static char const * const builtin_multi_pack_index_write_usage[] = {
+ BUILTIN_MIDX_WRITE_USAGE,
+ NULL
+};
+static char const * const builtin_multi_pack_index_verify_usage[] = {
+ BUILTIN_MIDX_VERIFY_USAGE,
+ NULL
+};
+static char const * const builtin_multi_pack_index_expire_usage[] = {
+ BUILTIN_MIDX_EXPIRE_USAGE,
+ NULL
+};
+static char const * const builtin_multi_pack_index_repack_usage[] = {
+ BUILTIN_MIDX_REPACK_USAGE,
+ NULL
+};
static char const * const builtin_multi_pack_index_usage[] = {
- N_("git multi-pack-index [<options>] (write|verify|expire|repack --batch-size=<size>)"),
+ BUILTIN_MIDX_WRITE_USAGE,
+ BUILTIN_MIDX_VERIFY_USAGE,
+ BUILTIN_MIDX_EXPIRE_USAGE,
+ BUILTIN_MIDX_REPACK_USAGE,
NULL
};
static struct opts_multi_pack_index {
const char *object_dir;
+ const char *preferred_pack;
unsigned long batch_size;
- int progress;
+ unsigned flags;
} opts;
-int cmd_multi_pack_index(int argc, const char **argv,
- const char *prefix)
+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(),
+};
+
+static struct option *add_common_options(struct option *prev)
{
- unsigned flags = 0;
+ return parse_options_concat(common_opts, prev);
+}
+
+static int cmd_multi_pack_index_write(int argc, const char **argv)
+{
+ struct option *options;
+ static struct option builtin_multi_pack_index_write_options[] = {
+ OPT_STRING(0, "preferred-pack", &opts.preferred_pack,
+ N_("preferred-pack"),
+ N_("pack for reuse when computing a multi-pack bitmap")),
+ OPT_END(),
+ };
+
+ options = add_common_options(builtin_multi_pack_index_write_options);
+
+ trace2_cmd_mode(argv[0]);
- static struct option builtin_multi_pack_index_options[] = {
- OPT_FILENAME(0, "object-dir", &opts.object_dir,
- N_("object directory containing set of packfile and pack-index pairs")),
- OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")),
+ argc = parse_options(argc, argv, NULL,
+ options, builtin_multi_pack_index_write_usage,
+ PARSE_OPT_KEEP_UNKNOWN);
+ if (argc)
+ usage_with_options(builtin_multi_pack_index_write_usage,
+ options);
+
+ FREE_AND_NULL(options);
+
+ return write_midx_file(opts.object_dir, opts.preferred_pack,
+ opts.flags);
+}
+
+static int cmd_multi_pack_index_verify(int argc, const char **argv)
+{
+ struct option *options = common_opts;
+
+ trace2_cmd_mode(argv[0]);
+
+ argc = parse_options(argc, argv, NULL,
+ options, builtin_multi_pack_index_verify_usage,
+ PARSE_OPT_KEEP_UNKNOWN);
+ if (argc)
+ usage_with_options(builtin_multi_pack_index_verify_usage,
+ options);
+
+ return verify_midx_file(the_repository, opts.object_dir, opts.flags);
+}
+
+static int cmd_multi_pack_index_expire(int argc, const char **argv)
+{
+ struct option *options = common_opts;
+
+ trace2_cmd_mode(argv[0]);
+
+ argc = parse_options(argc, argv, NULL,
+ options, builtin_multi_pack_index_expire_usage,
+ PARSE_OPT_KEEP_UNKNOWN);
+ if (argc)
+ usage_with_options(builtin_multi_pack_index_expire_usage,
+ options);
+
+ return expire_midx_packs(the_repository, opts.object_dir, opts.flags);
+}
+
+static int cmd_multi_pack_index_repack(int argc, const char **argv)
+{
+ struct option *options;
+ 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_END(),
};
+ options = add_common_options(builtin_multi_pack_index_repack_options);
+
+ trace2_cmd_mode(argv[0]);
+
+ argc = parse_options(argc, argv, NULL,
+ options,
+ builtin_multi_pack_index_repack_usage,
+ PARSE_OPT_KEEP_UNKNOWN);
+ if (argc)
+ usage_with_options(builtin_multi_pack_index_repack_usage,
+ options);
+
+ FREE_AND_NULL(options);
+
+ return midx_repack(the_repository, opts.object_dir,
+ (size_t)opts.batch_size, opts.flags);
+}
+
+int cmd_multi_pack_index(int argc, const char **argv,
+ const char *prefix)
+{
+ struct option *builtin_multi_pack_index_options = common_opts;
+
git_config(git_default_config, NULL);
- opts.progress = isatty(2);
+ if (isatty(2))
+ opts.flags |= MIDX_PROGRESS;
argc = parse_options(argc, argv, prefix,
builtin_multi_pack_index_options,
- builtin_multi_pack_index_usage, 0);
+ builtin_multi_pack_index_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
if (!opts.object_dir)
opts.object_dir = get_object_directory();
- if (opts.progress)
- flags |= MIDX_PROGRESS;
if (argc == 0)
+ goto usage;
+
+ if (!strcmp(argv[0], "repack"))
+ return cmd_multi_pack_index_repack(argc, argv);
+ else if (!strcmp(argv[0], "write"))
+ return cmd_multi_pack_index_write(argc, argv);
+ else if (!strcmp(argv[0], "verify"))
+ return cmd_multi_pack_index_verify(argc, argv);
+ else if (!strcmp(argv[0], "expire"))
+ return cmd_multi_pack_index_expire(argc, argv);
+ else {
+usage:
+ error(_("unrecognized subcommand: %s"), argv[0]);
usage_with_options(builtin_multi_pack_index_usage,
builtin_multi_pack_index_options);
-
- if (argc > 1) {
- die(_("too many arguments"));
- return 1;
}
-
- trace2_cmd_mode(argv[0]);
-
- if (!strcmp(argv[0], "repack"))
- return midx_repack(the_repository, opts.object_dir,
- (size_t)opts.batch_size, flags);
- if (opts.batch_size)
- die(_("--batch-size option is only for 'repack' subcommand"));
-
- if (!strcmp(argv[0], "write"))
- return write_midx_file(opts.object_dir, flags);
- if (!strcmp(argv[0], "verify"))
- return verify_midx_file(the_repository, opts.object_dir, flags);
- if (!strcmp(argv[0], "expire"))
- return expire_midx_packs(the_repository, opts.object_dir, flags);
-
- die(_("unrecognized subcommand: %s"), argv[0]);
}
diff --git a/builtin/mv.c b/builtin/mv.c
index 7dac714af9..3fccdcb645 100644
--- a/builtin/mv.c
+++ b/builtin/mv.c
@@ -221,7 +221,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
}
argc += last - first;
}
- } else if (!(ce = cache_file_exists(src, length, ignore_case))) {
+ } else if (!(ce = cache_file_exists(src, length, 0))) {
bad = _("not under version control");
} else if (ce_stage(ce)) {
bad = _("conflicted");
diff --git a/builtin/notes.c b/builtin/notes.c
index 08b8914d29..74bba39ca8 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -730,7 +730,7 @@ static int merge_commit(struct notes_merge_options *o)
else
oidclr(&parent_oid);
- t = xcalloc(1, sizeof(struct notes_tree));
+ CALLOC_ARRAY(t, 1);
init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0);
o->local_ref = local_ref_to_free =
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index 6d62aaf59a..de00adbb9e 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -37,6 +37,134 @@
#include "shallow.h"
#include "promisor-remote.h"
+/*
+ * Objects we are going to pack are collected in the `to_pack` structure.
+ * It contains an array (dynamically expanded) of the object data, and a map
+ * that can resolve SHA1s to their position in the array.
+ */
+static struct packing_data to_pack;
+
+static inline struct object_entry *oe_delta(
+ const struct packing_data *pack,
+ const struct object_entry *e)
+{
+ if (!e->delta_idx)
+ return NULL;
+ if (e->ext_base)
+ return &pack->ext_bases[e->delta_idx - 1];
+ else
+ return &pack->objects[e->delta_idx - 1];
+}
+
+static inline unsigned long oe_delta_size(struct packing_data *pack,
+ const struct object_entry *e)
+{
+ if (e->delta_size_valid)
+ return e->delta_size_;
+
+ /*
+ * pack->delta_size[] can't be NULL because oe_set_delta_size()
+ * must have been called when a new delta is saved with
+ * oe_set_delta().
+ * If oe_delta() returns NULL (i.e. default state, which means
+ * delta_size_valid is also false), then the caller must never
+ * call oe_delta_size().
+ */
+ return pack->delta_size[e - pack->objects];
+}
+
+unsigned long oe_get_size_slow(struct packing_data *pack,
+ const struct object_entry *e);
+
+static inline unsigned long oe_size(struct packing_data *pack,
+ const struct object_entry *e)
+{
+ if (e->size_valid)
+ return e->size_;
+
+ return oe_get_size_slow(pack, e);
+}
+
+static inline void oe_set_delta(struct packing_data *pack,
+ struct object_entry *e,
+ struct object_entry *delta)
+{
+ if (delta)
+ e->delta_idx = (delta - pack->objects) + 1;
+ else
+ e->delta_idx = 0;
+}
+
+static inline struct object_entry *oe_delta_sibling(
+ const struct packing_data *pack,
+ const struct object_entry *e)
+{
+ if (e->delta_sibling_idx)
+ return &pack->objects[e->delta_sibling_idx - 1];
+ return NULL;
+}
+
+static inline struct object_entry *oe_delta_child(
+ const struct packing_data *pack,
+ const struct object_entry *e)
+{
+ if (e->delta_child_idx)
+ return &pack->objects[e->delta_child_idx - 1];
+ return NULL;
+}
+
+static inline void oe_set_delta_child(struct packing_data *pack,
+ struct object_entry *e,
+ struct object_entry *delta)
+{
+ if (delta)
+ e->delta_child_idx = (delta - pack->objects) + 1;
+ else
+ e->delta_child_idx = 0;
+}
+
+static inline void oe_set_delta_sibling(struct packing_data *pack,
+ struct object_entry *e,
+ struct object_entry *delta)
+{
+ if (delta)
+ e->delta_sibling_idx = (delta - pack->objects) + 1;
+ else
+ e->delta_sibling_idx = 0;
+}
+
+static inline void oe_set_size(struct packing_data *pack,
+ struct object_entry *e,
+ unsigned long size)
+{
+ if (size < pack->oe_size_limit) {
+ e->size_ = size;
+ e->size_valid = 1;
+ } else {
+ e->size_valid = 0;
+ if (oe_get_size_slow(pack, e) != size)
+ BUG("'size' is supposed to be the object size!");
+ }
+}
+
+static inline void oe_set_delta_size(struct packing_data *pack,
+ struct object_entry *e,
+ unsigned long size)
+{
+ if (size < pack->oe_delta_size_limit) {
+ e->delta_size_ = size;
+ e->delta_size_valid = 1;
+ } else {
+ packing_data_lock(pack);
+ if (!pack->delta_size)
+ ALLOC_ARRAY(pack->delta_size, pack->nr_alloc);
+ packing_data_unlock(pack);
+
+ pack->delta_size[e - pack->objects] = size;
+ e->delta_size_valid = 0;
+ }
+}
+
#define IN_PACK(obj) oe_in_pack(&to_pack, obj)
#define SIZE(obj) oe_size(&to_pack, obj)
#define SET_SIZE(obj,size) oe_set_size(&to_pack, obj, size)
@@ -56,13 +184,6 @@ static const char *pack_usage[] = {
NULL
};
-/*
- * Objects we are going to pack are collected in the `to_pack` structure.
- * It contains an array (dynamically expanded) of the object data, and a map
- * that can resolve SHA1s to their position in the array.
- */
-static struct packing_data to_pack;
-
static struct pack_idx_entry **written_list;
static uint32_t nr_result, nr_written, nr_seen;
static struct bitmap_index *bitmap_git;
@@ -301,6 +422,17 @@ static void copy_pack_data(struct hashfile *f,
}
}
+static inline int oe_size_greater_than(struct packing_data *pack,
+ const struct object_entry *lhs,
+ unsigned long rhs)
+{
+ if (lhs->size_valid)
+ return lhs->size_ > rhs;
+ if (rhs < pack->oe_size_limit) /* rhs < 2^x <= lhs ? */
+ return 1;
+ return oe_get_size_slow(pack, lhs) > rhs;
+}
+
/* Return 0 if we will bust the pack-size limit */
static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entry *entry,
unsigned long limit, int usable_delta)
@@ -642,6 +774,14 @@ static int mark_tagged(const char *path, const struct object_id *oid, int flag,
return 0;
}
+static inline unsigned char oe_layer(struct packing_data *pack,
+ struct object_entry *e)
+{
+ if (!pack->layer)
+ return 0;
+ return pack->layer[e - pack->objects];
+}
+
static inline void add_to_write_order(struct object_entry **wo,
unsigned int *endp,
struct object_entry *e)
@@ -815,8 +955,8 @@ static struct reused_chunk {
/* The offset of the first object of this chunk in the original
* packfile. */
off_t original;
- /* The offset of the first object of this chunk in the generated
- * packfile minus "original". */
+ /* The difference for "original" minus the offset of the first object of
+ * this chunk in the generated packfile. */
off_t difference;
} *reused_chunks;
static int reused_chunks_nr;
@@ -1030,7 +1170,7 @@ static void write_pack_file(void)
write_order = compute_write_order();
do {
- struct object_id oid;
+ unsigned char hash[GIT_MAX_RAWSZ];
char *pack_tmp_name = NULL;
if (pack_to_stdout)
@@ -1059,13 +1199,13 @@ static void write_pack_file(void)
* If so, rewrite it like in fast-import
*/
if (pack_to_stdout) {
- finalize_hashfile(f, oid.hash, CSUM_HASH_IN_STREAM | CSUM_CLOSE);
+ finalize_hashfile(f, hash, CSUM_HASH_IN_STREAM | CSUM_CLOSE);
} else if (nr_written == nr_remaining) {
- finalize_hashfile(f, oid.hash, CSUM_HASH_IN_STREAM | CSUM_FSYNC | CSUM_CLOSE);
+ finalize_hashfile(f, hash, CSUM_HASH_IN_STREAM | CSUM_FSYNC | CSUM_CLOSE);
} else {
- int fd = finalize_hashfile(f, oid.hash, 0);
- fixup_pack_header_footer(fd, oid.hash, pack_tmp_name,
- nr_written, oid.hash, offset);
+ int fd = finalize_hashfile(f, hash, 0);
+ fixup_pack_header_footer(fd, hash, pack_tmp_name,
+ nr_written, hash, offset);
close(fd);
if (write_bitmap_index) {
if (write_bitmap_index != WRITE_BITMAP_QUIET)
@@ -1100,17 +1240,17 @@ static void write_pack_file(void)
strbuf_addf(&tmpname, "%s-", base_name);
if (write_bitmap_index) {
- bitmap_writer_set_checksum(oid.hash);
+ bitmap_writer_set_checksum(hash);
bitmap_writer_build_type_index(
&to_pack, written_list, nr_written);
}
finish_tmp_packfile(&tmpname, pack_tmp_name,
written_list, nr_written,
- &pack_idx_opts, oid.hash);
+ &pack_idx_opts, hash);
if (write_bitmap_index) {
- strbuf_addf(&tmpname, "%s.bitmap", oid_to_hex(&oid));
+ strbuf_addf(&tmpname, "%s.bitmap", hash_to_hex(hash));
stop_progress(&progress_state);
@@ -1124,7 +1264,7 @@ static void write_pack_file(void)
strbuf_release(&tmpname);
free(pack_tmp_name);
- puts(oid_to_hex(&oid));
+ puts(hash_to_hex(hash));
}
/* mark written objects as written to previous pack */
@@ -1188,7 +1328,8 @@ static int have_duplicate_entry(const struct object_id *oid,
return 1;
}
-static int want_found_object(int exclude, struct packed_git *p)
+static int want_found_object(const struct object_id *oid, int exclude,
+ struct packed_git *p)
{
if (exclude)
return 1;
@@ -1204,27 +1345,82 @@ static int want_found_object(int exclude, struct packed_git *p)
* make sure no copy of this object appears in _any_ pack that makes us
* to omit the object, so we need to check all the packs.
*
- * We can however first check whether these options can possible matter;
+ * We can however first check whether these options can possibly matter;
* if they do not matter we know we want the object in generated pack.
* Otherwise, we signal "-1" at the end to tell the caller that we do
* not know either way, and it needs to check more packs.
*/
- if (!ignore_packed_keep_on_disk &&
- !ignore_packed_keep_in_core &&
- (!local || !have_non_local_packs))
- return 1;
+ /*
+ * Objects in packs borrowed from elsewhere are discarded regardless of
+ * if they appear in other packs that weren't borrowed.
+ */
if (local && !p->pack_local)
return 0;
- if (p->pack_local &&
- ((ignore_packed_keep_on_disk && p->pack_keep) ||
- (ignore_packed_keep_in_core && p->pack_keep_in_core)))
- return 0;
+
+ /*
+ * Then handle .keep first, as we have a fast(er) path there.
+ */
+ if (ignore_packed_keep_on_disk || ignore_packed_keep_in_core) {
+ /*
+ * Set the flags for the kept-pack cache to be the ones we want
+ * to ignore.
+ *
+ * That is, if we are ignoring objects in on-disk keep packs,
+ * then we want to search through the on-disk keep and ignore
+ * the in-core ones.
+ */
+ unsigned flags = 0;
+ if (ignore_packed_keep_on_disk)
+ flags |= ON_DISK_KEEP_PACKS;
+ if (ignore_packed_keep_in_core)
+ flags |= IN_CORE_KEEP_PACKS;
+
+ if (ignore_packed_keep_on_disk && p->pack_keep)
+ return 0;
+ if (ignore_packed_keep_in_core && p->pack_keep_in_core)
+ return 0;
+ if (has_object_kept_pack(oid, flags))
+ return 0;
+ }
+
+ /*
+ * At this point we know definitively that either we don't care about
+ * keep-packs, or the object is not in one. Keep checking other
+ * conditions...
+ */
+ if (!local || !have_non_local_packs)
+ return 1;
/* we don't know yet; keep looking for more packs */
return -1;
}
+static int want_object_in_pack_one(struct packed_git *p,
+ const struct object_id *oid,
+ int exclude,
+ struct packed_git **found_pack,
+ off_t *found_offset)
+{
+ off_t offset;
+
+ if (p == *found_pack)
+ offset = *found_offset;
+ else
+ offset = find_pack_entry_one(oid->hash, p);
+
+ if (offset) {
+ if (!*found_pack) {
+ if (!is_pack_valid(p))
+ return -1;
+ *found_offset = offset;
+ *found_pack = p;
+ }
+ return want_found_object(oid, exclude, p);
+ }
+ return -1;
+}
+
/*
* Check whether we want the object in the pack (e.g., we do not want
* objects found in non-local stores if the "--local" option was used).
@@ -1252,7 +1448,7 @@ static int want_object_in_pack(const struct object_id *oid,
* are present we will determine the answer right now.
*/
if (*found_pack) {
- want = want_found_object(exclude, *found_pack);
+ want = want_found_object(oid, exclude, *found_pack);
if (want != -1)
return want;
}
@@ -1260,51 +1456,20 @@ static int want_object_in_pack(const struct object_id *oid,
for (m = get_multi_pack_index(the_repository); m; m = m->next) {
struct pack_entry e;
if (fill_midx_entry(the_repository, oid, &e, m)) {
- struct packed_git *p = e.p;
- off_t offset;
-
- if (p == *found_pack)
- offset = *found_offset;
- else
- offset = find_pack_entry_one(oid->hash, p);
-
- if (offset) {
- if (!*found_pack) {
- if (!is_pack_valid(p))
- continue;
- *found_offset = offset;
- *found_pack = p;
- }
- want = want_found_object(exclude, p);
- if (want != -1)
- return want;
- }
+ want = want_object_in_pack_one(e.p, oid, exclude, found_pack, found_offset);
+ if (want != -1)
+ return want;
}
}
list_for_each(pos, get_packed_git_mru(the_repository)) {
struct packed_git *p = list_entry(pos, struct packed_git, mru);
- off_t offset;
-
- if (p == *found_pack)
- offset = *found_offset;
- else
- offset = find_pack_entry_one(oid->hash, p);
-
- if (offset) {
- if (!*found_pack) {
- if (!is_pack_valid(p))
- continue;
- *found_offset = offset;
- *found_pack = p;
- }
- want = want_found_object(exclude, p);
- if (!exclude && want > 0)
- list_move(&p->mru,
- get_packed_git_mru(the_repository));
- if (want != -1)
- return want;
- }
+ want = want_object_in_pack_one(p, oid, exclude, found_pack, found_offset);
+ if (!exclude && want > 0)
+ list_move(&p->mru,
+ get_packed_git_mru(the_repository));
+ if (want != -1)
+ return want;
}
if (uri_protocols.nr) {
@@ -1635,7 +1800,7 @@ static void add_preferred_base(struct object_id *oid)
}
}
- it = xcalloc(1, sizeof(*it));
+ CALLOC_ARRAY(it, 1);
it->next = pbase_tree;
pbase_tree = it;
@@ -2096,7 +2261,7 @@ static void get_object_details(void)
progress_state = start_progress(_("Counting objects"),
to_pack.nr_objects);
- sorted_by_offset = xcalloc(to_pack.nr_objects, sizeof(struct object_entry *));
+ CALLOC_ARRAY(sorted_by_offset, to_pack.nr_objects);
for (i = 0; i < to_pack.nr_objects; i++)
sorted_by_offset[i] = to_pack.objects + i;
QSORT(sorted_by_offset, to_pack.nr_objects, pack_offset_sort);
@@ -2206,6 +2371,26 @@ static pthread_mutex_t progress_mutex;
* progress_mutex for protection.
*/
+static inline int oe_size_less_than(struct packing_data *pack,
+ const struct object_entry *lhs,
+ unsigned long rhs)
+{
+ if (lhs->size_valid)
+ return lhs->size_ < rhs;
+ if (rhs < pack->oe_size_limit) /* rhs < 2^x <= lhs ? */
+ return 0;
+ return oe_get_size_slow(pack, lhs) < rhs;
+}
+
+static inline void oe_set_tree_depth(struct packing_data *pack,
+ struct object_entry *e,
+ unsigned int tree_depth)
+{
+ if (!pack->tree_depth)
+ CALLOC_ARRAY(pack->tree_depth, pack->nr_alloc);
+ pack->tree_depth[e - pack->objects] = tree_depth;
+}
+
/*
* Return the size of the object without doing any delta
* reconstruction (so non-deltas are true object sizes, but deltas
@@ -2428,7 +2613,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,
struct unpacked *array;
unsigned long mem_usage = 0;
- array = xcalloc(window, sizeof(struct unpacked));
+ CALLOC_ARRAY(array, window);
for (;;) {
struct object_entry *entry;
@@ -2665,7 +2850,7 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size,
if (progress > pack_to_stdout)
fprintf_ln(stderr, _("Delta compression using up to %d threads"),
delta_search_threads);
- p = xcalloc(delta_search_threads, sizeof(*p));
+ CALLOC_ARRAY(p, delta_search_threads);
/* Partition the work amongst work threads. */
for (i = 0; i < delta_search_threads; i++) {
@@ -2986,6 +3171,191 @@ static int git_pack_config(const char *k, const char *v, void *cb)
return git_default_config(k, v, cb);
}
+/* Counters for trace2 output when in --stdin-packs mode. */
+static int stdin_packs_found_nr;
+static int stdin_packs_hints_nr;
+
+static int add_object_entry_from_pack(const struct object_id *oid,
+ struct packed_git *p,
+ uint32_t pos,
+ void *_data)
+{
+ struct rev_info *revs = _data;
+ struct object_info oi = OBJECT_INFO_INIT;
+ off_t ofs;
+ enum object_type type;
+
+ display_progress(progress_state, ++nr_seen);
+
+ if (have_duplicate_entry(oid, 0))
+ return 0;
+
+ ofs = nth_packed_object_offset(p, pos);
+ if (!want_object_in_pack(oid, 0, &p, &ofs))
+ return 0;
+
+ oi.typep = &type;
+ if (packed_object_info(the_repository, p, ofs, &oi) < 0)
+ die(_("could not get type of object %s in pack %s"),
+ oid_to_hex(oid), p->pack_name);
+ else if (type == OBJ_COMMIT) {
+ /*
+ * commits in included packs are used as starting points for the
+ * subsequent revision walk
+ */
+ add_pending_oid(revs, NULL, oid, 0);
+ }
+
+ stdin_packs_found_nr++;
+
+ create_object_entry(oid, type, 0, 0, 0, p, ofs);
+
+ return 0;
+}
+
+static void show_commit_pack_hint(struct commit *commit, void *_data)
+{
+ /* nothing to do; commits don't have a namehash */
+}
+
+static void show_object_pack_hint(struct object *object, const char *name,
+ void *_data)
+{
+ struct object_entry *oe = packlist_find(&to_pack, &object->oid);
+ if (!oe)
+ return;
+
+ /*
+ * Our 'to_pack' list was constructed by iterating all objects packed in
+ * included packs, and so doesn't have a non-zero hash field that you
+ * would typically pick up during a reachability traversal.
+ *
+ * Make a best-effort attempt to fill in the ->hash and ->no_try_delta
+ * here using a now in order to perhaps improve the delta selection
+ * process.
+ */
+ oe->hash = pack_name_hash(name);
+ oe->no_try_delta = name && no_try_delta(name);
+
+ stdin_packs_hints_nr++;
+}
+
+static int pack_mtime_cmp(const void *_a, const void *_b)
+{
+ struct packed_git *a = ((const struct string_list_item*)_a)->util;
+ struct packed_git *b = ((const struct string_list_item*)_b)->util;
+
+ /*
+ * order packs by descending mtime so that objects are laid out
+ * roughly as newest-to-oldest
+ */
+ if (a->mtime < b->mtime)
+ return 1;
+ else if (b->mtime < a->mtime)
+ return -1;
+ else
+ return 0;
+}
+
+static void read_packs_list_from_stdin(void)
+{
+ struct strbuf buf = STRBUF_INIT;
+ struct string_list include_packs = STRING_LIST_INIT_DUP;
+ struct string_list exclude_packs = STRING_LIST_INIT_DUP;
+ struct string_list_item *item = NULL;
+
+ struct packed_git *p;
+ struct rev_info revs;
+
+ repo_init_revisions(the_repository, &revs, NULL);
+ /*
+ * Use a revision walk to fill in the namehash of objects in the include
+ * packs. To save time, we'll avoid traversing through objects that are
+ * in excluded packs.
+ *
+ * That may cause us to avoid populating all of the namehash fields of
+ * all included objects, but our goal is best-effort, since this is only
+ * an optimization during delta selection.
+ */
+ revs.no_kept_objects = 1;
+ revs.keep_pack_cache_flags |= IN_CORE_KEEP_PACKS;
+ revs.blob_objects = 1;
+ revs.tree_objects = 1;
+ revs.tag_objects = 1;
+ revs.ignore_missing_links = 1;
+
+ while (strbuf_getline(&buf, stdin) != EOF) {
+ if (!buf.len)
+ continue;
+
+ if (*buf.buf == '^')
+ string_list_append(&exclude_packs, buf.buf + 1);
+ else
+ string_list_append(&include_packs, buf.buf);
+
+ strbuf_reset(&buf);
+ }
+
+ string_list_sort(&include_packs);
+ string_list_sort(&exclude_packs);
+
+ for (p = get_all_packs(the_repository); p; p = p->next) {
+ const char *pack_name = pack_basename(p);
+
+ item = string_list_lookup(&include_packs, pack_name);
+ if (!item)
+ item = string_list_lookup(&exclude_packs, pack_name);
+
+ if (item)
+ item->util = p;
+ }
+
+ /*
+ * 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.
+ */
+ for_each_string_list_item(item, &exclude_packs) {
+ struct packed_git *p = item->util;
+ if (!p)
+ die(_("could not find pack '%s'"), item->string);
+ p->pack_keep_in_core = 1;
+ }
+
+ /*
+ * Order packs by ascending mtime; use QSORT directly to access the
+ * string_list_item's ->util pointer, which string_list_sort() does not
+ * provide.
+ */
+ QSORT(include_packs.items, include_packs.nr, pack_mtime_cmp);
+
+ for_each_string_list_item(item, &include_packs) {
+ struct packed_git *p = item->util;
+ if (!p)
+ die(_("could not find pack '%s'"), item->string);
+ for_each_object_in_pack(p,
+ add_object_entry_from_pack,
+ &revs,
+ FOR_EACH_OBJECT_PACK_ORDER);
+ }
+
+ if (prepare_revision_walk(&revs))
+ die(_("revision walk setup failed"));
+ traverse_commit_list(&revs,
+ show_commit_pack_hint,
+ show_object_pack_hint,
+ NULL);
+
+ trace2_data_intmax("pack-objects", the_repository, "stdin_packs_found",
+ stdin_packs_found_nr);
+ trace2_data_intmax("pack-objects", the_repository, "stdin_packs_hints",
+ stdin_packs_hints_nr);
+
+ strbuf_release(&buf);
+ string_list_clear(&include_packs, 0);
+ string_list_clear(&exclude_packs, 0);
+}
+
static void read_object_list_from_stdin(void)
{
char line[GIT_MAX_HEXSZ + 1 + PATH_MAX + 2];
@@ -3176,7 +3546,7 @@ static void add_objects_in_unpacked_packs(void)
for (i = 0; i < p->num_objects; i++) {
nth_packed_object_id(&oid, p, i);
- o = lookup_unknown_object(&oid);
+ o = lookup_unknown_object(the_repository, &oid);
if (!(o->flags & OBJECT_ADDED))
mark_in_pack_object(o, p, &in_pack);
o->flags |= OBJECT_ADDED;
@@ -3269,6 +3639,7 @@ static void loosen_unused_packed_objects(void)
{
struct packed_git *p;
uint32_t i;
+ uint32_t loosened_objects_nr = 0;
struct object_id oid;
for (p = get_all_packs(the_repository); p; p = p->next) {
@@ -3282,11 +3653,16 @@ static void loosen_unused_packed_objects(void)
nth_packed_object_id(&oid, p, i);
if (!packlist_find(&to_pack, &oid) &&
!has_sha1_pack_kept_or_nonlocal(&oid) &&
- !loosened_object_can_be_discarded(&oid, p->mtime))
+ !loosened_object_can_be_discarded(&oid, p->mtime)) {
if (force_object_loose(&oid, p->mtime))
die(_("unable to force loose object"));
+ loosened_objects_nr++;
+ }
}
}
+
+ trace2_data_intmax("pack-objects", the_repository,
+ "loosen_unused_packed_objects/loosened", loosened_objects_nr);
}
/*
@@ -3306,7 +3682,7 @@ static int pack_options_allow_reuse(void)
static int get_object_list_from_bitmap(struct rev_info *revs)
{
- if (!(bitmap_git = prepare_bitmap_walk(revs, &filter_options)))
+ if (!(bitmap_git = prepare_bitmap_walk(revs, &filter_options, 0)))
return -1;
if (pack_options_allow_reuse() &&
@@ -3317,7 +3693,8 @@ static int get_object_list_from_bitmap(struct rev_info *revs)
&reuse_packfile_bitmap)) {
assert(reuse_packfile_objects);
nr_result += reuse_packfile_objects;
- display_progress(progress_state, nr_result);
+ nr_seen += reuse_packfile_objects;
+ display_progress(progress_state, nr_seen);
}
traverse_bitmap_commit_list(bitmap_git, revs,
@@ -3337,6 +3714,37 @@ static void record_recent_commit(struct commit *commit, void *data)
oid_array_append(&recent_objects, &commit->object.oid);
}
+static int mark_bitmap_preferred_tip(const char *refname,
+ const struct object_id *oid, int flags,
+ void *_data)
+{
+ struct object_id peeled;
+ struct object *object;
+
+ if (!peel_iterated_oid(oid, &peeled))
+ oid = &peeled;
+
+ object = parse_object_or_die(oid, refname);
+ if (object->type == OBJ_COMMIT)
+ object->flags |= NEEDS_BITMAP;
+
+ return 0;
+}
+
+static void mark_bitmap_preferred_tips(void)
+{
+ struct string_list_item *item;
+ const struct string_list *preferred_tips;
+
+ preferred_tips = bitmap_preferred_tips(the_repository);
+ if (!preferred_tips)
+ return;
+
+ for_each_string_list_item(item, preferred_tips) {
+ for_each_ref_in(item->string, mark_bitmap_preferred_tip, NULL);
+ }
+}
+
static void get_object_list(int ac, const char **av)
{
struct rev_info revs;
@@ -3391,6 +3799,9 @@ static void get_object_list(int ac, const char **av)
if (use_delta_islands)
load_delta_islands(the_repository, progress);
+ if (write_bitmap_index)
+ mark_bitmap_preferred_tips();
+
if (prepare_revision_walk(&revs))
die(_("revision walk setup failed"));
mark_edges_uninteresting(&revs, show_edge, sparse);
@@ -3489,6 +3900,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
struct strvec rp = STRVEC_INIT;
int rev_list_unpacked = 0, rev_list_all = 0, rev_list_reflog = 0;
int rev_list_index = 0;
+ int stdin_packs = 0;
struct string_list keep_pack_list = STRING_LIST_INIT_NODUP;
struct option pack_objects_options[] = {
OPT_SET_INT('q', "quiet", &progress,
@@ -3539,6 +3951,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
OPT_SET_INT_F(0, "indexed-objects", &rev_list_index,
N_("include objects referred to by the index"),
1, PARSE_OPT_NONEG),
+ OPT_BOOL(0, "stdin-packs", &stdin_packs,
+ N_("read packs from stdin")),
OPT_BOOL(0, "stdout", &pack_to_stdout,
N_("output pack to stdout")),
OPT_BOOL(0, "include-tag", &include_tag,
@@ -3613,6 +4027,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
if (pack_to_stdout != !base_name || argc)
usage_with_options(pack_usage, pack_objects_options);
+ if (depth < 0)
+ depth = 0;
if (depth >= (1 << OE_DEPTH_BITS)) {
warning(_("delta chain depth %d is too deep, forcing %d"),
depth, (1 << OE_DEPTH_BITS) - 1);
@@ -3623,6 +4039,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
(1U << OE_Z_DELTA_BITS) - 1);
cache_max_small_delta_size = (1U << OE_Z_DELTA_BITS) - 1;
}
+ if (window < 0)
+ window = 0;
strvec_push(&rp, "pack-objects");
if (thin) {
@@ -3645,7 +4063,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
use_internal_rev_list = 1;
strvec_push(&rp, "--indexed-objects");
}
- if (rev_list_unpacked) {
+ if (rev_list_unpacked && !stdin_packs) {
use_internal_rev_list = 1;
strvec_push(&rp, "--unpacked");
}
@@ -3690,8 +4108,13 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
if (filter_options.choice) {
if (!pack_to_stdout)
die(_("cannot use --filter without --stdout"));
+ if (stdin_packs)
+ die(_("cannot use --filter with --stdin-packs"));
}
+ if (stdin_packs && use_internal_rev_list)
+ die(_("cannot use internal rev list with --stdin-packs"));
+
/*
* "soft" reasons not to use bitmaps - for on-disk repack by default we want
*
@@ -3750,7 +4173,13 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
if (progress)
progress_state = start_progress(_("Enumerating objects"), 0);
- if (!use_internal_rev_list)
+ if (stdin_packs) {
+ /* avoids adding objects in excluded packs */
+ ignore_packed_keep_in_core = 1;
+ read_packs_list_from_stdin();
+ if (rev_list_unpacked)
+ add_unreachable_loose_objects();
+ } else if (!use_internal_rev_list)
read_object_list_from_stdin();
else {
get_object_list(rp.nr, rp.v);
diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c
index 6e115a811a..8bf5c0acad 100644
--- a/builtin/pack-redundant.c
+++ b/builtin/pack-redundant.c
@@ -20,7 +20,7 @@ static int load_all_packs, verbose, alt_odb;
struct llist_item {
struct llist_item *next;
- const struct object_id *oid;
+ struct object_id oid;
};
static struct llist {
struct llist_item *front;
@@ -95,10 +95,10 @@ static struct llist * llist_copy(struct llist *list)
static inline struct llist_item *llist_insert(struct llist *list,
struct llist_item *after,
- const struct object_id *oid)
+ const unsigned char *oid)
{
struct llist_item *new_item = llist_item_get();
- new_item->oid = oid;
+ oidread(&new_item->oid, oid);
new_item->next = NULL;
if (after != NULL) {
@@ -118,7 +118,7 @@ static inline struct llist_item *llist_insert(struct llist *list,
}
static inline struct llist_item *llist_insert_back(struct llist *list,
- const struct object_id *oid)
+ const unsigned char *oid)
{
return llist_insert(list, list->back, oid);
}
@@ -130,9 +130,9 @@ static inline struct llist_item *llist_insert_sorted_unique(struct llist *list,
l = (hint == NULL) ? list->front : hint;
while (l) {
- int cmp = oidcmp(l->oid, oid);
+ int cmp = oidcmp(&l->oid, oid);
if (cmp > 0) { /* we insert before this entry */
- return llist_insert(list, prev, oid);
+ return llist_insert(list, prev, oid->hash);
}
if (!cmp) { /* already exists */
return l;
@@ -141,11 +141,11 @@ static inline struct llist_item *llist_insert_sorted_unique(struct llist *list,
l = l->next;
}
/* insert at the end */
- return llist_insert_back(list, oid);
+ return llist_insert_back(list, oid->hash);
}
/* returns a pointer to an item in front of sha1 */
-static inline struct llist_item * llist_sorted_remove(struct llist *list, const struct object_id *oid, struct llist_item *hint)
+static inline struct llist_item * llist_sorted_remove(struct llist *list, const unsigned char *oid, struct llist_item *hint)
{
struct llist_item *prev, *l;
@@ -153,7 +153,7 @@ redo_from_start:
l = (hint == NULL) ? list->front : hint;
prev = NULL;
while (l) {
- const int cmp = oidcmp(l->oid, oid);
+ const int cmp = hashcmp(l->oid.hash, oid);
if (cmp > 0) /* not in list, since sorted */
return prev;
if (!cmp) { /* found */
@@ -188,7 +188,7 @@ static void llist_sorted_difference_inplace(struct llist *A,
b = B->front;
while (b) {
- hint = llist_sorted_remove(A, b->oid, hint);
+ hint = llist_sorted_remove(A, b->oid.hash, hint);
b = b->next;
}
}
@@ -260,10 +260,10 @@ static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2)
/* cmp ~ p1 - p2 */
if (cmp == 0) {
p1_hint = llist_sorted_remove(p1->unique_objects,
- (const struct object_id *)(p1_base + p1_off),
+ p1_base + p1_off,
p1_hint);
p2_hint = llist_sorted_remove(p2->unique_objects,
- (const struct object_id *)(p1_base + p1_off),
+ p1_base + p1_off,
p2_hint);
p1_off += p1_step;
p2_off += p2_step;
@@ -373,7 +373,7 @@ static void sort_pack_list(struct pack_list **pl)
return;
/* prepare an array of packed_list for easier sorting */
- ary = xcalloc(n, sizeof(struct pack_list *));
+ CALLOC_ARRAY(ary, n);
for (n = 0, p = *pl; p; p = p->next)
ary[n++] = p;
@@ -455,7 +455,7 @@ static void load_all_objects(void)
l = pl->remaining_objects->front;
while (l) {
hint = llist_insert_sorted_unique(all_objects,
- l->oid, hint);
+ &l->oid, hint);
l = l->next;
}
pl = pl->next;
@@ -521,7 +521,7 @@ static struct pack_list * add_pack(struct packed_git *p)
base += 256 * 4 + ((p->index_version < 2) ? 4 : 8);
step = the_hash_algo->rawsz + ((p->index_version < 2) ? 4 : 0);
while (off < p->num_objects * step) {
- llist_insert_back(l.remaining_objects, (const struct object_id *)(base + off));
+ llist_insert_back(l.remaining_objects, base + off);
off += step;
}
l.all_objects_size = l.remaining_objects->size;
diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index 78bc9fa770..50318849d6 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -25,7 +25,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
struct option range_diff_options[] = {
OPT_INTEGER(0, "creation-factor",
&range_diff_opts.creation_factor,
- N_("Percentage by which creation is weighted")),
+ N_("percentage by which creation is weighted")),
OPT_BOOL(0, "no-dual-color", &simple_color,
N_("use simple diff colors")),
OPT_PASSTHRU_ARGV(0, "notes", &other_arg,
diff --git a/builtin/rebase.c b/builtin/rebase.c
index de400f9a19..12f093121d 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -100,7 +100,6 @@ struct rebase_options {
char *strategy, *strategy_opts;
struct strbuf git_format_patch_opt;
int reschedule_failed_exec;
- int use_legacy_rebase;
int reapply_cherry_picks;
int fork_point;
};
@@ -486,7 +485,7 @@ static const char * const builtin_rebase_interactive_usage[] = {
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;
+ 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"),
@@ -739,6 +738,7 @@ static int finish_rebase(struct rebase_options *opts)
int ret = 0;
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);
/*
@@ -1102,11 +1102,6 @@ static int rebase_config(const char *var, const char *value, void *data)
return 0;
}
- if (!strcmp(var, "rebase.usebuiltin")) {
- opts->use_legacy_rebase = !git_config_bool(var, value);
- return 0;
- }
-
if (!strcmp(var, "rebase.backend")) {
return git_config_string(&opts->default_backend, var, value);
}
@@ -1145,7 +1140,7 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,
merge_bases = get_merge_bases(onto, head);
if (!merge_bases || merge_bases->next) {
- oidcpy(merge_base, &null_oid);
+ oidcpy(merge_base, null_oid());
goto done;
}
@@ -1441,11 +1436,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
gpg_sign = options.gpg_sign_opt ? "" : NULL;
FREE_AND_NULL(options.gpg_sign_opt);
- if (options.use_legacy_rebase ||
- !git_env_bool("GIT_TEST_REBASE_USE_BUILTIN", -1))
- warning(_("the rebase.useBuiltin support has been removed!\n"
- "See its entry in 'git help config' for details."));
-
strbuf_reset(&buf);
strbuf_addf(&buf, "%s/applying", apply_dir());
if(file_exists(buf.buf))
@@ -2119,6 +2109,7 @@ cleanup:
free(options.head_name);
free(options.gpg_sign_opt);
free(options.cmd);
+ strbuf_release(&options.git_format_patch_opt);
free(squash_onto_name);
return ret;
}
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index d26040c477..a34742513a 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -329,7 +329,7 @@ static void write_head_info(void)
for_each_alternate_ref(show_one_alternate_ref, &seen);
oidset_clear(&seen);
if (!sent_capabilities)
- show_ref("capabilities^{}", &null_oid);
+ show_ref("capabilities^{}", null_oid());
advertise_shallow_grafts(1);
@@ -358,7 +358,7 @@ static void proc_receive_ref_append(const char *prefix)
char *p;
int len;
- ref_pattern = xcalloc(1, sizeof(struct proc_receive_ref));
+ CALLOC_ARRAY(ref_pattern, 1);
p = strchr(prefix, ':');
if (p) {
while (prefix < p) {
@@ -1024,7 +1024,7 @@ static int read_proc_receive_report(struct packet_reader *reader,
}
if (new_report) {
if (!hint->report) {
- hint->report = xcalloc(1, sizeof(struct ref_push_report));
+ CALLOC_ARRAY(hint->report, 1);
report = hint->report;
} else {
report = hint->report;
@@ -2313,11 +2313,9 @@ static void prepare_shallow_update(struct shallow_info *si)
ALLOC_ARRAY(si->used_shallow, si->shallow->nr);
assign_shallow_commits_to_refs(si, si->used_shallow, NULL);
- si->need_reachability_test =
- xcalloc(si->shallow->nr, sizeof(*si->need_reachability_test));
- si->reachable =
- xcalloc(si->shallow->nr, sizeof(*si->reachable));
- si->shallow_ref = xcalloc(si->ref->nr, sizeof(*si->shallow_ref));
+ CALLOC_ARRAY(si->need_reachability_test, si->shallow->nr);
+ CALLOC_ARRAY(si->reachable, si->shallow->nr);
+ CALLOC_ARRAY(si->shallow_ref, si->ref->nr);
for (i = 0; i < si->nr_ours; i++)
si->need_reachability_test[si->ours[i]] = 1;
diff --git a/builtin/remote.c b/builtin/remote.c
index d11a5589e4..7f88e6ce9d 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -221,7 +221,7 @@ static int add(int argc, const char **argv)
if (fetch_tags != TAGS_DEFAULT) {
strbuf_reset(&buf);
- strbuf_addf(&buf, "remote.%s.tagopt", name);
+ strbuf_addf(&buf, "remote.%s.tagOpt", name);
git_config_set(buf.buf,
fetch_tags == TAGS_SET ? "--tags" : "--no-tags");
}
@@ -746,7 +746,7 @@ static int mv(int argc, const char **argv)
}
if (info->push_remote_name && !strcmp(info->push_remote_name, rename.old_name)) {
strbuf_reset(&buf);
- strbuf_addf(&buf, "branch.%s.pushremote", item->string);
+ strbuf_addf(&buf, "branch.%s.pushRemote", item->string);
git_config_set(buf.buf, rename.new_name);
}
}
@@ -938,9 +938,6 @@ static int get_remote_ref_states(const char *name,
struct ref_states *states,
int query)
{
- struct transport *transport;
- const struct ref *remote_refs;
-
states->remote = remote_get(name);
if (!states->remote)
return error(_("No such remote: '%s'"), name);
@@ -948,10 +945,12 @@ static int get_remote_ref_states(const char *name,
read_branches();
if (query) {
+ struct transport *transport;
+ const struct ref *remote_refs;
+
transport = transport_get(states->remote, states->remote->url_nr > 0 ?
states->remote->url[0] : NULL);
remote_refs = transport_get_remote_refs(transport, NULL);
- transport_disconnect(transport);
states->queried = 1;
if (query & GET_REF_STATES)
@@ -960,6 +959,7 @@ static int get_remote_ref_states(const char *name,
get_head_names(remote_refs, states);
if (query & GET_PUSH_REF_STATES)
get_push_ref_states(remote_refs, states);
+ transport_disconnect(transport);
} else {
for_each_ref(append_ref_to_tracked_list, states);
string_list_sort(&states->tracked);
diff --git a/builtin/repack.c b/builtin/repack.c
index 01440de2d5..5f9bc74adc 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -20,7 +20,7 @@ static int delta_base_offset = 1;
static int pack_kept_objects = -1;
static int write_bitmaps = -1;
static int use_delta_islands;
-static char *packdir, *packtmp;
+static char *packdir, *packtmp_name, *packtmp;
static const char *const git_repack_usage[] = {
N_("git repack [<options>]"),
@@ -297,6 +297,142 @@ static void repack_promisor_objects(const struct pack_objects_args *args,
#define ALL_INTO_ONE 1
#define LOOSEN_UNREACHABLE 2
+struct pack_geometry {
+ struct packed_git **pack;
+ uint32_t pack_nr, pack_alloc;
+ uint32_t split;
+};
+
+static uint32_t geometry_pack_weight(struct packed_git *p)
+{
+ if (open_pack_index(p))
+ die(_("cannot open index for %s"), p->pack_name);
+ return p->num_objects;
+}
+
+static int geometry_cmp(const void *va, const void *vb)
+{
+ uint32_t aw = geometry_pack_weight(*(struct packed_git **)va),
+ bw = geometry_pack_weight(*(struct packed_git **)vb);
+
+ if (aw < bw)
+ return -1;
+ if (aw > bw)
+ return 1;
+ return 0;
+}
+
+static void init_pack_geometry(struct pack_geometry **geometry_p)
+{
+ struct packed_git *p;
+ struct pack_geometry *geometry;
+
+ *geometry_p = xcalloc(1, sizeof(struct pack_geometry));
+ geometry = *geometry_p;
+
+ for (p = get_all_packs(the_repository); p; p = p->next) {
+ if (!pack_kept_objects && p->pack_keep)
+ continue;
+
+ ALLOC_GROW(geometry->pack,
+ geometry->pack_nr + 1,
+ geometry->pack_alloc);
+
+ geometry->pack[geometry->pack_nr] = p;
+ geometry->pack_nr++;
+ }
+
+ QSORT(geometry->pack, geometry->pack_nr, geometry_cmp);
+}
+
+static void split_pack_geometry(struct pack_geometry *geometry, int factor)
+{
+ uint32_t i;
+ uint32_t split;
+ off_t total_size = 0;
+
+ if (!geometry->pack_nr) {
+ geometry->split = geometry->pack_nr;
+ return;
+ }
+
+ /*
+ * First, count the number of packs (in descending order of size) which
+ * already form a geometric progression.
+ */
+ for (i = geometry->pack_nr - 1; i > 0; i--) {
+ struct packed_git *ours = geometry->pack[i];
+ struct packed_git *prev = geometry->pack[i - 1];
+
+ if (unsigned_mult_overflows(factor, geometry_pack_weight(prev)))
+ die(_("pack %s too large to consider in geometric "
+ "progression"),
+ prev->pack_name);
+
+ if (geometry_pack_weight(ours) < factor * geometry_pack_weight(prev))
+ break;
+ }
+
+ split = i;
+
+ if (split) {
+ /*
+ * Move the split one to the right, since the top element in the
+ * last-compared pair can't be in the progression. Only do this
+ * when we split in the middle of the array (otherwise if we got
+ * to the end, then the split is in the right place).
+ */
+ split++;
+ }
+
+ /*
+ * Then, anything to the left of 'split' must be in a new pack. But,
+ * creating that new pack may cause packs in the heavy half to no longer
+ * form a geometric progression.
+ *
+ * Compute an expected size of the new pack, and then determine how many
+ * packs in the heavy half need to be joined into it (if any) to restore
+ * the geometric progression.
+ */
+ for (i = 0; i < split; i++) {
+ struct packed_git *p = geometry->pack[i];
+
+ if (unsigned_add_overflows(total_size, geometry_pack_weight(p)))
+ die(_("pack %s too large to roll up"), p->pack_name);
+ total_size += geometry_pack_weight(p);
+ }
+ for (i = split; i < geometry->pack_nr; i++) {
+ struct packed_git *ours = geometry->pack[i];
+
+ if (unsigned_mult_overflows(factor, total_size))
+ die(_("pack %s too large to roll up"), ours->pack_name);
+
+ if (geometry_pack_weight(ours) < factor * total_size) {
+ if (unsigned_add_overflows(total_size,
+ geometry_pack_weight(ours)))
+ die(_("pack %s too large to roll up"),
+ ours->pack_name);
+
+ split++;
+ total_size += geometry_pack_weight(ours);
+ } else
+ break;
+ }
+
+ geometry->split = split;
+}
+
+static void clear_pack_geometry(struct pack_geometry *geometry)
+{
+ if (!geometry)
+ return;
+
+ free(geometry->pack);
+ geometry->pack_nr = 0;
+ geometry->pack_alloc = 0;
+ geometry->split = 0;
+}
+
int cmd_repack(int argc, const char **argv, const char *prefix)
{
struct child_process cmd = CHILD_PROCESS_INIT;
@@ -304,6 +440,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
struct string_list names = STRING_LIST_INIT_DUP;
struct string_list rollback = STRING_LIST_INIT_NODUP;
struct string_list existing_packs = STRING_LIST_INIT_DUP;
+ struct pack_geometry *geometry = NULL;
struct strbuf line = STRBUF_INIT;
int i, ext, ret;
FILE *out;
@@ -316,6 +453,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
struct string_list keep_pack_list = STRING_LIST_INIT_NODUP;
int no_update_server_info = 0;
struct pack_objects_args po_args = {NULL};
+ int geometric_factor = 0;
struct option builtin_repack_options[] = {
OPT_BIT('a', NULL, &pack_everything,
@@ -356,6 +494,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
N_("repack objects in packs marked with .keep")),
OPT_STRING_LIST(0, "keep-pack", &keep_pack_list, N_("name"),
N_("do not repack this pack")),
+ OPT_INTEGER('g', "geometric", &geometric_factor,
+ N_("find a geometric progression with factor <N>")),
OPT_END()
};
@@ -382,8 +522,16 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
if (write_bitmaps && !(pack_everything & ALL_INTO_ONE))
die(_(incremental_bitmap_conflict_error));
+ if (geometric_factor) {
+ if (pack_everything)
+ die(_("--geometric is incompatible with -A, -a"));
+ init_pack_geometry(&geometry);
+ split_pack_geometry(geometry, geometric_factor);
+ }
+
packdir = mkpathdup("%s/pack", get_object_directory());
- packtmp = mkpathdup("%s/.tmp-%d-pack", packdir, (int)getpid());
+ packtmp_name = xstrfmt(".tmp-%d-pack", (int)getpid());
+ packtmp = mkpathdup("%s/%s", packdir, packtmp_name);
sigchain_push_common(remove_pack_on_signal);
@@ -396,9 +544,21 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
strvec_pushf(&cmd.args, "--keep-pack=%s",
keep_pack_list.items[i].string);
strvec_push(&cmd.args, "--non-empty");
- strvec_push(&cmd.args, "--all");
- strvec_push(&cmd.args, "--reflog");
- strvec_push(&cmd.args, "--indexed-objects");
+ if (!geometry) {
+ /*
+ * We need to grab all reachable objects, including those that
+ * are reachable from reflogs and the index.
+ *
+ * When repacking into a geometric progression of packs,
+ * however, we ask 'git pack-objects --stdin-packs', and it is
+ * not about packing objects based on reachability but about
+ * repacking all the objects in specified packs and loose ones
+ * (indeed, --stdin-packs is incompatible with these options).
+ */
+ strvec_push(&cmd.args, "--all");
+ strvec_push(&cmd.args, "--reflog");
+ strvec_push(&cmd.args, "--indexed-objects");
+ }
if (has_promisor_remote())
strvec_push(&cmd.args, "--exclude-promisor-objects");
if (write_bitmaps > 0)
@@ -414,6 +574,10 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
repack_promisor_objects(&po_args, &names);
if (existing_packs.nr && delete_redundant) {
+ for_each_string_list_item(item, &names) {
+ strvec_pushf(&cmd.args, "--keep-pack=%s-%s.pack",
+ packtmp_name, item->string);
+ }
if (unpack_unreachable) {
strvec_pushf(&cmd.args,
"--unpack-unreachable=%s",
@@ -429,17 +593,37 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
strvec_push(&cmd.env_array, "GIT_REF_PARANOIA=1");
}
}
+ } else if (geometry) {
+ strvec_push(&cmd.args, "--stdin-packs");
+ strvec_push(&cmd.args, "--unpacked");
} else {
strvec_push(&cmd.args, "--unpacked");
strvec_push(&cmd.args, "--incremental");
}
- cmd.no_stdin = 1;
+ if (geometry)
+ cmd.in = -1;
+ else
+ cmd.no_stdin = 1;
ret = start_command(&cmd);
if (ret)
return ret;
+ if (geometry) {
+ FILE *in = xfdopen(cmd.in, "w");
+ /*
+ * The resulting pack should contain all objects in packs that
+ * are going to be rolled up, but exclude objects in packs which
+ * are being left alone.
+ */
+ for (i = 0; i < geometry->split; i++)
+ fprintf(in, "%s\n", pack_basename(geometry->pack[i]));
+ for (i = geometry->split; i < geometry->pack_nr; i++)
+ fprintf(in, "^%s\n", pack_basename(geometry->pack[i]));
+ fclose(in);
+ }
+
out = xfdopen(cmd.out, "r");
while (strbuf_getline_lf(&line, out) != EOF) {
if (line.len != the_hash_algo->hexsz)
@@ -507,6 +691,25 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
if (!string_list_has_string(&names, sha1))
remove_redundant_pack(packdir, item->string);
}
+
+ if (geometry) {
+ struct strbuf buf = STRBUF_INIT;
+
+ uint32_t i;
+ for (i = 0; i < geometry->split; i++) {
+ struct packed_git *p = geometry->pack[i];
+ if (string_list_has_string(&names,
+ hash_to_hex(p->hash)))
+ continue;
+
+ strbuf_reset(&buf);
+ strbuf_addstr(&buf, pack_basename(p));
+ strbuf_strip_suffix(&buf, ".pack");
+
+ remove_redundant_pack(packdir, buf.buf);
+ }
+ strbuf_release(&buf);
+ }
if (!po_args.quiet && isatty(2))
opts |= PRUNE_PACKED_VERBOSE;
prune_packed_objects(opts);
@@ -523,11 +726,12 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
remove_temporary_files();
if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0))
- write_midx_file(get_object_directory(), 0);
+ write_midx_file(get_object_directory(), NULL, 0);
string_list_clear(&names, 0);
string_list_clear(&rollback, 0);
string_list_clear(&existing_packs, 0);
+ clear_pack_geometry(geometry);
strbuf_release(&line);
return 0;
diff --git a/builtin/reset.c b/builtin/reset.c
index c635b062c3..43e855cb88 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -425,7 +425,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
dwim_ref(rev, strlen(rev), &dummy, &ref, 0);
if (ref && !starts_with(ref, "refs/"))
- ref = NULL;
+ FREE_AND_NULL(ref);
err = reset_index(ref, &oid, reset_type, quiet);
if (reset_type == KEEP && !err)
diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index b4d8ea0a35..7677b1af5a 100644
--- a/builtin/rev-list.c
+++ b/builtin/rev-list.c
@@ -398,7 +398,8 @@ static inline int parse_missing_action_value(const char *value)
}
static int try_bitmap_count(struct rev_info *revs,
- struct list_objects_filter_options *filter)
+ struct list_objects_filter_options *filter,
+ int filter_provided_objects)
{
uint32_t commit_count = 0,
tag_count = 0,
@@ -433,7 +434,7 @@ static int try_bitmap_count(struct rev_info *revs,
*/
max_count = revs->max_count;
- bitmap_git = prepare_bitmap_walk(revs, filter);
+ bitmap_git = prepare_bitmap_walk(revs, filter, filter_provided_objects);
if (!bitmap_git)
return -1;
@@ -450,7 +451,8 @@ static int try_bitmap_count(struct rev_info *revs,
}
static int try_bitmap_traversal(struct rev_info *revs,
- struct list_objects_filter_options *filter)
+ struct list_objects_filter_options *filter,
+ int filter_provided_objects)
{
struct bitmap_index *bitmap_git;
@@ -461,7 +463,7 @@ static int try_bitmap_traversal(struct rev_info *revs,
if (revs->max_count >= 0)
return -1;
- bitmap_git = prepare_bitmap_walk(revs, filter);
+ bitmap_git = prepare_bitmap_walk(revs, filter, filter_provided_objects);
if (!bitmap_git)
return -1;
@@ -471,14 +473,15 @@ static int try_bitmap_traversal(struct rev_info *revs,
}
static int try_bitmap_disk_usage(struct rev_info *revs,
- struct list_objects_filter_options *filter)
+ struct list_objects_filter_options *filter,
+ int filter_provided_objects)
{
struct bitmap_index *bitmap_git;
if (!show_disk_usage)
return -1;
- bitmap_git = prepare_bitmap_walk(revs, filter);
+ bitmap_git = prepare_bitmap_walk(revs, filter, filter_provided_objects);
if (!bitmap_git)
return -1;
@@ -499,6 +502,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
int bisect_show_vars = 0;
int bisect_find_all = 0;
int use_bitmap_index = 0;
+ int filter_provided_objects = 0;
const char *show_progress = NULL;
if (argc == 2 && !strcmp(argv[1], "-h"))
@@ -599,6 +603,10 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
list_objects_filter_set_no_filter(&filter_options);
continue;
}
+ if (!strcmp(arg, "--filter-provided-objects")) {
+ filter_provided_objects = 1;
+ continue;
+ }
if (!strcmp(arg, "--filter-print-omitted")) {
arg_print_omitted = 1;
continue;
@@ -665,11 +673,11 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
progress = start_delayed_progress(show_progress, 0);
if (use_bitmap_index) {
- if (!try_bitmap_count(&revs, &filter_options))
+ if (!try_bitmap_count(&revs, &filter_options, filter_provided_objects))
return 0;
- if (!try_bitmap_disk_usage(&revs, &filter_options))
+ if (!try_bitmap_disk_usage(&revs, &filter_options, filter_provided_objects))
return 0;
- if (!try_bitmap_traversal(&revs, &filter_options))
+ if (!try_bitmap_traversal(&revs, &filter_options, filter_provided_objects))
return 0;
}
@@ -694,6 +702,16 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
return show_bisect_vars(&info, reaches, all);
}
+ if (filter_provided_objects) {
+ struct commit_list *c;
+ for (i = 0; i < revs.pending.nr; i++) {
+ struct object_array_entry *pending = revs.pending.objects + i;
+ pending->item->flags |= NOT_USER_GIVEN;
+ }
+ for (c = revs.commits; c; c = c->next)
+ c->item->object.flags |= NOT_USER_GIVEN;
+ }
+
if (arg_print_omitted)
oidset_init(&omitted_objects, DEFAULT_OIDSET_SIZE);
if (arg_missing_action == MA_PRINT)
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 85bad9052e..7af8dab8bc 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -759,6 +759,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
continue;
}
if (opt_with_value(arg, "--path-format", &arg)) {
+ if (!arg)
+ die("--path-format requires an argument");
if (!strcmp(arg, "absolute")) {
format = FORMAT_CANONICAL;
} else if (!strcmp(arg, "relative")) {
diff --git a/builtin/revert.c b/builtin/revert.c
index 314a86c562..237f2f18d4 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -182,7 +182,7 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)
"--signoff", opts->signoff,
"--no-commit", opts->no_commit,
"-x", opts->record_origin,
- "--edit", opts->edit,
+ "--edit", opts->edit > 0,
NULL);
if (cmd) {
@@ -230,8 +230,6 @@ int cmd_revert(int argc, const char **argv, const char *prefix)
struct replay_opts opts = REPLAY_OPTS_INIT;
int res;
- if (isatty(0))
- opts.edit = 1;
opts.action = REPLAY_REVERT;
sequencer_init_config(&opts);
res = run_sequencer(argc, argv, &opts);
diff --git a/builtin/rm.c b/builtin/rm.c
index 4858631e0f..8a24c715e0 100644
--- a/builtin/rm.c
+++ b/builtin/rm.c
@@ -5,6 +5,7 @@
*/
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
+#include "advice.h"
#include "config.h"
#include "lockfile.h"
#include "dir.h"
@@ -254,7 +255,7 @@ static struct option builtin_rm_options[] = {
int cmd_rm(int argc, const char **argv, const char *prefix)
{
struct lock_file lock_file = LOCK_INIT;
- int i;
+ int i, ret = 0;
struct pathspec pathspec;
char *seen;
@@ -293,8 +294,12 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
seen = xcalloc(pathspec.nr, 1);
+ /* TODO: audit for interaction with sparse-index. */
+ 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))
+ continue;
if (!ce_path_match(&the_index, ce, &pathspec, seen))
continue;
ALLOC_GROW(list.entry, list.nr + 1, list.alloc);
@@ -308,25 +313,37 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
if (pathspec.nr) {
const char *original;
int seen_any = 0;
+ char *skip_worktree_seen = NULL;
+ struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP;
+
for (i = 0; i < pathspec.nr; i++) {
original = pathspec.items[i].original;
- if (!seen[i]) {
- if (!ignore_unmatch) {
- die(_("pathspec '%s' did not match any files"),
- original);
- }
- }
- else {
+ if (seen[i])
seen_any = 1;
- }
+ else if (ignore_unmatch)
+ continue;
+ else if (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);
+
if (!recursive && seen[i] == MATCHED_RECURSIVELY)
die(_("not removing '%s' recursively without -r"),
*original ? original : ".");
}
+ if (only_match_skip_worktree.nr) {
+ advise_on_updating_sparse_paths(&only_match_skip_worktree);
+ ret = 1;
+ }
+ free(skip_worktree_seen);
+ string_list_clear(&only_match_skip_worktree, 0);
+
if (!seen_any)
- exit(0);
+ exit(ret);
}
+ clear_pathspec(&pathspec);
+ free(seen);
if (!index_only)
submodules_absorb_gitdir_if_needed();
@@ -405,5 +422,5 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
COMMIT_LOCK | SKIP_IF_UNCHANGED))
die(_("Unable to write new index file"));
- return 0;
+ return ret;
}
diff --git a/builtin/show-index.c b/builtin/show-index.c
index 8106b03a6b..0e0b9fb95b 100644
--- a/builtin/show-index.c
+++ b/builtin/show-index.c
@@ -71,9 +71,11 @@ int cmd_show_index(int argc, const char **argv, const char *prefix)
uint32_t off;
} *entries;
ALLOC_ARRAY(entries, nr);
- for (i = 0; i < nr; i++)
+ for (i = 0; i < nr; i++) {
if (fread(entries[i].oid.hash, hashsz, 1, stdin) != 1)
die("unable to read sha1 %u/%u", i, nr);
+ entries[i].oid.algo = hash_algo_by_ptr(the_hash_algo);
+ }
for (i = 0; i < nr; i++)
if (fread(&entries[i].crc, 4, 1, stdin) != 1)
die("unable to read crc %u/%u", i, nr);
diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c
index 2306a9ad98..a4bdd7c494 100644
--- a/builtin/sparse-checkout.c
+++ b/builtin/sparse-checkout.c
@@ -14,6 +14,7 @@
#include "unpack-trees.h"
#include "wt-status.h"
#include "quote.h"
+#include "sparse-index.h"
static const char *empty_base = "";
@@ -64,7 +65,7 @@ static int sparse_checkout_list(int argc, const char **argv)
pl.use_cone_patterns = core_sparse_checkout_cone;
sparse_filename = get_sparse_checkout_filename();
- res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL);
+ res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL, 0);
free(sparse_filename);
if (res < 0) {
@@ -110,6 +111,8 @@ static int update_working_directory(struct pattern_list *pl)
if (is_index_unborn(r->index))
return UPDATE_SPARSITY_SUCCESS;
+ r->index->sparse_checkout_patterns = pl;
+
memset(&o, 0, sizeof(o));
o.verbose_update = isatty(2);
o.update = 1;
@@ -138,6 +141,7 @@ static int update_working_directory(struct pattern_list *pl)
else
rollback_lock_file(&lock_file);
+ r->index->sparse_checkout_patterns = NULL;
return result;
}
@@ -276,16 +280,20 @@ static int set_config(enum sparse_checkout_mode mode)
"core.sparseCheckoutCone",
mode == MODE_CONE_PATTERNS ? "true" : NULL);
+ if (mode == MODE_NO_PATTERNS)
+ set_sparse_index_config(the_repository, 0);
+
return 0;
}
static char const * const builtin_sparse_checkout_init_usage[] = {
- N_("git sparse-checkout init [--cone]"),
+ N_("git sparse-checkout init [--cone] [--[no-]sparse-index]"),
NULL
};
static struct sparse_checkout_init_opts {
int cone_mode;
+ int sparse_index;
} init_opts;
static int sparse_checkout_init(int argc, const char **argv)
@@ -300,11 +308,15 @@ static int sparse_checkout_init(int argc, const char **argv)
static struct option builtin_sparse_checkout_init_options[] = {
OPT_BOOL(0, "cone", &init_opts.cone_mode,
N_("initialize the sparse-checkout in cone mode")),
+ OPT_BOOL(0, "sparse-index", &init_opts.sparse_index,
+ N_("toggle the use of a sparse index")),
OPT_END(),
};
repo_read_index(the_repository);
+ init_opts.sparse_index = -1;
+
argc = parse_options(argc, argv, NULL,
builtin_sparse_checkout_init_options,
builtin_sparse_checkout_init_usage, 0);
@@ -321,12 +333,22 @@ static int sparse_checkout_init(int argc, const char **argv)
memset(&pl, 0, sizeof(pl));
sparse_filename = get_sparse_checkout_filename();
- res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL);
+ res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL, 0);
+
+ if (init_opts.sparse_index >= 0) {
+ if (set_sparse_index_config(the_repository, init_opts.sparse_index) < 0)
+ die(_("failed to modify sparse-index config"));
+
+ /* force an index rewrite */
+ repo_read_index(the_repository);
+ the_repository->index->updated_workdir = 1;
+ }
+
+ core_apply_sparse_checkout = 1;
/* If we already have a sparse-checkout file, use it. */
if (res >= 0) {
free(sparse_filename);
- core_apply_sparse_checkout = 1;
return update_working_directory(NULL);
}
@@ -348,6 +370,7 @@ static int sparse_checkout_init(int argc, const char **argv)
add_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0);
strbuf_addstr(&pattern, "!/*/");
add_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0);
+ pl.use_cone_patterns = init_opts.cone_mode;
return write_patterns_and_update(&pl);
}
@@ -483,7 +506,7 @@ static void add_patterns_cone_mode(int argc, const char **argv,
existing.use_cone_patterns = core_sparse_checkout_cone;
if (add_patterns_from_file_to_list(sparse_filename, "", 0,
- &existing, NULL))
+ &existing, NULL, 0))
die(_("unable to load existing sparse-checkout patterns"));
free(sparse_filename);
@@ -507,7 +530,7 @@ static void add_patterns_literal(int argc, const char **argv,
{
char *sparse_filename = get_sparse_checkout_filename();
if (add_patterns_from_file_to_list(sparse_filename, "", 0,
- pl, NULL))
+ pl, NULL, 0))
die(_("unable to load existing sparse-checkout patterns"));
free(sparse_filename);
add_patterns_from_input(pl, argc, argv);
@@ -517,19 +540,18 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m)
{
int result;
int changed_config = 0;
- struct pattern_list pl;
- memset(&pl, 0, sizeof(pl));
+ struct pattern_list *pl = xcalloc(1, sizeof(*pl));
switch (m) {
case ADD:
if (core_sparse_checkout_cone)
- add_patterns_cone_mode(argc, argv, &pl);
+ add_patterns_cone_mode(argc, argv, pl);
else
- add_patterns_literal(argc, argv, &pl);
+ add_patterns_literal(argc, argv, pl);
break;
case REPLACE:
- add_patterns_from_input(&pl, argc, argv);
+ add_patterns_from_input(pl, argc, argv);
break;
}
@@ -539,12 +561,13 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m)
changed_config = 1;
}
- result = write_patterns_and_update(&pl);
+ result = write_patterns_and_update(pl);
if (result && changed_config)
set_config(MODE_NO_PATTERNS);
- clear_pattern_list(&pl);
+ clear_pattern_list(pl);
+ free(pl);
return result;
}
@@ -614,6 +637,9 @@ static int sparse_checkout_disable(int argc, const char **argv)
strbuf_addstr(&match_all, "/*");
add_pattern(strbuf_detach(&match_all, NULL), empty_base, 0, &pl, 0);
+ prepare_repo_settings(the_repository);
+ the_repository->settings.sparse_index = 0;
+
if (update_working_directory(&pl))
die(_("error while refreshing working directory"));
diff --git a/builtin/stash.c b/builtin/stash.c
index ba774cce67..01066d7085 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -10,11 +10,13 @@
#include "strvec.h"
#include "run-command.h"
#include "dir.h"
+#include "entry.h"
#include "rerere.h"
#include "revision.h"
#include "log-tree.h"
#include "diffcore.h"
#include "exec-cmd.h"
+#include "entry.h"
#define INCLUDE_ALL_FILES 2
@@ -768,6 +770,7 @@ static int list_stash(int argc, const char **argv, const char *prefix)
static int show_stat = 1;
static int show_patch;
+static int show_include_untracked;
static int use_legacy_stash;
static int git_stash_config(const char *var, const char *value, void *cb)
@@ -780,6 +783,10 @@ static int git_stash_config(const char *var, const char *value, void *cb)
show_patch = git_config_bool(var, value);
return 0;
}
+ if (!strcmp(var, "stash.showincludeuntracked")) {
+ show_include_untracked = git_config_bool(var, value);
+ return 0;
+ }
if (!strcmp(var, "stash.usebuiltin")) {
use_legacy_stash = !git_config_bool(var, value);
return 0;
@@ -787,6 +794,33 @@ static int git_stash_config(const char *var, const char *value, void *cb)
return git_diff_basic_config(var, value, cb);
}
+static void diff_include_untracked(const struct stash_info *info, struct diff_options *diff_opt)
+{
+ const struct object_id *oid[] = { &info->w_commit, &info->u_tree };
+ struct tree *tree[ARRAY_SIZE(oid)];
+ struct tree_desc tree_desc[ARRAY_SIZE(oid)];
+ struct unpack_trees_options unpack_tree_opt = { 0 };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(oid); i++) {
+ tree[i] = parse_tree_indirect(oid[i]);
+ if (parse_tree(tree[i]) < 0)
+ die(_("failed to parse tree"));
+ init_tree_desc(&tree_desc[i], tree[i]->buffer, tree[i]->size);
+ }
+
+ unpack_tree_opt.head_idx = -1;
+ unpack_tree_opt.src_index = &the_index;
+ unpack_tree_opt.dst_index = &the_index;
+ unpack_tree_opt.merge = 1;
+ unpack_tree_opt.fn = stash_worktree_untracked_merge;
+
+ if (unpack_trees(ARRAY_SIZE(tree_desc), tree_desc, &unpack_tree_opt))
+ die(_("failed to unpack trees"));
+
+ do_diff_cache(&info->b_commit, diff_opt);
+}
+
static int show_stash(int argc, const char **argv, const char *prefix)
{
int i;
@@ -795,7 +829,18 @@ static int show_stash(int argc, const char **argv, const char *prefix)
struct rev_info rev;
struct strvec stash_args = STRVEC_INIT;
struct strvec revision_args = STRVEC_INIT;
+ enum {
+ UNTRACKED_NONE,
+ UNTRACKED_INCLUDE,
+ UNTRACKED_ONLY
+ } show_untracked = show_include_untracked ? UNTRACKED_INCLUDE : UNTRACKED_NONE;
struct option options[] = {
+ OPT_SET_INT('u', "include-untracked", &show_untracked,
+ N_("include untracked files in the stash"),
+ UNTRACKED_INCLUDE),
+ OPT_SET_INT_F(0, "only-untracked", &show_untracked,
+ N_("only show untracked files in the stash"),
+ UNTRACKED_ONLY, PARSE_OPT_NONEG),
OPT_END()
};
@@ -803,6 +848,10 @@ static int show_stash(int argc, const char **argv, const char *prefix)
git_config(git_diff_ui_config, NULL);
init_revisions(&rev, prefix);
+ argc = parse_options(argc, argv, prefix, options, git_stash_show_usage,
+ PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
+ PARSE_OPT_KEEP_DASHDASH);
+
strvec_push(&revision_args, argv[0]);
for (i = 1; i < argc; i++) {
if (argv[i][0] != '-')
@@ -845,7 +894,21 @@ static int show_stash(int argc, const char **argv, const char *prefix)
rev.diffopt.flags.recursive = 1;
setup_diff_pager(&rev.diffopt);
- diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt);
+ switch (show_untracked) {
+ case UNTRACKED_NONE:
+ diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt);
+ break;
+ case UNTRACKED_ONLY:
+ if (info.has_u)
+ diff_root_tree_oid(&info.u_tree, "", &rev.diffopt);
+ break;
+ case UNTRACKED_INCLUDE:
+ if (info.has_u)
+ diff_include_untracked(&info, &rev.diffopt);
+ else
+ diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt);
+ break;
+ }
log_tree_diff_flush(&rev);
free_stash_info(&info);
@@ -1350,6 +1413,8 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
int i;
char *ps_matched = xcalloc(ps->nr, 1);
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(&the_index);
for (i = 0; i < active_nr; i++)
ce_path_match(&the_index, active_cache[i], ps,
ps_matched);
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 9d505a6329..d55f6262e9 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -426,7 +426,8 @@ static int module_list(int argc, const char **argv, const char *prefix)
const struct cache_entry *ce = list.entries[i];
if (ce_stage(ce))
- printf("%06o %s U\t", ce->ce_mode, oid_to_hex(&null_oid));
+ printf("%06o %s U\t", ce->ce_mode,
+ oid_to_hex(null_oid()));
else
printf("%06o %s %d\t", ce->ce_mode,
oid_to_hex(&ce->oid), ce_stage(ce));
@@ -466,7 +467,7 @@ static void runcommand_in_submodule_cb(const struct cache_entry *list_item,
displaypath = get_submodule_displaypath(path, info->prefix);
- sub = submodule_from_path(the_repository, &null_oid, path);
+ sub = submodule_from_path(the_repository, null_oid(), path);
if (!sub)
die(_("No url found for submodule path '%s' in .gitmodules"),
@@ -623,7 +624,7 @@ static void init_submodule(const char *path, const char *prefix,
displaypath = get_submodule_displaypath(path, prefix);
- sub = submodule_from_path(the_repository, &null_oid, path);
+ sub = submodule_from_path(the_repository, null_oid(), path);
if (!sub)
die(_("No url found for submodule path '%s' in .gitmodules"),
@@ -783,14 +784,14 @@ static void status_submodule(const char *path, const struct object_id *ce_oid,
struct strbuf buf = STRBUF_INIT;
const char *git_dir;
- if (!submodule_from_path(the_repository, &null_oid, path))
+ if (!submodule_from_path(the_repository, null_oid(), path))
die(_("no submodule mapping found in .gitmodules for path '%s'"),
path);
displaypath = get_submodule_displaypath(path, prefix);
if ((CE_STAGEMASK & ce_flags) >> CE_STAGESHIFT) {
- print_status(flags, 'U', path, &null_oid, displaypath);
+ print_status(flags, 'U', path, null_oid(), displaypath);
goto cleanup;
}
@@ -916,7 +917,7 @@ static int module_name(int argc, const char **argv, const char *prefix)
if (argc != 2)
usage(_("git submodule--helper name <path>"));
- sub = submodule_from_path(the_repository, &null_oid, argv[1]);
+ sub = submodule_from_path(the_repository, null_oid(), argv[1]);
if (!sub)
die(_("no submodule mapping found in .gitmodules for path '%s'"),
@@ -1040,7 +1041,7 @@ static void generate_submodule_summary(struct summary_cb *info,
char *errmsg = NULL;
int total_commits = -1;
- if (!info->cached && oideq(&p->oid_dst, &null_oid)) {
+ if (!info->cached && oideq(&p->oid_dst, null_oid())) {
if (S_ISGITLINK(p->mod_dst)) {
struct ref_store *refs = get_submodule_ref_store(p->sm_path);
if (refs)
@@ -1177,7 +1178,7 @@ static void prepare_submodule_summary(struct summary_cb *info,
if (info->for_status && p->status != 'A' &&
(sub = submodule_from_path(the_repository,
- &null_oid, p->sm_path))) {
+ null_oid(), p->sm_path))) {
char *config_key = NULL;
const char *value;
int ignore_all = 0;
@@ -1373,7 +1374,7 @@ static void sync_submodule(const char *path, const char *prefix,
if (!is_submodule_active(the_repository, path))
return;
- sub = submodule_from_path(the_repository, &null_oid, path);
+ sub = submodule_from_path(the_repository, null_oid(), path);
if (sub && sub->url) {
if (starts_with_dot_dot_slash(sub->url) ||
@@ -1525,7 +1526,7 @@ static void deinit_submodule(const char *path, const char *prefix,
struct strbuf sb_config = STRBUF_INIT;
char *sub_git_dir = xstrfmt("%s/.git", path);
- sub = submodule_from_path(the_repository, &null_oid, path);
+ sub = submodule_from_path(the_repository, null_oid(), path);
if (!sub || !sub->name)
goto cleanup;
@@ -1925,7 +1926,7 @@ static void determine_submodule_update_strategy(struct repository *r,
const char *update,
struct submodule_update_strategy *out)
{
- const struct submodule *sub = submodule_from_path(r, &null_oid, path);
+ const struct submodule *sub = submodule_from_path(r, null_oid(), path);
char *key;
const char *val;
@@ -2077,7 +2078,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
goto cleanup;
}
- sub = submodule_from_path(the_repository, &null_oid, ce->name);
+ sub = submodule_from_path(the_repository, null_oid(), ce->name);
if (suc->recursive_prefix)
displaypath = relative_path(suc->recursive_prefix,
@@ -2395,7 +2396,7 @@ static const char *remote_submodule_branch(const char *path)
const char *branch = NULL;
char *key;
- sub = submodule_from_path(the_repository, &null_oid, path);
+ sub = submodule_from_path(the_repository, null_oid(), path);
if (!sub)
return NULL;
@@ -2533,7 +2534,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);
+ sub = submodule_from_path(the_repository, null_oid(), path);
if (!sub)
BUG("We could get the submodule handle before?");
diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c
index 80237f0df1..e547a08d6c 100644
--- a/builtin/symbolic-ref.c
+++ b/builtin/symbolic-ref.c
@@ -24,9 +24,11 @@ static int check_symref(const char *HEAD, int quiet, int shorten, int print)
return 1;
}
if (print) {
+ char *to_free = NULL;
if (shorten)
- refname = shorten_unambiguous_ref(refname, 0);
+ refname = to_free = shorten_unambiguous_ref(refname, 0);
puts(refname);
+ free(to_free);
}
return 0;
}
diff --git a/builtin/tag.c b/builtin/tag.c
index d403417b56..82fcfc0982 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -39,6 +39,8 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting,
struct ref_format *format)
{
struct ref_array array;
+ struct strbuf output = STRBUF_INIT;
+ struct strbuf err = STRBUF_INIT;
char *to_free = NULL;
int i;
@@ -63,8 +65,17 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting,
filter_refs(&array, filter, FILTER_REFS_TAGS);
ref_array_sort(sorting, &array);
- for (i = 0; i < array.nr; i++)
- show_ref_array_item(array.items[i], format);
+ for (i = 0; i < array.nr; i++) {
+ strbuf_reset(&output);
+ strbuf_reset(&err);
+ if (format_ref_array_item(array.items[i], format, &output, &err))
+ die("%s", err.buf);
+ fwrite(output.buf, 1, output.len, stdout);
+ putchar('\n');
+ }
+
+ strbuf_release(&err);
+ strbuf_release(&output);
ref_array_clear(&array);
free(to_free);
diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c
index ca54fd1668..4a9466295b 100644
--- a/builtin/unpack-objects.c
+++ b/builtin/unpack-objects.c
@@ -46,7 +46,7 @@ static struct obj_buffer *lookup_object_buffer(struct object *base)
static void add_object_buffer(struct object *object, char *buffer, unsigned long size)
{
struct obj_buffer *obj;
- obj = xcalloc(1, sizeof(struct obj_buffer));
+ CALLOC_ARRAY(obj, 1);
obj->buffer = buffer;
obj->size = size;
if (add_decoration(&obj_decorate, object, obj))
@@ -355,7 +355,7 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size,
struct object_id base_oid;
if (type == OBJ_REF_DELTA) {
- hashcpy(base_oid.hash, fill(the_hash_algo->rawsz));
+ oidread(&base_oid, fill(the_hash_algo->rawsz));
use(the_hash_algo->rawsz);
delta_data = get_data(delta_size);
if (dry_run || !delta_data) {
@@ -421,7 +421,8 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size,
* has not been resolved yet.
*/
oidclr(&obj_list[nr].oid);
- add_delta_to_list(nr, &null_oid, base_offset, delta_data, delta_size);
+ add_delta_to_list(nr, null_oid(), base_offset,
+ delta_data, delta_size);
return;
}
}
@@ -501,7 +502,7 @@ static void unpack_all(void)
if (!quiet)
progress = start_progress(_("Unpacking objects"), nr_objects);
- obj_list = xcalloc(nr_objects, sizeof(*obj_list));
+ CALLOC_ARRAY(obj_list, nr_objects);
for (i = 0; i < nr_objects; i++) {
unpack_one(i);
display_progress(progress, i + 1);
@@ -576,7 +577,7 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
the_hash_algo->init_fn(&ctx);
unpack_all();
the_hash_algo->update_fn(&ctx, buffer, offset);
- the_hash_algo->final_fn(oid.hash, &ctx);
+ the_hash_algo->final_oid_fn(&oid, &ctx);
if (strict) {
write_rest();
if (fsck_finish(&fsck_options))
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 79087bccea..f1f16f2de5 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -745,6 +745,8 @@ static int do_reupdate(int ac, const char **av,
*/
has_head = 0;
redo:
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(&the_index);
for (pos = 0; pos < active_nr; pos++) {
const struct cache_entry *ce = active_cache[pos];
struct cache_entry *old = NULL;
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 1cd5c2016e..976bf8ed06 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -118,10 +118,8 @@ static void prune_worktrees(void)
struct dirent *d;
if (!dir)
return;
- while ((d = readdir(dir)) != NULL) {
+ while ((d = readdir_skip_dot_and_dotdot(dir)) != NULL) {
char *path;
- if (is_dot_or_dotdot(d->d_name))
- continue;
strbuf_reset(&reason);
if (should_prune_worktree(d->d_name, &reason, &path, expire))
prune_worktree(d->d_name, reason.buf);
@@ -331,7 +329,7 @@ static int add_worktree(const char *path, const char *refname,
*/
strbuf_reset(&sb);
strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
- write_file(sb.buf, "%s", oid_to_hex(&null_oid));
+ write_file(sb.buf, "%s", oid_to_hex(null_oid()));
strbuf_reset(&sb);
strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
write_file(sb.buf, "../..");
@@ -394,7 +392,7 @@ done:
cp.argv = NULL;
cp.trace2_hook_name = "post-checkout";
strvec_pushl(&cp.args, absolute_path(hook),
- oid_to_hex(&null_oid),
+ oid_to_hex(null_oid()),
oid_to_hex(&commit->object.oid),
"1", NULL);
ret = run_command(&cp);
@@ -446,16 +444,18 @@ static void print_preparing_worktree_line(int detach,
static const char *dwim_branch(const char *path, const char **new_branch)
{
int n;
+ int branch_exists;
const char *s = worktree_basename(path, &n);
const char *branchname = xstrndup(s, n);
struct strbuf ref = STRBUF_INIT;
UNLEAK(branchname);
- if (!strbuf_check_branch_ref(&ref, branchname) &&
- ref_exists(ref.buf)) {
- strbuf_release(&ref);
+
+ branch_exists = !strbuf_check_branch_ref(&ref, branchname) &&
+ ref_exists(ref.buf);
+ strbuf_release(&ref);
+ if (branch_exists)
return branchname;
- }
*new_branch = branchname;
if (guess_remote) {
diff --git a/bulk-checkin.c b/bulk-checkin.c
index 583aacb9e3..127312acd1 100644
--- a/bulk-checkin.c
+++ b/bulk-checkin.c
@@ -211,7 +211,7 @@ static int deflate_to_pack(struct bulk_checkin_state *state,
/* Note: idx is non-NULL when we are writing */
if ((flags & HASH_WRITE_OBJECT) != 0)
- idx = xcalloc(1, sizeof(*idx));
+ CALLOC_ARRAY(idx, 1);
already_hashed_to = 0;
@@ -238,7 +238,7 @@ static int deflate_to_pack(struct bulk_checkin_state *state,
if (lseek(fd, seekback, SEEK_SET) == (off_t) -1)
return error("cannot seek back");
}
- the_hash_algo->final_fn(result_oid->hash, &ctx);
+ the_hash_algo->final_oid_fn(result_oid, &ctx);
if (!idx)
return 0;
diff --git a/cache-tree.c b/cache-tree.c
index 2fb483d3c0..45e58666af 100644
--- a/cache-tree.c
+++ b/cache-tree.c
@@ -6,6 +6,7 @@
#include "object-store.h"
#include "replace-object.h"
#include "promisor-remote.h"
+#include "sparse-index.h"
#ifndef DEBUG_CACHE_TREE
#define DEBUG_CACHE_TREE 0
@@ -255,6 +256,24 @@ static int update_one(struct cache_tree *it,
*skip_count = 0;
+ /*
+ * If the first entry of this region is a sparse directory
+ * entry corresponding exactly to 'base', then this cache_tree
+ * struct is a "leaf" in the data structure, pointing to the
+ * tree OID specified in the entry.
+ */
+ if (entries > 0) {
+ const struct cache_entry *ce = cache[0];
+
+ if (S_ISSPARSEDIR(ce->ce_mode) &&
+ ce->ce_namelen == baselen &&
+ !strncmp(ce->name, base, baselen)) {
+ it->entry_count = 1;
+ oidcpy(&it->oid, &ce->oid);
+ return 1;
+ }
+ }
+
if (0 <= it->entry_count && has_object_file(&it->oid))
return it->entry_count;
@@ -442,6 +461,8 @@ int cache_tree_update(struct index_state *istate, int flags)
if (i)
return i;
+ ensure_full_index(istate);
+
if (!istate->cache_tree)
istate->cache_tree = cache_tree();
@@ -564,7 +585,7 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p)
* hence +2.
*/
it->subtree_alloc = subtree_nr + 2;
- it->down = xcalloc(it->subtree_alloc, sizeof(struct cache_tree_sub *));
+ CALLOC_ARRAY(it->down, it->subtree_alloc);
for (i = 0; i < subtree_nr; i++) {
/* read each subtree */
struct cache_tree *sub;
@@ -787,6 +808,19 @@ int cache_tree_matches_traversal(struct cache_tree *root,
return 0;
}
+static void verify_one_sparse(struct repository *r,
+ struct index_state *istate,
+ struct cache_tree *it,
+ struct strbuf *path,
+ int pos)
+{
+ struct cache_entry *ce = istate->cache[pos];
+
+ if (!S_ISSPARSEDIR(ce->ce_mode))
+ BUG("directory '%s' is present in index, but not sparse",
+ path->buf);
+}
+
static void verify_one(struct repository *r,
struct index_state *istate,
struct cache_tree *it,
@@ -809,6 +843,12 @@ static void verify_one(struct repository *r,
if (path->len) {
pos = index_name_pos(istate, path->buf, path->len);
+
+ if (pos >= 0) {
+ verify_one_sparse(r, istate, it, path, pos);
+ return;
+ }
+
pos = -pos - 1;
} else {
pos = 0;
diff --git a/cache.h b/cache.h
index 6fda8091f1..ba04ff8bd3 100644
--- a/cache.h
+++ b/cache.h
@@ -204,6 +204,8 @@ struct cache_entry {
#error "CE_EXTENDED_FLAGS out of range"
#endif
+#define S_ISSPARSEDIR(m) ((m) == S_IFDIR)
+
/* Forward structure decls */
struct pathspec;
struct child_process;
@@ -249,6 +251,8 @@ static inline unsigned int create_ce_mode(unsigned int mode)
{
if (S_ISLNK(mode))
return S_IFLNK;
+ if (S_ISSPARSEDIR(mode))
+ return S_IFDIR;
if (S_ISDIR(mode) || S_ISGITLINK(mode))
return S_IFGITLINK;
return S_IFREG | ce_permissions(mode);
@@ -305,6 +309,7 @@ static inline unsigned int canon_mode(unsigned int mode)
struct split_index;
struct untracked_cache;
struct progress;
+struct pattern_list;
struct index_state {
struct cache_entry **cache;
@@ -319,7 +324,14 @@ struct index_state {
drop_cache_tree : 1,
updated_workdir : 1,
updated_skipworktree : 1,
- fsmonitor_has_run_once : 1;
+ fsmonitor_has_run_once : 1,
+
+ /*
+ * sparse_index == 1 when sparse-directory
+ * entries exist. Requires sparse-checkout
+ * in cone mode.
+ */
+ sparse_index : 1;
struct hashmap name_hash;
struct hashmap dir_hash;
struct object_id oid;
@@ -329,6 +341,7 @@ struct index_state {
struct mem_pool *ce_mem_pool;
struct progress *progress;
struct repository *repo;
+ struct pattern_list *sparse_checkout_patterns;
};
/* Name hashing */
@@ -337,6 +350,7 @@ void add_name_hash(struct index_state *istate, struct cache_entry *ce);
void remove_name_hash(struct index_state *istate, struct cache_entry *ce);
void free_name_hash(struct index_state *istate);
+void ensure_full_index(struct index_state *istate);
/* Cache entry creation and cleanup */
@@ -356,16 +370,20 @@ struct cache_entry *make_empty_cache_entry(struct index_state *istate,
size_t name_len);
/*
- * Create a cache_entry that is not intended to be added to an index.
- * Caller is responsible for discarding the cache_entry
- * with `discard_cache_entry`.
+ * Create a cache_entry that is not intended to be added to an index. If
+ * `ce_mem_pool` is not NULL, the entry is allocated within the given memory
+ * pool. Caller is responsible for discarding "loose" entries with
+ * `discard_cache_entry()` and the memory pool with
+ * `mem_pool_discard(ce_mem_pool, should_validate_cache_entries())`.
*/
struct cache_entry *make_transient_cache_entry(unsigned int mode,
const struct object_id *oid,
const char *path,
- int stage);
+ int stage,
+ struct mem_pool *ce_mem_pool);
-struct cache_entry *make_empty_transient_cache_entry(size_t name_len);
+struct cache_entry *make_empty_transient_cache_entry(size_t len,
+ struct mem_pool *ce_mem_pool);
/*
* Discard cache entry.
@@ -722,6 +740,8 @@ int read_index_from(struct index_state *, const char *path,
const char *gitdir);
int is_index_unborn(struct index_state *);
+void ensure_full_index(struct index_state *istate);
+
/* For use with `write_locked_index()`. */
#define COMMIT_LOCK (1 << 0)
#define SKIP_IF_UNCHANGED (1 << 1)
@@ -785,7 +805,7 @@ struct cache_entry *index_file_exists(struct index_state *istate, const char *na
* index_name_pos(&index, "f", 1) -> -3
* index_name_pos(&index, "g", 1) -> -5
*/
-int index_name_pos(const struct index_state *, const char *name, int namelen);
+int index_name_pos(struct index_state *, const char *name, int namelen);
/*
* Some functions return the negative complement of an insert position when a
@@ -803,7 +823,7 @@ static inline int index_pos_to_insert_pos(uintmax_t pos)
#define ADD_CACHE_OK_TO_ADD 1 /* Ok to add */
#define ADD_CACHE_OK_TO_REPLACE 2 /* Ok to replace file/directory */
#define ADD_CACHE_SKIP_DFCHECK 4 /* Ok to skip DF conflict checks */
-#define ADD_CACHE_JUST_APPEND 8 /* Append only; tree.c::read_tree() */
+#define ADD_CACHE_JUST_APPEND 8 /* Append only */
#define ADD_CACHE_NEW_ONLY 16 /* Do not replace existing ones */
#define ADD_CACHE_KEEP_CACHE_TREE 32 /* Do not invalidate cache-tree */
#define ADD_CACHE_RENORMALIZE 64 /* Pass along HASH_RENORMALIZE */
@@ -835,8 +855,8 @@ int add_file_to_index(struct index_state *, const char *path, int flags);
int chmod_index_entry(struct index_state *, struct cache_entry *ce, char flip);
int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);
void set_object_name_for_intent_to_add_entry(struct cache_entry *ce);
-int index_name_is_other(const struct index_state *, const char *, int);
-void *read_blob_data_from_index(const struct index_state *, const char *, unsigned long *);
+int index_name_is_other(struct index_state *, const char *, int);
+void *read_blob_data_from_index(struct index_state *, const char *, unsigned long *);
/* do stat comparison even if CE_VALID is true */
#define CE_MATCH_IGNORE_VALID 01
@@ -879,13 +899,14 @@ int match_stat_data_racy(const struct index_state *istate,
void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, struct stat *st);
-#define REFRESH_REALLY 0x0001 /* ignore_valid */
-#define REFRESH_UNMERGED 0x0002 /* allow unmerged */
-#define REFRESH_QUIET 0x0004 /* be quiet about it */
-#define REFRESH_IGNORE_MISSING 0x0008 /* ignore non-existent */
-#define REFRESH_IGNORE_SUBMODULES 0x0010 /* ignore submodules */
-#define REFRESH_IN_PORCELAIN 0x0020 /* user friendly output, not "needs update" */
-#define REFRESH_PROGRESS 0x0040 /* show progress bar if stderr is tty */
+#define REFRESH_REALLY (1 << 0) /* ignore_valid */
+#define REFRESH_UNMERGED (1 << 1) /* allow unmerged */
+#define REFRESH_QUIET (1 << 2) /* be quiet about it */
+#define REFRESH_IGNORE_MISSING (1 << 3) /* ignore non-existent */
+#define REFRESH_IGNORE_SUBMODULES (1 << 4) /* ignore submodules */
+#define REFRESH_IN_PORCELAIN (1 << 5) /* user friendly output, not "needs update" */
+#define REFRESH_PROGRESS (1 << 6) /* show progress bar if stderr is tty */
+#define REFRESH_IGNORE_SKIP_WORKTREE (1 << 7) /* ignore skip_worktree entries */
int refresh_index(struct index_state *, unsigned int flags, const struct pathspec *pathspec, char *seen, const char *header_msg);
/*
* Refresh the index and write it to disk.
@@ -1044,6 +1065,7 @@ struct repository_format {
int worktree_config;
int is_bare;
int hash_algo;
+ int sparse_index;
char *work_tree;
struct string_list unknown_extensions;
struct string_list v1_only_extensions;
@@ -1254,6 +1276,7 @@ int is_ntfs_dotgit(const char *name);
int is_ntfs_dotgitmodules(const char *name);
int is_ntfs_dotgitignore(const char *name);
int is_ntfs_dotgitattributes(const char *name);
+int is_ntfs_dotmailmap(const char *name);
/*
* Returns true iff "str" could be confused as a command-line option when
@@ -1621,30 +1644,6 @@ const char *show_ident_date(const struct ident_split *id,
*/
int ident_cmp(const struct ident_split *, const struct ident_split *);
-struct checkout {
- struct index_state *istate;
- const char *base_dir;
- int base_dir_len;
- struct delayed_checkout *delayed_checkout;
- struct checkout_metadata meta;
- unsigned force:1,
- quiet:1,
- not_new:1,
- clone:1,
- refresh_cache:1;
-};
-#define CHECKOUT_INIT { NULL, "" }
-
-#define TEMPORARY_FILENAME_LENGTH 25
-int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath, int *nr_checkouts);
-void enable_delayed_checkout(struct checkout *state);
-int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);
-/*
- * Unlink the last component and schedule the leading directories for
- * removal, such that empty directories get removed.
- */
-void unlink_entry(const struct cache_entry *ce);
-
struct cache_def {
struct strbuf path;
int flags;
@@ -1659,7 +1658,7 @@ static inline void cache_def_clear(struct cache_def *cache)
int has_symlink_leading_path(const char *name, int len);
int threaded_has_symlink_leading_path(struct cache_def *, const char *, int);
-int check_leading_path(const char *name, int len);
+int check_leading_path(const char *name, int len, int warn_on_lstat_err);
int has_dirs_only_path(const char *name, int len, int prefix_len);
void invalidate_lstat_cache(void);
void schedule_dir_for_removal(const char *name, int len);
diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh
index a66b5e8c75..3ce81ffee9 100755
--- a/ci/run-build-and-tests.sh
+++ b/ci/run-build-and-tests.sh
@@ -16,6 +16,7 @@ linux-gcc)
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
make test
export GIT_TEST_SPLIT_INDEX=yes
+ export GIT_TEST_MERGE_ALGORITHM=recursive
export GIT_TEST_FULL_IN_PACK_ARRAY=true
export GIT_TEST_OE_SIZE=10
export GIT_TEST_OE_DELTA_SIZE=5
@@ -25,6 +26,7 @@ linux-gcc)
export GIT_TEST_ADD_I_USE_BUILTIN=1
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master
export GIT_TEST_WRITE_REV_INDEX=1
+ export GIT_TEST_CHECKOUT_WORKERS=2
make test
;;
linux-clang)
diff --git a/combine-diff.c b/combine-diff.c
index 9228aebc16..7d925ce9ce 100644
--- a/combine-diff.c
+++ b/combine-diff.c
@@ -214,11 +214,11 @@ static struct lline *coalesce_lines(struct lline *base, int *lenbase,
* - Else if we have NEW, insert newend lline into base and
* consume newend
*/
- lcs = xcalloc(st_add(origbaselen, 1), sizeof(int*));
- directions = xcalloc(st_add(origbaselen, 1), sizeof(enum coalesce_direction*));
+ CALLOC_ARRAY(lcs, st_add(origbaselen, 1));
+ CALLOC_ARRAY(directions, st_add(origbaselen, 1));
for (i = 0; i < origbaselen + 1; i++) {
- lcs[i] = xcalloc(st_add(lennew, 1), sizeof(int));
- directions[i] = xcalloc(st_add(lennew, 1), sizeof(enum coalesce_direction));
+ CALLOC_ARRAY(lcs[i], st_add(lennew, 1));
+ CALLOC_ARRAY(directions[i], st_add(lennew, 1));
directions[i][0] = BASE;
}
for (j = 1; j < lennew + 1; j++)
@@ -398,8 +398,8 @@ static void consume_hunk(void *state_,
state->lost_bucket = &state->sline[state->nb-1];
}
if (!state->sline[state->nb-1].p_lno)
- state->sline[state->nb-1].p_lno =
- xcalloc(state->num_parent, sizeof(unsigned long));
+ CALLOC_ARRAY(state->sline[state->nb - 1].p_lno,
+ state->num_parent);
state->sline[state->nb-1].p_lno[state->n] = state->ob;
}
@@ -1068,7 +1068,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
&result_size, NULL, NULL);
} else if (textconv) {
struct diff_filespec *df = alloc_filespec(elem->path);
- fill_filespec(df, &null_oid, 0, st.st_mode);
+ fill_filespec(df, null_oid(), 0, st.st_mode);
result_size = fill_textconv(opt->repo, textconv, df, &result);
free_filespec(df);
} else if (0 <= (fd = open(elem->path, O_RDONLY))) {
@@ -1159,7 +1159,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
if (result_size && result[result_size-1] != '\n')
cnt++; /* incomplete line */
- sline = xcalloc(st_add(cnt, 2), sizeof(*sline));
+ CALLOC_ARRAY(sline, st_add(cnt, 2));
sline[0].bol = result;
for (lno = 0, cp = result; cp < result + result_size; cp++) {
if (*cp == '\n') {
@@ -1178,7 +1178,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
/* Even p_lno[cnt+1] is valid -- that is for the end line number
* for deletion hunk at the end.
*/
- sline[0].p_lno = xcalloc(st_mult(st_add(cnt, 2), num_parent), sizeof(unsigned long));
+ CALLOC_ARRAY(sline[0].p_lno, st_mult(st_add(cnt, 2), num_parent));
for (lno = 0; lno <= cnt; lno++)
sline[lno+1].p_lno = sline[lno].p_lno + num_parent;
@@ -1319,7 +1319,7 @@ static struct diff_filepair *combined_pair(struct combine_diff_path *p,
struct diff_filespec *pool;
pair = xmalloc(sizeof(*pair));
- pool = xcalloc(st_add(num_parent, 1), sizeof(struct diff_filespec));
+ CALLOC_ARRAY(pool, st_add(num_parent, 1));
pair->one = pool + 1;
pair->two = pool;
@@ -1348,7 +1348,7 @@ static void handle_combined_callback(struct diff_options *opt,
struct diff_queue_struct q;
int i;
- q.queue = xcalloc(num_paths, sizeof(struct diff_filepair *));
+ CALLOC_ARRAY(q.queue, num_paths);
q.alloc = num_paths;
q.nr = num_paths;
for (i = 0, p = paths; p; p = p->next)
diff --git a/commit-graph.c b/commit-graph.c
index ca025ce8eb..2bcb4e0f89 100644
--- a/commit-graph.c
+++ b/commit-graph.c
@@ -96,6 +96,13 @@ define_commit_slab(commit_graph_data_slab, struct commit_graph_data);
static struct commit_graph_data_slab commit_graph_data_slab =
COMMIT_SLAB_INIT(1, commit_graph_data_slab);
+static int get_configured_generation_version(struct repository *r)
+{
+ int version = 2;
+ repo_config_get_int(r, "commitgraph.generationversion", &version);
+ return version;
+}
+
uint32_t commit_graph_position(const struct commit *c)
{
struct commit_graph_data *data =
@@ -394,10 +401,13 @@ struct commit_graph *parse_commit_graph(struct repository *r,
pair_chunk(cf, GRAPH_CHUNKID_DATA, &graph->chunk_commit_data);
pair_chunk(cf, GRAPH_CHUNKID_EXTRAEDGES, &graph->chunk_extra_edges);
pair_chunk(cf, GRAPH_CHUNKID_BASE, &graph->chunk_base_graphs);
- pair_chunk(cf, GRAPH_CHUNKID_GENERATION_DATA,
- &graph->chunk_generation_data);
- pair_chunk(cf, GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW,
- &graph->chunk_generation_data_overflow);
+
+ if (get_configured_generation_version(r) >= 2) {
+ pair_chunk(cf, GRAPH_CHUNKID_GENERATION_DATA,
+ &graph->chunk_generation_data);
+ pair_chunk(cf, GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW,
+ &graph->chunk_generation_data_overflow);
+ }
if (r->settings.commit_graph_read_changed_paths) {
pair_chunk(cf, GRAPH_CHUNKID_BLOOMINDEXES,
@@ -415,7 +425,7 @@ struct commit_graph *parse_commit_graph(struct repository *r,
FREE_AND_NULL(graph->bloom_filter_settings);
}
- hashcpy(graph->oid.hash, graph->data + graph->data_len - graph->hash_len);
+ oidread(&graph->oid, graph->data + graph->data_len - graph->hash_len);
if (verify_commit_graph_lite(graph))
goto free_and_return;
@@ -516,7 +526,7 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r,
return NULL;
count = st.st_size / (the_hash_algo->hexsz + 1);
- oids = xcalloc(count, sizeof(struct object_id));
+ CALLOC_ARRAY(oids, count);
prepare_alt_odb(r);
@@ -736,7 +746,7 @@ static void load_oid_from_graph(struct commit_graph *g,
lex_index = pos - g->num_commits_in_base;
- hashcpy(oid->hash, g->chunk_oid_lookup + g->hash_len * lex_index);
+ oidread(oid, g->chunk_oid_lookup + g->hash_len * lex_index);
}
static struct commit_list **insert_parent_or_die(struct repository *r,
@@ -929,7 +939,7 @@ static struct tree *load_tree_for_commit(struct repository *r,
commit_data = g->chunk_commit_data +
GRAPH_DATA_WIDTH * (graph_pos - g->num_commits_in_base);
- hashcpy(oid.hash, commit_data);
+ oidread(&oid, commit_data);
set_commit_tree(c, lookup_tree(r, &oid));
return c->maybe_tree;
@@ -1783,8 +1793,8 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
struct lock_file lk = LOCK_INIT;
const unsigned hashsz = the_hash_algo->rawsz;
struct strbuf progress_title = STRBUF_INIT;
- struct object_id file_hash;
struct chunkfile *cf;
+ unsigned char file_hash[GIT_MAX_RAWSZ];
if (ctx->split) {
struct strbuf tmp_file = STRBUF_INIT;
@@ -1839,8 +1849,6 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
add_chunk(cf, GRAPH_CHUNKID_DATA, (hashsz + 16) * ctx->commits.nr,
write_graph_chunk_data);
- if (git_env_bool(GIT_TEST_COMMIT_GRAPH_NO_GDAT, 0))
- ctx->write_generation_data = 0;
if (ctx->write_generation_data)
add_chunk(cf, GRAPH_CHUNKID_GENERATION_DATA,
sizeof(uint32_t) * ctx->commits.nr,
@@ -1901,7 +1909,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
}
close_commit_graph(ctx->r->objects);
- finalize_hashfile(f, file_hash.hash, CSUM_HASH_IN_STREAM | CSUM_FSYNC);
+ finalize_hashfile(f, file_hash, CSUM_HASH_IN_STREAM | CSUM_FSYNC);
free_chunkfile(cf);
if (ctx->split) {
@@ -1937,7 +1945,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
unlink(graph_name);
}
- ctx->commit_graph_hash_after[ctx->num_commit_graphs_after - 1] = xstrdup(oid_to_hex(&file_hash));
+ ctx->commit_graph_hash_after[ctx->num_commit_graphs_after - 1] = xstrdup(hash_to_hex(file_hash));
final_graph_name = get_split_graph_filename(ctx->odb,
ctx->commit_graph_hash_after[ctx->num_commit_graphs_after - 1]);
ctx->commit_graph_filenames_after[ctx->num_commit_graphs_after - 1] = final_graph_name;
@@ -2223,6 +2231,7 @@ int write_commit_graph(struct object_directory *odb,
enum commit_graph_write_flags flags,
const struct commit_graph_opts *opts)
{
+ struct repository *r = the_repository;
struct write_commit_graph_context *ctx;
uint32_t i;
int res = 0;
@@ -2230,23 +2239,23 @@ int write_commit_graph(struct object_directory *odb,
struct bloom_filter_settings bloom_settings = DEFAULT_BLOOM_FILTER_SETTINGS;
struct topo_level_slab topo_levels;
- prepare_repo_settings(the_repository);
- if (!the_repository->settings.core_commit_graph) {
+ prepare_repo_settings(r);
+ if (!r->settings.core_commit_graph) {
warning(_("attempting to write a commit-graph, but 'core.commitGraph' is disabled"));
return 0;
}
- if (!commit_graph_compatible(the_repository))
+ if (!commit_graph_compatible(r))
return 0;
- ctx = xcalloc(1, sizeof(struct write_commit_graph_context));
- ctx->r = the_repository;
+ CALLOC_ARRAY(ctx, 1);
+ ctx->r = r;
ctx->odb = odb;
ctx->append = flags & COMMIT_GRAPH_WRITE_APPEND ? 1 : 0;
ctx->report_progress = flags & COMMIT_GRAPH_WRITE_PROGRESS ? 1 : 0;
ctx->split = flags & COMMIT_GRAPH_WRITE_SPLIT ? 1 : 0;
ctx->opts = opts;
ctx->total_bloom_filter_data_size = 0;
- ctx->write_generation_data = 1;
+ ctx->write_generation_data = (get_configured_generation_version(r) == 2);
ctx->num_generation_data_overflows = 0;
bloom_settings.bits_per_entry = git_env_ulong("GIT_TEST_BLOOM_SETTINGS_BITS_PER_ENTRY",
@@ -2313,7 +2322,7 @@ int write_commit_graph(struct object_directory *odb,
struct commit_graph *g = ctx->r->objects->commit_graph;
for (i = 0; i < g->num_commits; i++) {
struct object_id oid;
- hashcpy(oid.hash, g->chunk_oid_lookup + g->hash_len * i);
+ oidread(&oid, g->chunk_oid_lookup + g->hash_len * i);
oid_array_append(&ctx->oids, &oid);
}
}
@@ -2416,7 +2425,8 @@ static void graph_report(const char *fmt, ...)
int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags)
{
uint32_t i, cur_fanout_pos = 0;
- struct object_id prev_oid, cur_oid, checksum;
+ struct object_id prev_oid, cur_oid;
+ unsigned char checksum[GIT_MAX_HEXSZ];
int generation_zero = 0;
struct hashfile *f;
int devnull;
@@ -2435,8 +2445,8 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags)
devnull = open("/dev/null", O_WRONLY);
f = hashfd(devnull, NULL);
hashwrite(f, g->data, g->data_len - g->hash_len);
- finalize_hashfile(f, checksum.hash, CSUM_CLOSE);
- if (!hasheq(checksum.hash, g->data + g->data_len - g->hash_len)) {
+ finalize_hashfile(f, checksum, CSUM_CLOSE);
+ if (!hasheq(checksum, g->data + g->data_len - g->hash_len)) {
graph_report(_("the commit-graph file has incorrect checksum and is likely corrupt"));
verify_commit_graph_error = VERIFY_COMMIT_GRAPH_ERROR_HASH;
}
@@ -2444,7 +2454,7 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags)
for (i = 0; i < g->num_commits; i++) {
struct commit *graph_commit;
- hashcpy(cur_oid.hash, g->chunk_oid_lookup + g->hash_len * i);
+ oidread(&cur_oid, g->chunk_oid_lookup + g->hash_len * i);
if (i && oidcmp(&prev_oid, &cur_oid) >= 0)
graph_report(_("commit-graph has incorrect OID order: %s then %s"),
@@ -2492,7 +2502,7 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags)
timestamp_t generation;
display_progress(progress, i + 1);
- hashcpy(cur_oid.hash, g->chunk_oid_lookup + g->hash_len * i);
+ oidread(&cur_oid, g->chunk_oid_lookup + g->hash_len * i);
graph_commit = lookup_commit(r, &cur_oid);
odb_commit = (struct commit *)create_object(r, &cur_oid, alloc_commit_node(r));
diff --git a/commit-graph.h b/commit-graph.h
index 97f3497c27..96c24fb577 100644
--- a/commit-graph.h
+++ b/commit-graph.h
@@ -6,7 +6,6 @@
#include "oidset.h"
#define GIT_TEST_COMMIT_GRAPH "GIT_TEST_COMMIT_GRAPH"
-#define GIT_TEST_COMMIT_GRAPH_NO_GDAT "GIT_TEST_COMMIT_GRAPH_NO_GDAT"
#define GIT_TEST_COMMIT_GRAPH_DIE_ON_PARSE "GIT_TEST_COMMIT_GRAPH_DIE_ON_PARSE"
#define GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS "GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS"
diff --git a/commit-reach.c b/commit-reach.c
index 2ea84d3dc0..c226ee3da4 100644
--- a/commit-reach.c
+++ b/commit-reach.c
@@ -183,7 +183,7 @@ static int remove_redundant_no_gen(struct repository *r,
int *filled_index;
int i, j, filled;
- work = xcalloc(cnt, sizeof(*work));
+ CALLOC_ARRAY(work, cnt);
redundant = xcalloc(cnt, 1);
ALLOC_ARRAY(filled_index, cnt - 1);
@@ -399,7 +399,7 @@ static struct commit_list *get_merge_bases_many_0(struct repository *r,
/* There are more than one */
cnt = commit_list_count(result);
- rslt = xcalloc(cnt, sizeof(*rslt));
+ CALLOC_ARRAY(rslt, cnt);
for (list = result, i = 0; list; list = list->next)
rslt[i++] = list->item;
free_commit_list(result);
@@ -541,7 +541,7 @@ struct commit_list *reduce_heads(struct commit_list *heads)
p->item->object.flags |= STALE;
num_head++;
}
- array = xcalloc(num_head, sizeof(*array));
+ CALLOC_ARRAY(array, num_head);
for (p = heads, i = 0; p; p = p->next) {
if (p->item->object.flags & STALE) {
array[i++] = p->item;
diff --git a/commit.c b/commit.c
index 6ccd774841..8ea55a447f 100644
--- a/commit.c
+++ b/commit.c
@@ -535,6 +535,20 @@ int find_commit_subject(const char *commit_buffer, const char **subject)
return eol - p;
}
+size_t commit_subject_length(const char *body)
+{
+ const char *p = body;
+ while (*p) {
+ const char *next = skip_blank_lines(p);
+ if (next != p)
+ break;
+ p = strchrnul(p, '\n');
+ if (*p)
+ p++;
+ }
+ return p - body;
+}
+
struct commit_list *commit_list_insert(struct commit *item, struct commit_list **list_p)
{
struct commit_list *new_list = xmalloc(sizeof(struct commit_list));
@@ -1171,7 +1185,7 @@ static void handle_signed_tag(struct commit *parent, struct commit_extra_header
* if (verify_signed_buffer(buf, len, buf + len, size - len, ...))
* warn("warning: signed tag unverified.");
*/
- mergetag = xcalloc(1, sizeof(*mergetag));
+ CALLOC_ARRAY(mergetag, 1);
mergetag->key = xstrdup("mergetag");
mergetag->value = buf;
mergetag->len = size;
@@ -1336,7 +1350,7 @@ static struct commit_extra_header *read_commit_extra_header_lines(
excluded_header_field(line, eof - line, exclude))
continue;
- it = xcalloc(1, sizeof(*it));
+ CALLOC_ARRAY(it, 1);
it->key = xmemdupz(line, eof-line);
*tail = it;
tail = &it->next;
diff --git a/commit.h b/commit.h
index 49c0f50396..df42eb434f 100644
--- a/commit.h
+++ b/commit.h
@@ -167,6 +167,9 @@ const void *detach_commit_buffer(struct commit *, unsigned long *sizep);
/* Find beginning and length of commit subject. */
int find_commit_subject(const char *commit_buffer, const char **subject);
+/* Return length of the commit subject from commit log message. */
+size_t commit_subject_length(const char *body);
+
struct commit_list *commit_list_insert(struct commit *item,
struct commit_list **list);
int commit_list_contains(struct commit *item,
diff --git a/compat/mingw.c b/compat/mingw.c
index a43599841c..aa647b367b 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -685,6 +685,8 @@ ssize_t mingw_write(int fd, const void *buf, size_t len)
int mingw_access(const char *filename, int mode)
{
wchar_t wfilename[MAX_PATH];
+ if (!strcmp("nul", filename) || !strcmp("/dev/null", filename))
+ return 0;
if (xutftowcs_path(wfilename, filename) < 0)
return -1;
/* X_OK is not supported by the MSVCRT version */
diff --git a/compat/precompose_utf8.c b/compat/precompose_utf8.c
index ec560565a8..cce1d57a46 100644
--- a/compat/precompose_utf8.c
+++ b/compat/precompose_utf8.c
@@ -60,10 +60,12 @@ void probe_utf8_pathname_composition(void)
strbuf_release(&path);
}
-static inline const char *precompose_string_if_needed(const char *in)
+const char *precompose_string_if_needed(const char *in)
{
size_t inlen;
size_t outlen;
+ if (!in)
+ return NULL;
if (has_non_ascii(in, (size_t)-1, &inlen)) {
iconv_t ic_prec;
char *out;
@@ -96,10 +98,7 @@ const char *precompose_argv_prefix(int argc, const char **argv, const char *pref
argv[i] = precompose_string_if_needed(argv[i]);
i++;
}
- if (prefix) {
- prefix = precompose_string_if_needed(prefix);
- }
- return prefix;
+ return precompose_string_if_needed(prefix);
}
diff --git a/compat/precompose_utf8.h b/compat/precompose_utf8.h
index d70b84665c..fea06cf28a 100644
--- a/compat/precompose_utf8.h
+++ b/compat/precompose_utf8.h
@@ -29,6 +29,7 @@ typedef struct {
} PREC_DIR;
const char *precompose_argv_prefix(int argc, const char **argv, const char *prefix);
+const char *precompose_string_if_needed(const char *in);
void probe_utf8_pathname_composition(void);
PREC_DIR *precompose_utf8_opendir(const char *dirname);
diff --git a/compat/simple-ipc/ipc-shared.c b/compat/simple-ipc/ipc-shared.c
new file mode 100644
index 0000000000..1b9d359ab6
--- /dev/null
+++ b/compat/simple-ipc/ipc-shared.c
@@ -0,0 +1,32 @@
+#include "cache.h"
+#include "simple-ipc.h"
+#include "strbuf.h"
+#include "pkt-line.h"
+#include "thread-utils.h"
+
+#ifndef SUPPORTS_SIMPLE_IPC
+/*
+ * This source file should only be compiled when Simple IPC is supported.
+ * See the top-level Makefile.
+ */
+#error SUPPORTS_SIMPLE_IPC not defined
+#endif
+
+int ipc_server_run(const char *path, const struct ipc_server_opts *opts,
+ ipc_server_application_cb *application_cb,
+ void *application_data)
+{
+ struct ipc_server_data *server_data = NULL;
+ int ret;
+
+ ret = ipc_server_run_async(&server_data, path, opts,
+ application_cb, application_data);
+ if (ret)
+ return ret;
+
+ ret = ipc_server_await(server_data);
+
+ ipc_server_free(server_data);
+
+ return ret;
+}
diff --git a/compat/simple-ipc/ipc-unix-socket.c b/compat/simple-ipc/ipc-unix-socket.c
new file mode 100644
index 0000000000..1927e6ef4b
--- /dev/null
+++ b/compat/simple-ipc/ipc-unix-socket.c
@@ -0,0 +1,1003 @@
+#include "cache.h"
+#include "simple-ipc.h"
+#include "strbuf.h"
+#include "pkt-line.h"
+#include "thread-utils.h"
+#include "unix-socket.h"
+#include "unix-stream-server.h"
+
+#ifndef SUPPORTS_SIMPLE_IPC
+/*
+ * This source file should only be compiled when Simple IPC is supported.
+ * See the top-level Makefile.
+ */
+#error SUPPORTS_SIMPLE_IPC not defined
+#endif
+
+enum ipc_active_state ipc_get_active_state(const char *path)
+{
+ enum ipc_active_state state = IPC_STATE__OTHER_ERROR;
+ struct ipc_client_connect_options options
+ = IPC_CLIENT_CONNECT_OPTIONS_INIT;
+ struct stat st;
+ struct ipc_client_connection *connection_test = NULL;
+
+ options.wait_if_busy = 0;
+ options.wait_if_not_found = 0;
+
+ if (lstat(path, &st) == -1) {
+ switch (errno) {
+ case ENOENT:
+ case ENOTDIR:
+ return IPC_STATE__NOT_LISTENING;
+ default:
+ return IPC_STATE__INVALID_PATH;
+ }
+ }
+
+ /* also complain if a plain file is in the way */
+ if ((st.st_mode & S_IFMT) != S_IFSOCK)
+ return IPC_STATE__INVALID_PATH;
+
+ /*
+ * Just because the filesystem has a S_IFSOCK type inode
+ * at `path`, doesn't mean it that there is a server listening.
+ * Ping it to be sure.
+ */
+ state = ipc_client_try_connect(path, &options, &connection_test);
+ ipc_client_close_connection(connection_test);
+
+ return state;
+}
+
+/*
+ * Retry frequency when trying to connect to a server.
+ *
+ * This value should be short enough that we don't seriously delay our
+ * caller, but not fast enough that our spinning puts pressure on the
+ * system.
+ */
+#define WAIT_STEP_MS (50)
+
+/*
+ * Try to connect to the server. If the server is just starting up or
+ * is very busy, we may not get a connection the first time.
+ */
+static enum ipc_active_state connect_to_server(
+ const char *path,
+ int timeout_ms,
+ const struct ipc_client_connect_options *options,
+ int *pfd)
+{
+ int k;
+
+ *pfd = -1;
+
+ for (k = 0; k < timeout_ms; k += WAIT_STEP_MS) {
+ int fd = unix_stream_connect(path, options->uds_disallow_chdir);
+
+ if (fd != -1) {
+ *pfd = fd;
+ return IPC_STATE__LISTENING;
+ }
+
+ if (errno == ENOENT) {
+ if (!options->wait_if_not_found)
+ return IPC_STATE__PATH_NOT_FOUND;
+
+ goto sleep_and_try_again;
+ }
+
+ if (errno == ETIMEDOUT) {
+ if (!options->wait_if_busy)
+ return IPC_STATE__NOT_LISTENING;
+
+ goto sleep_and_try_again;
+ }
+
+ if (errno == ECONNREFUSED) {
+ if (!options->wait_if_busy)
+ return IPC_STATE__NOT_LISTENING;
+
+ goto sleep_and_try_again;
+ }
+
+ return IPC_STATE__OTHER_ERROR;
+
+ sleep_and_try_again:
+ sleep_millisec(WAIT_STEP_MS);
+ }
+
+ return IPC_STATE__NOT_LISTENING;
+}
+
+/*
+ * The total amount of time that we are willing to wait when trying to
+ * connect to a server.
+ *
+ * When the server is first started, it might take a little while for
+ * it to become ready to service requests. Likewise, the server may
+ * be very (temporarily) busy and not respond to our connections.
+ *
+ * We should gracefully and silently handle those conditions and try
+ * again for a reasonable time period.
+ *
+ * The value chosen here should be long enough for the server
+ * to reliably heal from the above conditions.
+ */
+#define MY_CONNECTION_TIMEOUT_MS (1000)
+
+enum ipc_active_state ipc_client_try_connect(
+ const char *path,
+ const struct ipc_client_connect_options *options,
+ struct ipc_client_connection **p_connection)
+{
+ enum ipc_active_state state = IPC_STATE__OTHER_ERROR;
+ int fd = -1;
+
+ *p_connection = NULL;
+
+ trace2_region_enter("ipc-client", "try-connect", NULL);
+ trace2_data_string("ipc-client", NULL, "try-connect/path", path);
+
+ state = connect_to_server(path, MY_CONNECTION_TIMEOUT_MS,
+ options, &fd);
+
+ trace2_data_intmax("ipc-client", NULL, "try-connect/state",
+ (intmax_t)state);
+ trace2_region_leave("ipc-client", "try-connect", NULL);
+
+ if (state == IPC_STATE__LISTENING) {
+ (*p_connection) = xcalloc(1, sizeof(struct ipc_client_connection));
+ (*p_connection)->fd = fd;
+ }
+
+ return state;
+}
+
+void ipc_client_close_connection(struct ipc_client_connection *connection)
+{
+ if (!connection)
+ return;
+
+ if (connection->fd != -1)
+ close(connection->fd);
+
+ free(connection);
+}
+
+int ipc_client_send_command_to_connection(
+ struct ipc_client_connection *connection,
+ const char *message, struct strbuf *answer)
+{
+ int ret = 0;
+
+ strbuf_setlen(answer, 0);
+
+ trace2_region_enter("ipc-client", "send-command", NULL);
+
+ if (write_packetized_from_buf_no_flush(message, strlen(message),
+ connection->fd) < 0 ||
+ packet_flush_gently(connection->fd) < 0) {
+ ret = error(_("could not send IPC command"));
+ goto done;
+ }
+
+ if (read_packetized_to_strbuf(
+ connection->fd, answer,
+ PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR) < 0) {
+ ret = error(_("could not read IPC response"));
+ goto done;
+ }
+
+done:
+ trace2_region_leave("ipc-client", "send-command", NULL);
+ return ret;
+}
+
+int ipc_client_send_command(const char *path,
+ const struct ipc_client_connect_options *options,
+ const char *message, struct strbuf *answer)
+{
+ int ret = -1;
+ enum ipc_active_state state;
+ struct ipc_client_connection *connection = NULL;
+
+ state = ipc_client_try_connect(path, options, &connection);
+
+ if (state != IPC_STATE__LISTENING)
+ return ret;
+
+ ret = ipc_client_send_command_to_connection(connection, message, answer);
+
+ ipc_client_close_connection(connection);
+
+ return ret;
+}
+
+static int set_socket_blocking_flag(int fd, int make_nonblocking)
+{
+ int flags;
+
+ flags = fcntl(fd, F_GETFL, NULL);
+
+ if (flags < 0)
+ return -1;
+
+ if (make_nonblocking)
+ flags |= O_NONBLOCK;
+ else
+ flags &= ~O_NONBLOCK;
+
+ return fcntl(fd, F_SETFL, flags);
+}
+
+/*
+ * Magic numbers used to annotate callback instance data.
+ * These are used to help guard against accidentally passing the
+ * wrong instance data across multiple levels of callbacks (which
+ * is easy to do if there are `void*` arguments).
+ */
+enum magic {
+ MAGIC_SERVER_REPLY_DATA,
+ MAGIC_WORKER_THREAD_DATA,
+ MAGIC_ACCEPT_THREAD_DATA,
+ MAGIC_SERVER_DATA,
+};
+
+struct ipc_server_reply_data {
+ enum magic magic;
+ int fd;
+ struct ipc_worker_thread_data *worker_thread_data;
+};
+
+struct ipc_worker_thread_data {
+ enum magic magic;
+ struct ipc_worker_thread_data *next_thread;
+ struct ipc_server_data *server_data;
+ pthread_t pthread_id;
+};
+
+struct ipc_accept_thread_data {
+ enum magic magic;
+ struct ipc_server_data *server_data;
+
+ struct unix_ss_socket *server_socket;
+
+ int fd_send_shutdown;
+ int fd_wait_shutdown;
+ pthread_t pthread_id;
+};
+
+/*
+ * With unix-sockets, the conceptual "ipc-server" is implemented as a single
+ * controller "accept-thread" thread and a pool of "worker-thread" threads.
+ * The former does the usual `accept()` loop and dispatches connections
+ * to an idle worker thread. The worker threads wait in an idle loop for
+ * a new connection, communicate with the client and relay data to/from
+ * the `application_cb` and then wait for another connection from the
+ * server thread. This avoids the overhead of constantly creating and
+ * destroying threads.
+ */
+struct ipc_server_data {
+ enum magic magic;
+ ipc_server_application_cb *application_cb;
+ void *application_data;
+ struct strbuf buf_path;
+
+ struct ipc_accept_thread_data *accept_thread;
+ struct ipc_worker_thread_data *worker_thread_list;
+
+ pthread_mutex_t work_available_mutex;
+ pthread_cond_t work_available_cond;
+
+ /*
+ * Accepted but not yet processed client connections are kept
+ * in a circular buffer FIFO. The queue is empty when the
+ * positions are equal.
+ */
+ int *fifo_fds;
+ int queue_size;
+ int back_pos;
+ int front_pos;
+
+ int shutdown_requested;
+ int is_stopped;
+};
+
+/*
+ * Remove and return the oldest queued connection.
+ *
+ * Returns -1 if empty.
+ */
+static int fifo_dequeue(struct ipc_server_data *server_data)
+{
+ /* ASSERT holding mutex */
+
+ int fd;
+
+ if (server_data->back_pos == server_data->front_pos)
+ return -1;
+
+ fd = server_data->fifo_fds[server_data->front_pos];
+ server_data->fifo_fds[server_data->front_pos] = -1;
+
+ server_data->front_pos++;
+ if (server_data->front_pos == server_data->queue_size)
+ server_data->front_pos = 0;
+
+ return fd;
+}
+
+/*
+ * Push a new fd onto the back of the queue.
+ *
+ * Drop it and return -1 if queue is already full.
+ */
+static int fifo_enqueue(struct ipc_server_data *server_data, int fd)
+{
+ /* ASSERT holding mutex */
+
+ int next_back_pos;
+
+ next_back_pos = server_data->back_pos + 1;
+ if (next_back_pos == server_data->queue_size)
+ next_back_pos = 0;
+
+ if (next_back_pos == server_data->front_pos) {
+ /* Queue is full. Just drop it. */
+ close(fd);
+ return -1;
+ }
+
+ server_data->fifo_fds[server_data->back_pos] = fd;
+ server_data->back_pos = next_back_pos;
+
+ return fd;
+}
+
+/*
+ * Wait for a connection to be queued to the FIFO and return it.
+ *
+ * Returns -1 if someone has already requested a shutdown.
+ */
+static int worker_thread__wait_for_connection(
+ struct ipc_worker_thread_data *worker_thread_data)
+{
+ /* ASSERT NOT holding mutex */
+
+ struct ipc_server_data *server_data = worker_thread_data->server_data;
+ int fd = -1;
+
+ pthread_mutex_lock(&server_data->work_available_mutex);
+ for (;;) {
+ if (server_data->shutdown_requested)
+ break;
+
+ fd = fifo_dequeue(server_data);
+ if (fd >= 0)
+ break;
+
+ pthread_cond_wait(&server_data->work_available_cond,
+ &server_data->work_available_mutex);
+ }
+ pthread_mutex_unlock(&server_data->work_available_mutex);
+
+ return fd;
+}
+
+/*
+ * Forward declare our reply callback function so that any compiler
+ * errors are reported when we actually define the function (in addition
+ * to any errors reported when we try to pass this callback function as
+ * a parameter in a function call). The former are easier to understand.
+ */
+static ipc_server_reply_cb do_io_reply_callback;
+
+/*
+ * Relay application's response message to the client process.
+ * (We do not flush at this point because we allow the caller
+ * to chunk data to the client thru us.)
+ */
+static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,
+ const char *response, size_t response_len)
+{
+ if (reply_data->magic != MAGIC_SERVER_REPLY_DATA)
+ BUG("reply_cb called with wrong instance data");
+
+ return write_packetized_from_buf_no_flush(response, response_len,
+ reply_data->fd);
+}
+
+/* A randomly chosen value. */
+#define MY_WAIT_POLL_TIMEOUT_MS (10)
+
+/*
+ * If the client hangs up without sending any data on the wire, just
+ * quietly close the socket and ignore this client.
+ *
+ * This worker thread is committed to reading the IPC request data
+ * from the client at the other end of this fd. Wait here for the
+ * client to actually put something on the wire -- because if the
+ * client just does a ping (connect and hangup without sending any
+ * data), our use of the pkt-line read routines will spew an error
+ * message.
+ *
+ * Return -1 if the client hung up.
+ * Return 0 if data (possibly incomplete) is ready.
+ */
+static int worker_thread__wait_for_io_start(
+ struct ipc_worker_thread_data *worker_thread_data,
+ int fd)
+{
+ struct ipc_server_data *server_data = worker_thread_data->server_data;
+ struct pollfd pollfd[1];
+ int result;
+
+ for (;;) {
+ pollfd[0].fd = fd;
+ pollfd[0].events = POLLIN;
+
+ result = poll(pollfd, 1, MY_WAIT_POLL_TIMEOUT_MS);
+ if (result < 0) {
+ if (errno == EINTR)
+ continue;
+ goto cleanup;
+ }
+
+ if (result == 0) {
+ /* a timeout */
+
+ int in_shutdown;
+
+ pthread_mutex_lock(&server_data->work_available_mutex);
+ in_shutdown = server_data->shutdown_requested;
+ pthread_mutex_unlock(&server_data->work_available_mutex);
+
+ /*
+ * If a shutdown is already in progress and this
+ * client has not started talking yet, just drop it.
+ */
+ if (in_shutdown)
+ goto cleanup;
+ continue;
+ }
+
+ if (pollfd[0].revents & POLLHUP)
+ goto cleanup;
+
+ if (pollfd[0].revents & POLLIN)
+ return 0;
+
+ goto cleanup;
+ }
+
+cleanup:
+ close(fd);
+ return -1;
+}
+
+/*
+ * Receive the request/command from the client and pass it to the
+ * registered request-callback. The request-callback will compose
+ * a response and call our reply-callback to send it to the client.
+ */
+static int worker_thread__do_io(
+ struct ipc_worker_thread_data *worker_thread_data,
+ int fd)
+{
+ /* ASSERT NOT holding lock */
+
+ struct strbuf buf = STRBUF_INIT;
+ struct ipc_server_reply_data reply_data;
+ int ret = 0;
+
+ reply_data.magic = MAGIC_SERVER_REPLY_DATA;
+ reply_data.worker_thread_data = worker_thread_data;
+
+ reply_data.fd = fd;
+
+ ret = read_packetized_to_strbuf(
+ reply_data.fd, &buf,
+ PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR);
+ 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);
+
+ packet_flush_gently(reply_data.fd);
+ }
+ else {
+ /*
+ * The client probably disconnected/shutdown before it
+ * could send a well-formed message. Ignore it.
+ */
+ }
+
+ strbuf_release(&buf);
+ close(reply_data.fd);
+
+ return ret;
+}
+
+/*
+ * Block SIGPIPE on the current thread (so that we get EPIPE from
+ * write() rather than an actual signal).
+ *
+ * Note that using sigchain_push() and _pop() to control SIGPIPE
+ * around our IO calls is not thread safe:
+ * [] It uses a global stack of handler frames.
+ * [] It uses ALLOC_GROW() to resize it.
+ * [] Finally, according to the `signal(2)` man-page:
+ * "The effects of `signal()` in a multithreaded process are unspecified."
+ */
+static void thread_block_sigpipe(sigset_t *old_set)
+{
+ sigset_t new_set;
+
+ sigemptyset(&new_set);
+ sigaddset(&new_set, SIGPIPE);
+
+ sigemptyset(old_set);
+ pthread_sigmask(SIG_BLOCK, &new_set, old_set);
+}
+
+/*
+ * Thread proc for an IPC worker thread. It handles a series of
+ * connections from clients. It pulls the next fd from the queue
+ * processes it, and then waits for the next client.
+ *
+ * Block SIGPIPE in this worker thread for the life of the thread.
+ * This avoids stray (and sometimes delayed) SIGPIPE signals caused
+ * by client errors and/or when we are under extremely heavy IO load.
+ *
+ * This means that the application callback will have SIGPIPE blocked.
+ * The callback should not change it.
+ */
+static void *worker_thread_proc(void *_worker_thread_data)
+{
+ struct ipc_worker_thread_data *worker_thread_data = _worker_thread_data;
+ struct ipc_server_data *server_data = worker_thread_data->server_data;
+ sigset_t old_set;
+ int fd, io;
+ int ret;
+
+ trace2_thread_start("ipc-worker");
+
+ thread_block_sigpipe(&old_set);
+
+ for (;;) {
+ fd = worker_thread__wait_for_connection(worker_thread_data);
+ if (fd == -1)
+ break; /* in shutdown */
+
+ io = worker_thread__wait_for_io_start(worker_thread_data, fd);
+ if (io == -1)
+ continue; /* client hung up without sending anything */
+
+ ret = worker_thread__do_io(worker_thread_data, fd);
+
+ if (ret == SIMPLE_IPC_QUIT) {
+ trace2_data_string("ipc-worker", NULL, "queue_stop_async",
+ "application_quit");
+ /*
+ * The application layer is telling the ipc-server
+ * layer to shutdown.
+ *
+ * We DO NOT have a response to send to the client.
+ *
+ * Queue an async stop (to stop the other threads) and
+ * allow this worker thread to exit now (no sense waiting
+ * for the thread-pool shutdown signal).
+ *
+ * Other non-idle worker threads are allowed to finish
+ * responding to their current clients.
+ */
+ ipc_server_stop_async(server_data);
+ break;
+ }
+ }
+
+ trace2_thread_exit();
+ return NULL;
+}
+
+/* A randomly chosen value. */
+#define MY_ACCEPT_POLL_TIMEOUT_MS (60 * 1000)
+
+/*
+ * Accept a new client connection on our socket. This uses non-blocking
+ * IO so that we can also wait for shutdown requests on our socket-pair
+ * without actually spinning on a fast timeout.
+ */
+static int accept_thread__wait_for_connection(
+ struct ipc_accept_thread_data *accept_thread_data)
+{
+ struct pollfd pollfd[2];
+ int result;
+
+ for (;;) {
+ pollfd[0].fd = accept_thread_data->fd_wait_shutdown;
+ pollfd[0].events = POLLIN;
+
+ pollfd[1].fd = accept_thread_data->server_socket->fd_socket;
+ pollfd[1].events = POLLIN;
+
+ result = poll(pollfd, 2, MY_ACCEPT_POLL_TIMEOUT_MS);
+ if (result < 0) {
+ if (errno == EINTR)
+ continue;
+ return result;
+ }
+
+ if (result == 0) {
+ /* a timeout */
+
+ /*
+ * If someone deletes or force-creates a new unix
+ * domain socket at our path, all future clients
+ * will be routed elsewhere and we silently starve.
+ * If that happens, just queue a shutdown.
+ */
+ if (unix_ss_was_stolen(
+ accept_thread_data->server_socket)) {
+ trace2_data_string("ipc-accept", NULL,
+ "queue_stop_async",
+ "socket_stolen");
+ ipc_server_stop_async(
+ accept_thread_data->server_data);
+ }
+ continue;
+ }
+
+ if (pollfd[0].revents & POLLIN) {
+ /* shutdown message queued to socketpair */
+ return -1;
+ }
+
+ if (pollfd[1].revents & POLLIN) {
+ /* a connection is available on server_socket */
+
+ int client_fd =
+ accept(accept_thread_data->server_socket->fd_socket,
+ NULL, NULL);
+ if (client_fd >= 0)
+ return client_fd;
+
+ /*
+ * An error here is unlikely -- it probably
+ * indicates that the connecting process has
+ * already dropped the connection.
+ */
+ continue;
+ }
+
+ BUG("unandled poll result errno=%d r[0]=%d r[1]=%d",
+ errno, pollfd[0].revents, pollfd[1].revents);
+ }
+}
+
+/*
+ * Thread proc for the IPC server "accept thread". This waits for
+ * an incoming socket connection, appends it to the queue of available
+ * connections, and notifies a worker thread to process it.
+ *
+ * Block SIGPIPE in this thread for the life of the thread. This
+ * avoids any stray SIGPIPE signals when closing pipe fds under
+ * extremely heavy loads (such as when the fifo queue is full and we
+ * drop incomming connections).
+ */
+static void *accept_thread_proc(void *_accept_thread_data)
+{
+ struct ipc_accept_thread_data *accept_thread_data = _accept_thread_data;
+ struct ipc_server_data *server_data = accept_thread_data->server_data;
+ sigset_t old_set;
+
+ trace2_thread_start("ipc-accept");
+
+ thread_block_sigpipe(&old_set);
+
+ for (;;) {
+ int client_fd = accept_thread__wait_for_connection(
+ accept_thread_data);
+
+ pthread_mutex_lock(&server_data->work_available_mutex);
+ if (server_data->shutdown_requested) {
+ pthread_mutex_unlock(&server_data->work_available_mutex);
+ if (client_fd >= 0)
+ close(client_fd);
+ break;
+ }
+
+ if (client_fd < 0) {
+ /* ignore transient accept() errors */
+ }
+ else {
+ fifo_enqueue(server_data, client_fd);
+ pthread_cond_broadcast(&server_data->work_available_cond);
+ }
+ pthread_mutex_unlock(&server_data->work_available_mutex);
+ }
+
+ trace2_thread_exit();
+ return NULL;
+}
+
+/*
+ * We can't predict the connection arrival rate relative to the worker
+ * processing rate, therefore we allow the "accept-thread" to queue up
+ * a generous number of connections, since we'd rather have the client
+ * not unnecessarily timeout if we can avoid it. (The assumption is
+ * that this will be used for FSMonitor and a few second wait on a
+ * connection is better than having the client timeout and do the full
+ * computation itself.)
+ *
+ * The FIFO queue size is set to a multiple of the worker pool size.
+ * This value chosen at random.
+ */
+#define FIFO_SCALE (100)
+
+/*
+ * The backlog value for `listen(2)`. This doesn't need to huge,
+ * rather just large enough for our "accept-thread" to wake up and
+ * queue incoming connections onto the FIFO without the kernel
+ * dropping any.
+ *
+ * This value chosen at random.
+ */
+#define LISTEN_BACKLOG (50)
+
+static int create_listener_socket(
+ const char *path,
+ const struct ipc_server_opts *ipc_opts,
+ struct unix_ss_socket **new_server_socket)
+{
+ struct unix_ss_socket *server_socket = NULL;
+ struct unix_stream_listen_opts uslg_opts = UNIX_STREAM_LISTEN_OPTS_INIT;
+ int ret;
+
+ uslg_opts.listen_backlog_size = LISTEN_BACKLOG;
+ uslg_opts.disallow_chdir = ipc_opts->uds_disallow_chdir;
+
+ ret = unix_ss_create(path, &uslg_opts, -1, &server_socket);
+ if (ret)
+ return ret;
+
+ if (set_socket_blocking_flag(server_socket->fd_socket, 1)) {
+ int saved_errno = errno;
+ unix_ss_free(server_socket);
+ errno = saved_errno;
+ return -1;
+ }
+
+ *new_server_socket = server_socket;
+
+ trace2_data_string("ipc-server", NULL, "listen-with-lock", path);
+ return 0;
+}
+
+static int setup_listener_socket(
+ const char *path,
+ const struct ipc_server_opts *ipc_opts,
+ struct unix_ss_socket **new_server_socket)
+{
+ int ret, saved_errno;
+
+ trace2_region_enter("ipc-server", "create-listener_socket", NULL);
+
+ ret = create_listener_socket(path, ipc_opts, new_server_socket);
+
+ saved_errno = errno;
+ trace2_region_leave("ipc-server", "create-listener_socket", NULL);
+ errno = saved_errno;
+
+ return ret;
+}
+
+/*
+ * Start IPC server in a pool of background threads.
+ */
+int ipc_server_run_async(struct ipc_server_data **returned_server_data,
+ const char *path, const struct ipc_server_opts *opts,
+ ipc_server_application_cb *application_cb,
+ void *application_data)
+{
+ struct unix_ss_socket *server_socket = NULL;
+ struct ipc_server_data *server_data;
+ int sv[2];
+ int k;
+ int ret;
+ int nr_threads = opts->nr_threads;
+
+ *returned_server_data = NULL;
+
+ /*
+ * Create a socketpair and set sv[1] to non-blocking. This
+ * will used to send a shutdown message to the accept-thread
+ * and allows the accept-thread to wait on EITHER a client
+ * connection or a shutdown request without spinning.
+ */
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0)
+ return -1;
+
+ if (set_socket_blocking_flag(sv[1], 1)) {
+ int saved_errno = errno;
+ close(sv[0]);
+ close(sv[1]);
+ errno = saved_errno;
+ return -1;
+ }
+
+ ret = setup_listener_socket(path, opts, &server_socket);
+ if (ret) {
+ int saved_errno = errno;
+ close(sv[0]);
+ close(sv[1]);
+ errno = saved_errno;
+ return ret;
+ }
+
+ server_data = xcalloc(1, sizeof(*server_data));
+ server_data->magic = MAGIC_SERVER_DATA;
+ server_data->application_cb = application_cb;
+ server_data->application_data = application_data;
+ strbuf_init(&server_data->buf_path, 0);
+ strbuf_addstr(&server_data->buf_path, path);
+
+ if (nr_threads < 1)
+ nr_threads = 1;
+
+ pthread_mutex_init(&server_data->work_available_mutex, NULL);
+ pthread_cond_init(&server_data->work_available_cond, NULL);
+
+ server_data->queue_size = nr_threads * FIFO_SCALE;
+ CALLOC_ARRAY(server_data->fifo_fds, server_data->queue_size);
+
+ server_data->accept_thread =
+ xcalloc(1, sizeof(*server_data->accept_thread));
+ server_data->accept_thread->magic = MAGIC_ACCEPT_THREAD_DATA;
+ server_data->accept_thread->server_data = server_data;
+ server_data->accept_thread->server_socket = server_socket;
+ server_data->accept_thread->fd_send_shutdown = sv[0];
+ server_data->accept_thread->fd_wait_shutdown = sv[1];
+
+ if (pthread_create(&server_data->accept_thread->pthread_id, NULL,
+ accept_thread_proc, server_data->accept_thread))
+ die_errno(_("could not start accept_thread '%s'"), path);
+
+ for (k = 0; k < nr_threads; k++) {
+ struct ipc_worker_thread_data *wtd;
+
+ wtd = xcalloc(1, sizeof(*wtd));
+ wtd->magic = MAGIC_WORKER_THREAD_DATA;
+ wtd->server_data = server_data;
+
+ if (pthread_create(&wtd->pthread_id, NULL, worker_thread_proc,
+ wtd)) {
+ if (k == 0)
+ die(_("could not start worker[0] for '%s'"),
+ path);
+ /*
+ * Limp along with the thread pool that we have.
+ */
+ break;
+ }
+
+ wtd->next_thread = server_data->worker_thread_list;
+ server_data->worker_thread_list = wtd;
+ }
+
+ *returned_server_data = server_data;
+ return 0;
+}
+
+/*
+ * Gently tell the IPC server treads to shutdown.
+ * Can be run on any thread.
+ */
+int ipc_server_stop_async(struct ipc_server_data *server_data)
+{
+ /* ASSERT NOT holding mutex */
+
+ int fd;
+
+ if (!server_data)
+ return 0;
+
+ trace2_region_enter("ipc-server", "server-stop-async", NULL);
+
+ pthread_mutex_lock(&server_data->work_available_mutex);
+
+ server_data->shutdown_requested = 1;
+
+ /*
+ * Write a byte to the shutdown socket pair to wake up the
+ * accept-thread.
+ */
+ if (write(server_data->accept_thread->fd_send_shutdown, "Q", 1) < 0)
+ error_errno("could not write to fd_send_shutdown");
+
+ /*
+ * Drain the queue of existing connections.
+ */
+ while ((fd = fifo_dequeue(server_data)) != -1)
+ close(fd);
+
+ /*
+ * Gently tell worker threads to stop processing new connections
+ * and exit. (This does not abort in-process conversations.)
+ */
+ pthread_cond_broadcast(&server_data->work_available_cond);
+
+ pthread_mutex_unlock(&server_data->work_available_mutex);
+
+ trace2_region_leave("ipc-server", "server-stop-async", NULL);
+
+ return 0;
+}
+
+/*
+ * Wait for all IPC server threads to stop.
+ */
+int ipc_server_await(struct ipc_server_data *server_data)
+{
+ pthread_join(server_data->accept_thread->pthread_id, NULL);
+
+ if (!server_data->shutdown_requested)
+ BUG("ipc-server: accept-thread stopped for '%s'",
+ server_data->buf_path.buf);
+
+ while (server_data->worker_thread_list) {
+ struct ipc_worker_thread_data *wtd =
+ server_data->worker_thread_list;
+
+ pthread_join(wtd->pthread_id, NULL);
+
+ server_data->worker_thread_list = wtd->next_thread;
+ free(wtd);
+ }
+
+ server_data->is_stopped = 1;
+
+ return 0;
+}
+
+void ipc_server_free(struct ipc_server_data *server_data)
+{
+ struct ipc_accept_thread_data * accept_thread_data;
+
+ if (!server_data)
+ return;
+
+ if (!server_data->is_stopped)
+ BUG("cannot free ipc-server while running for '%s'",
+ server_data->buf_path.buf);
+
+ accept_thread_data = server_data->accept_thread;
+ if (accept_thread_data) {
+ unix_ss_free(accept_thread_data->server_socket);
+
+ if (accept_thread_data->fd_send_shutdown != -1)
+ close(accept_thread_data->fd_send_shutdown);
+ if (accept_thread_data->fd_wait_shutdown != -1)
+ close(accept_thread_data->fd_wait_shutdown);
+
+ free(server_data->accept_thread);
+ }
+
+ while (server_data->worker_thread_list) {
+ struct ipc_worker_thread_data *wtd =
+ server_data->worker_thread_list;
+
+ server_data->worker_thread_list = wtd->next_thread;
+ free(wtd);
+ }
+
+ pthread_cond_destroy(&server_data->work_available_cond);
+ pthread_mutex_destroy(&server_data->work_available_mutex);
+
+ strbuf_release(&server_data->buf_path);
+
+ free(server_data->fifo_fds);
+ free(server_data);
+}
diff --git a/compat/simple-ipc/ipc-win32.c b/compat/simple-ipc/ipc-win32.c
new file mode 100644
index 0000000000..8dc7bda087
--- /dev/null
+++ b/compat/simple-ipc/ipc-win32.c
@@ -0,0 +1,755 @@
+#include "cache.h"
+#include "simple-ipc.h"
+#include "strbuf.h"
+#include "pkt-line.h"
+#include "thread-utils.h"
+
+#ifndef SUPPORTS_SIMPLE_IPC
+/*
+ * This source file should only be compiled when Simple IPC is supported.
+ * See the top-level Makefile.
+ */
+#error SUPPORTS_SIMPLE_IPC not defined
+#endif
+
+static int initialize_pipe_name(const char *path, wchar_t *wpath, size_t alloc)
+{
+ int off = 0;
+ struct strbuf realpath = STRBUF_INIT;
+
+ if (!strbuf_realpath(&realpath, path, 0))
+ return -1;
+
+ off = swprintf(wpath, alloc, L"\\\\.\\pipe\\");
+ if (xutftowcs(wpath + off, realpath.buf, alloc - off) < 0)
+ return -1;
+
+ /* Handle drive prefix */
+ if (wpath[off] && wpath[off + 1] == L':') {
+ wpath[off + 1] = L'_';
+ off += 2;
+ }
+
+ for (; wpath[off]; off++)
+ if (wpath[off] == L'/')
+ wpath[off] = L'\\';
+
+ strbuf_release(&realpath);
+ return 0;
+}
+
+static enum ipc_active_state get_active_state(wchar_t *pipe_path)
+{
+ if (WaitNamedPipeW(pipe_path, NMPWAIT_USE_DEFAULT_WAIT))
+ return IPC_STATE__LISTENING;
+
+ if (GetLastError() == ERROR_SEM_TIMEOUT)
+ return IPC_STATE__NOT_LISTENING;
+
+ if (GetLastError() == ERROR_FILE_NOT_FOUND)
+ return IPC_STATE__PATH_NOT_FOUND;
+
+ return IPC_STATE__OTHER_ERROR;
+}
+
+enum ipc_active_state ipc_get_active_state(const char *path)
+{
+ wchar_t pipe_path[MAX_PATH];
+
+ if (initialize_pipe_name(path, pipe_path, ARRAY_SIZE(pipe_path)) < 0)
+ return IPC_STATE__INVALID_PATH;
+
+ return get_active_state(pipe_path);
+}
+
+#define WAIT_STEP_MS (50)
+
+static enum ipc_active_state connect_to_server(
+ const wchar_t *wpath,
+ DWORD timeout_ms,
+ const struct ipc_client_connect_options *options,
+ int *pfd)
+{
+ DWORD t_start_ms, t_waited_ms;
+ DWORD step_ms;
+ HANDLE hPipe = INVALID_HANDLE_VALUE;
+ DWORD mode = PIPE_READMODE_BYTE;
+ DWORD gle;
+
+ *pfd = -1;
+
+ for (;;) {
+ hPipe = CreateFileW(wpath, GENERIC_READ | GENERIC_WRITE,
+ 0, NULL, OPEN_EXISTING, 0, NULL);
+ if (hPipe != INVALID_HANDLE_VALUE)
+ break;
+
+ gle = GetLastError();
+
+ switch (gle) {
+ case ERROR_FILE_NOT_FOUND:
+ if (!options->wait_if_not_found)
+ return IPC_STATE__PATH_NOT_FOUND;
+ if (!timeout_ms)
+ return IPC_STATE__PATH_NOT_FOUND;
+
+ step_ms = (timeout_ms < WAIT_STEP_MS) ?
+ timeout_ms : WAIT_STEP_MS;
+ sleep_millisec(step_ms);
+
+ timeout_ms -= step_ms;
+ break; /* try again */
+
+ case ERROR_PIPE_BUSY:
+ if (!options->wait_if_busy)
+ return IPC_STATE__NOT_LISTENING;
+ if (!timeout_ms)
+ return IPC_STATE__NOT_LISTENING;
+
+ t_start_ms = (DWORD)(getnanotime() / 1000000);
+
+ if (!WaitNamedPipeW(wpath, timeout_ms)) {
+ if (GetLastError() == ERROR_SEM_TIMEOUT)
+ return IPC_STATE__NOT_LISTENING;
+
+ return IPC_STATE__OTHER_ERROR;
+ }
+
+ /*
+ * A pipe server instance became available.
+ * Race other client processes to connect to
+ * it.
+ *
+ * But first decrement our overall timeout so
+ * that we don't starve if we keep losing the
+ * race. But also guard against special
+ * NPMWAIT_ values (0 and -1).
+ */
+ t_waited_ms = (DWORD)(getnanotime() / 1000000) - t_start_ms;
+ if (t_waited_ms < timeout_ms)
+ timeout_ms -= t_waited_ms;
+ else
+ timeout_ms = 1;
+ break; /* try again */
+
+ default:
+ return IPC_STATE__OTHER_ERROR;
+ }
+ }
+
+ if (!SetNamedPipeHandleState(hPipe, &mode, NULL, NULL)) {
+ CloseHandle(hPipe);
+ return IPC_STATE__OTHER_ERROR;
+ }
+
+ *pfd = _open_osfhandle((intptr_t)hPipe, O_RDWR|O_BINARY);
+ if (*pfd < 0) {
+ CloseHandle(hPipe);
+ return IPC_STATE__OTHER_ERROR;
+ }
+
+ /* fd now owns hPipe */
+
+ return IPC_STATE__LISTENING;
+}
+
+/*
+ * The default connection timeout for Windows clients.
+ *
+ * This is not currently part of the ipc_ API (nor the config settings)
+ * because of differences between Windows and other platforms.
+ *
+ * This value was chosen at random.
+ */
+#define WINDOWS_CONNECTION_TIMEOUT_MS (30000)
+
+enum ipc_active_state ipc_client_try_connect(
+ const char *path,
+ const struct ipc_client_connect_options *options,
+ struct ipc_client_connection **p_connection)
+{
+ wchar_t wpath[MAX_PATH];
+ enum ipc_active_state state = IPC_STATE__OTHER_ERROR;
+ int fd = -1;
+
+ *p_connection = NULL;
+
+ trace2_region_enter("ipc-client", "try-connect", NULL);
+ trace2_data_string("ipc-client", NULL, "try-connect/path", path);
+
+ if (initialize_pipe_name(path, wpath, ARRAY_SIZE(wpath)) < 0)
+ state = IPC_STATE__INVALID_PATH;
+ else
+ state = connect_to_server(wpath, WINDOWS_CONNECTION_TIMEOUT_MS,
+ options, &fd);
+
+ trace2_data_intmax("ipc-client", NULL, "try-connect/state",
+ (intmax_t)state);
+ trace2_region_leave("ipc-client", "try-connect", NULL);
+
+ if (state == IPC_STATE__LISTENING) {
+ (*p_connection) = xcalloc(1, sizeof(struct ipc_client_connection));
+ (*p_connection)->fd = fd;
+ }
+
+ return state;
+}
+
+void ipc_client_close_connection(struct ipc_client_connection *connection)
+{
+ if (!connection)
+ return;
+
+ if (connection->fd != -1)
+ close(connection->fd);
+
+ free(connection);
+}
+
+int ipc_client_send_command_to_connection(
+ struct ipc_client_connection *connection,
+ const char *message, struct strbuf *answer)
+{
+ int ret = 0;
+
+ strbuf_setlen(answer, 0);
+
+ trace2_region_enter("ipc-client", "send-command", NULL);
+
+ if (write_packetized_from_buf_no_flush(message, strlen(message),
+ connection->fd) < 0 ||
+ packet_flush_gently(connection->fd) < 0) {
+ ret = error(_("could not send IPC command"));
+ goto done;
+ }
+
+ FlushFileBuffers((HANDLE)_get_osfhandle(connection->fd));
+
+ if (read_packetized_to_strbuf(
+ connection->fd, answer,
+ PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR) < 0) {
+ ret = error(_("could not read IPC response"));
+ goto done;
+ }
+
+done:
+ trace2_region_leave("ipc-client", "send-command", NULL);
+ return ret;
+}
+
+int ipc_client_send_command(const char *path,
+ const struct ipc_client_connect_options *options,
+ const char *message, struct strbuf *response)
+{
+ int ret = -1;
+ enum ipc_active_state state;
+ struct ipc_client_connection *connection = NULL;
+
+ state = ipc_client_try_connect(path, options, &connection);
+
+ if (state != IPC_STATE__LISTENING)
+ return ret;
+
+ ret = ipc_client_send_command_to_connection(connection, message, response);
+
+ ipc_client_close_connection(connection);
+
+ return ret;
+}
+
+/*
+ * Duplicate the given pipe handle and wrap it in a file descriptor so
+ * that we can use pkt-line on it.
+ */
+static int dup_fd_from_pipe(const HANDLE pipe)
+{
+ HANDLE process = GetCurrentProcess();
+ HANDLE handle;
+ int fd;
+
+ if (!DuplicateHandle(process, pipe, process, &handle, 0, FALSE,
+ DUPLICATE_SAME_ACCESS)) {
+ errno = err_win_to_posix(GetLastError());
+ return -1;
+ }
+
+ fd = _open_osfhandle((intptr_t)handle, O_RDWR|O_BINARY);
+ if (fd < 0) {
+ errno = err_win_to_posix(GetLastError());
+ CloseHandle(handle);
+ return -1;
+ }
+
+ /*
+ * `handle` is now owned by `fd` and will be automatically closed
+ * when the descriptor is closed.
+ */
+
+ return fd;
+}
+
+/*
+ * Magic numbers used to annotate callback instance data.
+ * These are used to help guard against accidentally passing the
+ * wrong instance data across multiple levels of callbacks (which
+ * is easy to do if there are `void*` arguments).
+ */
+enum magic {
+ MAGIC_SERVER_REPLY_DATA,
+ MAGIC_SERVER_THREAD_DATA,
+ MAGIC_SERVER_DATA,
+};
+
+struct ipc_server_reply_data {
+ enum magic magic;
+ int fd;
+ struct ipc_server_thread_data *server_thread_data;
+};
+
+struct ipc_server_thread_data {
+ enum magic magic;
+ struct ipc_server_thread_data *next_thread;
+ struct ipc_server_data *server_data;
+ pthread_t pthread_id;
+ HANDLE hPipe;
+};
+
+/*
+ * On Windows, the conceptual "ipc-server" is implemented as a pool of
+ * n idential/peer "server-thread" threads. That is, there is no
+ * hierarchy of threads; and therefore no controller thread managing
+ * the pool. Each thread has an independent handle to the named pipe,
+ * receives incoming connections, processes the client, and re-uses
+ * the pipe for the next client connection.
+ *
+ * Therefore, the "ipc-server" only needs to maintain a list of the
+ * spawned threads for eventual "join" purposes.
+ *
+ * A single "stop-event" is visible to all of the server threads to
+ * tell them to shutdown (when idle).
+ */
+struct ipc_server_data {
+ enum magic magic;
+ ipc_server_application_cb *application_cb;
+ void *application_data;
+ struct strbuf buf_path;
+ wchar_t wpath[MAX_PATH];
+
+ HANDLE hEventStopRequested;
+ struct ipc_server_thread_data *thread_list;
+ int is_stopped;
+};
+
+enum connect_result {
+ CR_CONNECTED = 0,
+ CR_CONNECT_PENDING,
+ CR_CONNECT_ERROR,
+ CR_WAIT_ERROR,
+ CR_SHUTDOWN,
+};
+
+static enum connect_result queue_overlapped_connect(
+ struct ipc_server_thread_data *server_thread_data,
+ OVERLAPPED *lpo)
+{
+ if (ConnectNamedPipe(server_thread_data->hPipe, lpo))
+ goto failed;
+
+ switch (GetLastError()) {
+ case ERROR_IO_PENDING:
+ return CR_CONNECT_PENDING;
+
+ case ERROR_PIPE_CONNECTED:
+ SetEvent(lpo->hEvent);
+ return CR_CONNECTED;
+
+ default:
+ break;
+ }
+
+failed:
+ error(_("ConnectNamedPipe failed for '%s' (%lu)"),
+ server_thread_data->server_data->buf_path.buf,
+ GetLastError());
+ return CR_CONNECT_ERROR;
+}
+
+/*
+ * Use Windows Overlapped IO to wait for a connection or for our event
+ * to be signalled.
+ */
+static enum connect_result wait_for_connection(
+ struct ipc_server_thread_data *server_thread_data,
+ OVERLAPPED *lpo)
+{
+ enum connect_result r;
+ HANDLE waitHandles[2];
+ DWORD dwWaitResult;
+
+ r = queue_overlapped_connect(server_thread_data, lpo);
+ if (r != CR_CONNECT_PENDING)
+ return r;
+
+ waitHandles[0] = server_thread_data->server_data->hEventStopRequested;
+ waitHandles[1] = lpo->hEvent;
+
+ dwWaitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);
+ switch (dwWaitResult) {
+ case WAIT_OBJECT_0 + 0:
+ return CR_SHUTDOWN;
+
+ case WAIT_OBJECT_0 + 1:
+ ResetEvent(lpo->hEvent);
+ return CR_CONNECTED;
+
+ default:
+ return CR_WAIT_ERROR;
+ }
+}
+
+/*
+ * Forward declare our reply callback function so that any compiler
+ * errors are reported when we actually define the function (in addition
+ * to any errors reported when we try to pass this callback function as
+ * a parameter in a function call). The former are easier to understand.
+ */
+static ipc_server_reply_cb do_io_reply_callback;
+
+/*
+ * Relay application's response message to the client process.
+ * (We do not flush at this point because we allow the caller
+ * to chunk data to the client thru us.)
+ */
+static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,
+ const char *response, size_t response_len)
+{
+ if (reply_data->magic != MAGIC_SERVER_REPLY_DATA)
+ BUG("reply_cb called with wrong instance data");
+
+ return write_packetized_from_buf_no_flush(response, response_len,
+ reply_data->fd);
+}
+
+/*
+ * Receive the request/command from the client and pass it to the
+ * registered request-callback. The request-callback will compose
+ * a response and call our reply-callback to send it to the client.
+ *
+ * Simple-IPC only contains one round trip, so we flush and close
+ * here after the response.
+ */
+static int do_io(struct ipc_server_thread_data *server_thread_data)
+{
+ struct strbuf buf = STRBUF_INIT;
+ struct ipc_server_reply_data reply_data;
+ int ret = 0;
+
+ reply_data.magic = MAGIC_SERVER_REPLY_DATA;
+ reply_data.server_thread_data = server_thread_data;
+
+ reply_data.fd = dup_fd_from_pipe(server_thread_data->hPipe);
+ if (reply_data.fd < 0)
+ return error(_("could not create fd from pipe for '%s'"),
+ server_thread_data->server_data->buf_path.buf);
+
+ ret = read_packetized_to_strbuf(
+ reply_data.fd, &buf,
+ PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR);
+ 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);
+
+ packet_flush_gently(reply_data.fd);
+
+ FlushFileBuffers((HANDLE)_get_osfhandle((reply_data.fd)));
+ }
+ else {
+ /*
+ * The client probably disconnected/shutdown before it
+ * could send a well-formed message. Ignore it.
+ */
+ }
+
+ strbuf_release(&buf);
+ close(reply_data.fd);
+
+ return ret;
+}
+
+/*
+ * Handle IPC request and response with this connected client. And reset
+ * the pipe to prepare for the next client.
+ */
+static int use_connection(struct ipc_server_thread_data *server_thread_data)
+{
+ int ret;
+
+ ret = do_io(server_thread_data);
+
+ FlushFileBuffers(server_thread_data->hPipe);
+ DisconnectNamedPipe(server_thread_data->hPipe);
+
+ return ret;
+}
+
+/*
+ * Thread proc for an IPC server worker thread. It handles a series of
+ * connections from clients. It cleans and reuses the hPipe between each
+ * client.
+ */
+static void *server_thread_proc(void *_server_thread_data)
+{
+ struct ipc_server_thread_data *server_thread_data = _server_thread_data;
+ HANDLE hEventConnected = INVALID_HANDLE_VALUE;
+ OVERLAPPED oConnect;
+ enum connect_result cr;
+ int ret;
+
+ assert(server_thread_data->hPipe != INVALID_HANDLE_VALUE);
+
+ trace2_thread_start("ipc-server");
+ trace2_data_string("ipc-server", NULL, "pipe",
+ server_thread_data->server_data->buf_path.buf);
+
+ hEventConnected = CreateEventW(NULL, TRUE, FALSE, NULL);
+
+ memset(&oConnect, 0, sizeof(oConnect));
+ oConnect.hEvent = hEventConnected;
+
+ for (;;) {
+ cr = wait_for_connection(server_thread_data, &oConnect);
+
+ switch (cr) {
+ case CR_SHUTDOWN:
+ goto finished;
+
+ case CR_CONNECTED:
+ ret = use_connection(server_thread_data);
+ if (ret == SIMPLE_IPC_QUIT) {
+ ipc_server_stop_async(
+ server_thread_data->server_data);
+ goto finished;
+ }
+ if (ret > 0) {
+ /*
+ * Ignore (transient) IO errors with this
+ * client and reset for the next client.
+ */
+ }
+ break;
+
+ case CR_CONNECT_PENDING:
+ /* By construction, this should not happen. */
+ BUG("ipc-server[%s]: unexpeced CR_CONNECT_PENDING",
+ server_thread_data->server_data->buf_path.buf);
+
+ case CR_CONNECT_ERROR:
+ case CR_WAIT_ERROR:
+ /*
+ * Ignore these theoretical errors.
+ */
+ DisconnectNamedPipe(server_thread_data->hPipe);
+ break;
+
+ default:
+ BUG("unandled case after wait_for_connection");
+ }
+ }
+
+finished:
+ CloseHandle(server_thread_data->hPipe);
+ CloseHandle(hEventConnected);
+
+ trace2_thread_exit();
+ return NULL;
+}
+
+static HANDLE create_new_pipe(wchar_t *wpath, int is_first)
+{
+ HANDLE hPipe;
+ DWORD dwOpenMode, dwPipeMode;
+ LPSECURITY_ATTRIBUTES lpsa = NULL;
+
+ dwOpenMode = PIPE_ACCESS_INBOUND | PIPE_ACCESS_OUTBOUND |
+ FILE_FLAG_OVERLAPPED;
+
+ dwPipeMode = PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE | PIPE_WAIT |
+ PIPE_REJECT_REMOTE_CLIENTS;
+
+ if (is_first) {
+ dwOpenMode |= FILE_FLAG_FIRST_PIPE_INSTANCE;
+
+ /*
+ * On Windows, the first server pipe instance gets to
+ * 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.
+ */
+ }
+
+ hPipe = CreateNamedPipeW(wpath, dwOpenMode, dwPipeMode,
+ PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, lpsa);
+
+ return hPipe;
+}
+
+int ipc_server_run_async(struct ipc_server_data **returned_server_data,
+ const char *path, const struct ipc_server_opts *opts,
+ ipc_server_application_cb *application_cb,
+ void *application_data)
+{
+ struct ipc_server_data *server_data;
+ wchar_t wpath[MAX_PATH];
+ HANDLE hPipeFirst = INVALID_HANDLE_VALUE;
+ int k;
+ int ret = 0;
+ int nr_threads = opts->nr_threads;
+
+ *returned_server_data = NULL;
+
+ ret = initialize_pipe_name(path, wpath, ARRAY_SIZE(wpath));
+ if (ret < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ hPipeFirst = create_new_pipe(wpath, 1);
+ if (hPipeFirst == INVALID_HANDLE_VALUE) {
+ errno = EADDRINUSE;
+ return -2;
+ }
+
+ server_data = xcalloc(1, sizeof(*server_data));
+ server_data->magic = MAGIC_SERVER_DATA;
+ server_data->application_cb = application_cb;
+ server_data->application_data = application_data;
+ server_data->hEventStopRequested = CreateEvent(NULL, TRUE, FALSE, NULL);
+ strbuf_init(&server_data->buf_path, 0);
+ strbuf_addstr(&server_data->buf_path, path);
+ wcscpy(server_data->wpath, wpath);
+
+ if (nr_threads < 1)
+ nr_threads = 1;
+
+ for (k = 0; k < nr_threads; k++) {
+ struct ipc_server_thread_data *std;
+
+ std = xcalloc(1, sizeof(*std));
+ std->magic = MAGIC_SERVER_THREAD_DATA;
+ std->server_data = server_data;
+ std->hPipe = INVALID_HANDLE_VALUE;
+
+ std->hPipe = (k == 0)
+ ? hPipeFirst
+ : create_new_pipe(server_data->wpath, 0);
+
+ if (std->hPipe == INVALID_HANDLE_VALUE) {
+ /*
+ * If we've reached a pipe instance limit for
+ * this path, just use fewer threads.
+ */
+ free(std);
+ break;
+ }
+
+ if (pthread_create(&std->pthread_id, NULL,
+ server_thread_proc, std)) {
+ /*
+ * Likewise, if we're out of threads, just use
+ * fewer threads than requested.
+ *
+ * However, we just give up if we can't even get
+ * one thread. This should not happen.
+ */
+ if (k == 0)
+ die(_("could not start thread[0] for '%s'"),
+ path);
+
+ CloseHandle(std->hPipe);
+ free(std);
+ break;
+ }
+
+ std->next_thread = server_data->thread_list;
+ server_data->thread_list = std;
+ }
+
+ *returned_server_data = server_data;
+ return 0;
+}
+
+int ipc_server_stop_async(struct ipc_server_data *server_data)
+{
+ if (!server_data)
+ return 0;
+
+ /*
+ * Gently tell all of the ipc_server threads to shutdown.
+ * This will be seen the next time they are idle (and waiting
+ * for a connection).
+ *
+ * We DO NOT attempt to force them to drop an active connection.
+ */
+ SetEvent(server_data->hEventStopRequested);
+ return 0;
+}
+
+int ipc_server_await(struct ipc_server_data *server_data)
+{
+ DWORD dwWaitResult;
+
+ if (!server_data)
+ return 0;
+
+ dwWaitResult = WaitForSingleObject(server_data->hEventStopRequested, INFINITE);
+ if (dwWaitResult != WAIT_OBJECT_0)
+ return error(_("wait for hEvent failed for '%s'"),
+ server_data->buf_path.buf);
+
+ while (server_data->thread_list) {
+ struct ipc_server_thread_data *std = server_data->thread_list;
+
+ pthread_join(std->pthread_id, NULL);
+
+ server_data->thread_list = std->next_thread;
+ free(std);
+ }
+
+ server_data->is_stopped = 1;
+
+ return 0;
+}
+
+void ipc_server_free(struct ipc_server_data *server_data)
+{
+ if (!server_data)
+ return;
+
+ if (!server_data->is_stopped)
+ BUG("cannot free ipc-server while running for '%s'",
+ server_data->buf_path.buf);
+
+ strbuf_release(&server_data->buf_path);
+
+ if (server_data->hEventStopRequested != INVALID_HANDLE_VALUE)
+ CloseHandle(server_data->hEventStopRequested);
+
+ while (server_data->thread_list) {
+ struct ipc_server_thread_data *std = server_data->thread_list;
+
+ server_data->thread_list = std->next_thread;
+ free(std);
+ }
+
+ free(server_data);
+}
diff --git a/config.c b/config.c
index f90b633dba..f9c400ad30 100644
--- a/config.c
+++ b/config.c
@@ -1180,20 +1180,6 @@ static void die_bad_number(const char *name, const char *value)
}
}
-NORETURN
-static void die_bad_bool(const char *name, const char *value)
-{
- if (!strcmp(name, "GIT_TEST_GETTEXT_POISON"))
- /*
- * We explicitly *don't* use _() here since it would
- * cause an infinite loop with _() needing to call
- * use_gettext_poison().
- */
- die("bad boolean config value '%s' for '%s'", value, name);
- else
- die(_("bad boolean config value '%s' for '%s'"), value, name);
-}
-
int git_config_int(const char *name, const char *value)
{
int ret;
@@ -1268,7 +1254,7 @@ int git_config_bool(const char *name, const char *value)
{
int v = git_parse_maybe_bool(value);
if (v < 0)
- die_bad_bool(name, value);
+ die(_("bad boolean config value '%s' for '%s'"), value, name);
return v;
}
@@ -1844,12 +1830,26 @@ static int git_config_from_blob_ref(config_fn_t fn,
return git_config_from_blob_oid(fn, name, &oid, data);
}
-const char *git_etc_gitconfig(void)
+char *git_system_config(void)
+{
+ char *system_config = xstrdup_or_null(getenv("GIT_CONFIG_SYSTEM"));
+ if (system_config)
+ return system_config;
+ return system_path(ETC_GITCONFIG);
+}
+
+void git_global_config(char **user_out, char **xdg_out)
{
- static const char *system_wide;
- if (!system_wide)
- system_wide = system_path(ETC_GITCONFIG);
- return system_wide;
+ char *user_config = xstrdup_or_null(getenv("GIT_CONFIG_GLOBAL"));
+ char *xdg_config = NULL;
+
+ if (!user_config) {
+ user_config = expand_user_path("~/.gitconfig", 0);
+ xdg_config = xdg_config_home("config");
+ }
+
+ *user_out = user_config;
+ *xdg_out = xdg_config;
}
/*
@@ -1883,8 +1883,9 @@ static int do_git_config_sequence(const struct config_options *opts,
config_fn_t fn, void *data)
{
int ret = 0;
- char *xdg_config = xdg_config_home("config");
- char *user_config = expand_user_path("~/.gitconfig", 0);
+ char *system_config = git_system_config();
+ char *xdg_config = NULL;
+ char *user_config = NULL;
char *repo_config;
enum config_scope prev_parsing_scope = current_parsing_scope;
@@ -1896,13 +1897,14 @@ static int do_git_config_sequence(const struct config_options *opts,
repo_config = NULL;
current_parsing_scope = CONFIG_SCOPE_SYSTEM;
- if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK,
- opts->system_gently ?
- ACCESS_EACCES_OK : 0))
- ret += git_config_from_file(fn, git_etc_gitconfig(),
- data);
+ if (git_config_system() && system_config &&
+ !access_or_die(system_config, R_OK,
+ opts->system_gently ? ACCESS_EACCES_OK : 0))
+ ret += git_config_from_file(fn, system_config, data);
current_parsing_scope = CONFIG_SCOPE_GLOBAL;
+ git_global_config(&user_config, &xdg_config);
+
if (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))
ret += git_config_from_file(fn, xdg_config, data);
@@ -1927,6 +1929,7 @@ static int do_git_config_sequence(const struct config_options *opts,
die(_("unable to parse command-line config"));
current_parsing_scope = prev_parsing_scope;
+ free(system_config);
free(xdg_config);
free(user_config);
free(repo_config);
@@ -2269,7 +2272,7 @@ static void repo_read_config(struct repository *repo)
opts.git_dir = repo->gitdir;
if (!repo->config)
- repo->config = xcalloc(1, sizeof(struct config_set));
+ CALLOC_ARRAY(repo->config, 1);
else
git_configset_clear(repo->config);
diff --git a/config.h b/config.h
index 19a9adbaa9..9038538ffd 100644
--- a/config.h
+++ b/config.h
@@ -318,7 +318,6 @@ int git_config_rename_section(const char *, const char *);
int git_config_rename_section_in_file(const char *, const char *, const char *);
int git_config_copy_section(const char *, const char *);
int git_config_copy_section_in_file(const char *, const char *, const char *);
-const char *git_etc_gitconfig(void);
int git_env_bool(const char *, int);
unsigned long git_env_ulong(const char *, unsigned long);
int git_config_system(void);
@@ -327,6 +326,9 @@ int config_error_nonbool(const char *);
#define config_error_nonbool(s) (config_error_nonbool(s), const_error())
#endif
+char *git_system_config(void);
+void git_global_config(char **user, char **xdg);
+
int git_config_parse_parameter(const char *, config_fn_t fn, void *data);
enum config_scope current_config_scope(void);
diff --git a/config.mak.uname b/config.mak.uname
index d204c20a64..cb443b4e02 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -424,6 +424,7 @@ ifeq ($(uname_S),Windows)
RUNTIME_PREFIX = YesPlease
HAVE_WPGMPTR = YesWeDo
NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
+ USE_WIN32_IPC = YesPlease
USE_WIN32_MMAP = YesPlease
MMAP_PREVENTS_DELETE = UnfortunatelyYes
# USE_NED_ALLOCATOR = YesPlease
@@ -600,6 +601,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
RUNTIME_PREFIX = YesPlease
HAVE_WPGMPTR = YesWeDo
NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
+ USE_WIN32_IPC = YesPlease
USE_WIN32_MMAP = YesPlease
MMAP_PREVENTS_DELETE = UnfortunatelyYes
USE_NED_ALLOCATOR = YesPlease
diff --git a/connect.c b/connect.c
index 40b5c15f81..70b13389ba 100644
--- a/connect.c
+++ b/connect.c
@@ -254,7 +254,7 @@ static int process_dummy_ref(const struct packet_reader *reader)
return 0;
name++;
- return oideq(&null_oid, &oid) && !strcmp(name, "capabilities^{}");
+ return oideq(null_oid(), &oid) && !strcmp(name, "capabilities^{}");
}
static void check_no_capabilities(const char *line, int len)
diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt
index ac3dbc079a..a87841340e 100644
--- a/contrib/buildsystems/CMakeLists.txt
+++ b/contrib/buildsystems/CMakeLists.txt
@@ -58,6 +58,10 @@ if(WIN32)
# In the vcpkg edition, we need this to be able to link to libcurl
set(CURL_NO_CURL_CMAKE ON)
+
+ # Copy the necessary vcpkg DLLs (like iconv) to the install dir
+ set(X_VCPKG_APPLOCAL_DEPS_INSTALL ON)
+ set(CMAKE_TOOLCHAIN_FILE ${VCPKG_DIR}/scripts/buildsystems/vcpkg.cmake CACHE STRING "Vcpkg toolchain file")
endif()
find_program(SH_EXE sh PATHS "C:/Program Files/Git/bin")
@@ -243,7 +247,20 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
add_compile_definitions(PROCFS_EXECUTABLE_PATH="/proc/self/exe" HAVE_DEV_TTY )
- list(APPEND compat_SOURCES unix-socket.c)
+ list(APPEND compat_SOURCES unix-socket.c unix-stream-server.c)
+endif()
+
+if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
+ list(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c)
+ add_compile_definitions(SUPPORTS_SIMPLE_IPC)
+ set(SUPPORTS_SIMPLE_IPC 1)
+else()
+ # Simple IPC requires both Unix sockets and pthreads on Unix-based systems.
+ if(NOT NO_UNIX_SOCKETS AND NOT NO_PTHREADS)
+ list(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-unix-socket.c)
+ add_compile_definitions(SUPPORTS_SIMPLE_IPC)
+ set(SUPPORTS_SIMPLE_IPC 1)
+ endif()
endif()
set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX})
@@ -685,13 +702,17 @@ endif()
parse_makefile_for_executables(git_builtin_extra "BUILT_INS")
+option(SKIP_DASHED_BUILT_INS "Skip hardlinking the dashed versions of the built-ins")
+
#Creating hardlinks
+if(NOT SKIP_DASHED_BUILT_INS)
foreach(s ${git_SOURCES} ${git_builtin_extra})
string(REPLACE "${CMAKE_SOURCE_DIR}/builtin/" "" s ${s})
string(REPLACE ".c" "" s ${s})
file(APPEND ${CMAKE_BINARY_DIR}/CreateLinks.cmake "file(CREATE_LINK git${EXE_EXTENSION} git-${s}${EXE_EXTENSION})\n")
list(APPEND git_links ${CMAKE_BINARY_DIR}/git-${s}${EXE_EXTENSION})
endforeach()
+endif()
if(CURL_FOUND)
set(remote_exes
@@ -807,15 +828,19 @@ list(TRANSFORM git_shell_scripts PREPEND "${CMAKE_BINARY_DIR}/")
list(TRANSFORM git_perl_scripts PREPEND "${CMAKE_BINARY_DIR}/")
#install
-install(TARGETS git git-shell
+foreach(program ${PROGRAMS_BUILT})
+if(program STREQUAL "git" OR program STREQUAL "git-shell")
+install(TARGETS ${program}
RUNTIME DESTINATION bin)
+else()
+install(TARGETS ${program}
+ RUNTIME DESTINATION libexec/git-core)
+endif()
+endforeach()
+
install(PROGRAMS ${CMAKE_BINARY_DIR}/git-cvsserver
DESTINATION bin)
-list(REMOVE_ITEM PROGRAMS_BUILT git git-shell)
-install(TARGETS ${PROGRAMS_BUILT}
- RUNTIME DESTINATION libexec/git-core)
-
set(bin_links
git-receive-pack git-upload-archive git-upload-pack)
@@ -828,12 +853,12 @@ install(CODE "file(CREATE_LINK ${CMAKE_INSTALL_PREFIX}/bin/git-shell${EXE_EXTENS
foreach(b ${git_links})
string(REPLACE "${CMAKE_BINARY_DIR}" "" b ${b})
- install(CODE "file(CREATE_LINK ${CMAKE_INSTALL_PREFIX}/bin/git${EXE_EXTENSION} ${CMAKE_INSTALL_PREFIX}/libexec/git-core/${b}${EXE_EXTENSION})")
+ install(CODE "file(CREATE_LINK ${CMAKE_INSTALL_PREFIX}/bin/git${EXE_EXTENSION} ${CMAKE_INSTALL_PREFIX}/libexec/git-core/${b})")
endforeach()
foreach(b ${git_http_links})
string(REPLACE "${CMAKE_BINARY_DIR}" "" b ${b})
- install(CODE "file(CREATE_LINK ${CMAKE_INSTALL_PREFIX}/libexec/git-core/git-remote-http${EXE_EXTENSION} ${CMAKE_INSTALL_PREFIX}/libexec/git-core/${b}${EXE_EXTENSION})")
+ install(CODE "file(CREATE_LINK ${CMAKE_INSTALL_PREFIX}/libexec/git-core/git-remote-http${EXE_EXTENSION} ${CMAKE_INSTALL_PREFIX}/libexec/git-core/${b})")
endforeach()
install(PROGRAMS ${git_shell_scripts} ${git_perl_scripts} ${CMAKE_BINARY_DIR}/git-p4
@@ -956,6 +981,7 @@ file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "X='${EXE_EXTENSION}'\n")
file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_GETTEXT='${NO_GETTEXT}'\n")
file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "RUNTIME_PREFIX='${RUNTIME_PREFIX}'\n")
file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_PYTHON='${NO_PYTHON}'\n")
+file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "SUPPORTS_SIMPLE_IPC='${SUPPORTS_SIMPLE_IPC}'\n")
if(WIN32)
file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "PATH=\"$PATH:$TEST_DIRECTORY/../compat/vcbuild/vcpkg/installed/x64-windows/bin\"\n")
endif()
diff --git a/contrib/coccinelle/array.cocci b/contrib/coccinelle/array.cocci
index 46b8d2ee11..9a4f00cb1b 100644
--- a/contrib/coccinelle/array.cocci
+++ b/contrib/coccinelle/array.cocci
@@ -88,3 +88,11 @@ expression n;
@@
- ptr = xmalloc((n) * sizeof(T));
+ ALLOC_ARRAY(ptr, n);
+
+@@
+type T;
+T *ptr;
+expression n != 1;
+@@
+- ptr = xcalloc(n, \( sizeof(*ptr) \| sizeof(T) \) )
++ CALLOC_ARRAY(ptr, n)
diff --git a/contrib/coccinelle/xcalloc.cocci b/contrib/coccinelle/xcalloc.cocci
new file mode 100644
index 0000000000..c291011607
--- /dev/null
+++ b/contrib/coccinelle/xcalloc.cocci
@@ -0,0 +1,10 @@
+@@
+type T;
+T *ptr;
+expression n;
+@@
+ xcalloc(
++ n,
+ \( sizeof(T) \| sizeof(*ptr) \)
+- , n
+ )
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 7dc6cd8eb8..3c5739b905 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -77,7 +77,7 @@ __git_find_repo_path ()
test -d "$__git_dir" &&
__git_repo_path="$__git_dir"
elif [ -n "${GIT_DIR-}" ]; then
- test -d "${GIT_DIR-}" &&
+ test -d "$GIT_DIR" &&
__git_repo_path="$GIT_DIR"
elif [ -d .git ]; then
__git_repo_path=.git
@@ -427,7 +427,7 @@ __gitcomp_builtin ()
if [ -z "$options" ]; then
local completion_helper
- if [ "$GIT_COMPLETION_SHOW_ALL" = "1" ]; then
+ if [ "${GIT_COMPLETION_SHOW_ALL-}" = "1" ]; then
completion_helper="--git-completion-helper-all"
else
completion_helper="--git-completion-helper"
@@ -744,7 +744,7 @@ __git_refs ()
track=""
;;
*)
- for i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD REBASE_HEAD; do
+ for i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD REBASE_HEAD CHERRY_PICK_HEAD; do
case "$i" in
$match*)
if [ -e "$dir/$i" ]; then
@@ -1006,8 +1006,8 @@ __git_complete_revlist ()
__git_complete_remote_or_refspec ()
{
- local cur_="$cur" cmd="${words[1]}"
- local i c=2 remote="" pfx="" lhs=1 no_complete_refspec=0
+ local cur_="$cur" cmd="${words[__git_cmd_idx]}"
+ local i c=$((__git_cmd_idx+1)) remote="" pfx="" lhs=1 no_complete_refspec=0
if [ "$cmd" = "remote" ]; then
((c++))
fi
@@ -1129,7 +1129,7 @@ __git_pretty_aliases ()
# __git_aliased_command requires 1 argument
__git_aliased_command ()
{
- local cur=$1 last list word cmdline
+ local cur=$1 last list= word cmdline
while [[ -n "$cur" ]]; do
if [[ "$list" == *" $cur "* ]]; then
@@ -1176,7 +1176,7 @@ __git_aliased_command ()
# --show-idx: Optionally show the index of the found word in the $words array.
__git_find_on_cmdline ()
{
- local word c=1 show_idx
+ local word c="$__git_cmd_idx" show_idx
while test $# -gt 1; do
case "$1" in
@@ -1221,7 +1221,7 @@ __git_find_last_on_cmdline ()
done
local wordlist="$1"
- while [ $c -gt 1 ]; do
+ while [ $c -gt "$__git_cmd_idx" ]; do
((c--))
for word in $wordlist; do
if [ "$word" = "${words[c]}" ]; then
@@ -1306,7 +1306,7 @@ __git_count_arguments ()
local word i c=0
# Skip "git" (first argument)
- for ((i=1; i < ${#words[@]}; i++)); do
+ for ((i="$__git_cmd_idx"; i < ${#words[@]}; i++)); do
word="${words[i]}"
case "$word" in
@@ -1333,6 +1333,7 @@ __git_whitespacelist="nowarn warn error error-all fix"
__git_patchformat="mbox stgit stgit-series hg mboxrd"
__git_showcurrentpatch="diff raw"
__git_am_inprogress_options="--skip --continue --resolved --abort --quit --show-current-patch"
+__git_quoted_cr="nowarn warn strip"
_git_am ()
{
@@ -1354,6 +1355,10 @@ _git_am ()
__gitcomp "$__git_showcurrentpatch" "" "${cur##--show-current-patch=}"
return
;;
+ --quoted-cr=*)
+ __gitcomp "$__git_quoted_cr" "" "${cur##--quoted-cr=}"
+ return
+ ;;
--*)
__gitcomp_builtin am "" \
"$__git_am_inprogress_options"
@@ -1442,7 +1447,7 @@ __git_ref_fieldlist="refname objecttype objectsize objectname upstream push HEAD
_git_branch ()
{
- local i c=1 only_local_ref="n" has_r="n"
+ local i c="$__git_cmd_idx" only_local_ref="n" has_r="n"
while [ $c -lt $cword ]; do
i="${words[c]}"
@@ -1474,12 +1479,12 @@ _git_branch ()
_git_bundle ()
{
- local cmd="${words[2]}"
+ local cmd="${words[__git_cmd_idx+1]}"
case "$cword" in
- 2)
+ $((__git_cmd_idx+1)))
__gitcomp "create list-heads verify unbundle"
;;
- 3)
+ $((__git_cmd_idx+2)))
# looking for a file
;;
*)
@@ -1894,7 +1899,7 @@ _git_grep ()
esac
case "$cword,$prev" in
- 2,*|*,-*)
+ $((__git_cmd_idx+1)),*|*,-*)
__git_complete_symbol && return
;;
esac
@@ -1910,7 +1915,7 @@ _git_help ()
return
;;
esac
- if test -n "$GIT_TESTING_ALL_COMMAND_LIST"
+ if test -n "${GIT_TESTING_ALL_COMMAND_LIST-}"
then
__gitcomp "$GIT_TESTING_ALL_COMMAND_LIST $(__git --list-cmds=alias,list-guide) gitk"
else
@@ -2474,7 +2479,7 @@ _git_switch ()
__git_config_get_set_variables ()
{
local prevword word config_file= c=$cword
- while [ $c -gt 1 ]; do
+ while [ $c -gt "$__git_cmd_idx" ]; do
word="${words[c]}"
case "$word" in
--system|--global|--local|--file=*)
@@ -3013,66 +3018,48 @@ _git_sparse_checkout ()
_git_stash ()
{
- local save_opts='--all --keep-index --no-keep-index --quiet --patch --include-untracked'
local subcommands='push list show apply clear drop pop create branch'
local subcommand="$(__git_find_on_cmdline "$subcommands save")"
- if [ -z "$subcommand" -a -n "$(__git_find_on_cmdline "-p")" ]; then
- subcommand="push"
- fi
+
if [ -z "$subcommand" ]; then
- case "$cur" in
- --*)
- __gitcomp "$save_opts"
+ case "$((cword - __git_cmd_idx)),$cur" in
+ *,--*)
+ __gitcomp_builtin stash_push
;;
- sa*)
- if [ -z "$(__git_find_on_cmdline "$save_opts")" ]; then
- __gitcomp "save"
- fi
+ 1,sa*)
+ __gitcomp "save"
;;
- *)
- if [ -z "$(__git_find_on_cmdline "$save_opts")" ]; then
- __gitcomp "$subcommands"
- fi
+ 1,*)
+ __gitcomp "$subcommands"
;;
esac
- else
- case "$subcommand,$cur" in
- push,--*)
- __gitcomp "$save_opts --message"
- ;;
- save,--*)
- __gitcomp "$save_opts"
- ;;
- apply,--*|pop,--*)
- __gitcomp "--index --quiet"
- ;;
- drop,--*)
- __gitcomp "--quiet"
- ;;
- list,--*)
- __gitcomp "--name-status --oneline --patch-with-stat"
- ;;
- show,--*)
- __gitcomp "$__git_diff_common_options"
- ;;
- branch,--*)
- ;;
- branch,*)
- if [ $cword -eq 3 ]; then
- __git_complete_refs
- else
- __gitcomp_nl "$(__git stash list \
- | sed -n -e 's/:.*//p')"
- fi
- ;;
- show,*|apply,*|drop,*|pop,*)
+ return
+ fi
+
+ case "$subcommand,$cur" in
+ list,--*)
+ # NEEDSWORK: can we somehow unify this with the options in _git_log() and _git_show()
+ __gitcomp_builtin stash_list "$__git_log_common_options $__git_diff_common_options"
+ ;;
+ show,--*)
+ __gitcomp_builtin stash_show "$__git_diff_common_options"
+ ;;
+ *,--*)
+ __gitcomp_builtin "stash_$subcommand"
+ ;;
+ branch,*)
+ if [ $cword -eq $((__git_cmd_idx+2)) ]; then
+ __git_complete_refs
+ else
__gitcomp_nl "$(__git stash list \
| sed -n -e 's/:.*//p')"
- ;;
- *)
- ;;
- esac
- fi
+ fi
+ ;;
+ show,*|apply,*|drop,*|pop,*)
+ __gitcomp_nl "$(__git stash list \
+ | sed -n -e 's/:.*//p')"
+ ;;
+ esac
}
_git_submodule ()
@@ -3225,7 +3212,7 @@ _git_svn ()
_git_tag ()
{
- local i c=1 f=0
+ local i c="$__git_cmd_idx" f=0
while [ $c -lt $cword ]; do
i="${words[c]}"
case "$i" in
@@ -3268,9 +3255,10 @@ _git_whatchanged ()
__git_complete_worktree_paths ()
{
local IFS=$'\n'
+ # Generate completion reply from worktree list skipping the first
+ # entry: it's the path of the main worktree, which can't be moved,
+ # removed, locked, etc.
__gitcomp_nl "$(git worktree list --porcelain |
- # Skip the first entry: it's the path of the main worktree,
- # which can't be moved, removed, locked, etc.
sed -n -e '2,$ s/^worktree //p')"
}
@@ -3398,21 +3386,40 @@ __git_main ()
{
local i c=1 command __git_dir __git_repo_path
local __git_C_args C_args_count=0
+ local __git_cmd_idx
while [ $c -lt $cword ]; do
i="${words[c]}"
case "$i" in
- --git-dir=*) __git_dir="${i#--git-dir=}" ;;
- --git-dir) ((c++)) ; __git_dir="${words[c]}" ;;
- --bare) __git_dir="." ;;
- --help) command="help"; break ;;
- -c|--work-tree|--namespace) ((c++)) ;;
- -C) __git_C_args[C_args_count++]=-C
+ --git-dir=*)
+ __git_dir="${i#--git-dir=}"
+ ;;
+ --git-dir)
+ ((c++))
+ __git_dir="${words[c]}"
+ ;;
+ --bare)
+ __git_dir="."
+ ;;
+ --help)
+ command="help"
+ break
+ ;;
+ -c|--work-tree|--namespace)
+ ((c++))
+ ;;
+ -C)
+ __git_C_args[C_args_count++]=-C
((c++))
__git_C_args[C_args_count++]="${words[c]}"
;;
- -*) ;;
- *) command="$i"; break ;;
+ -*)
+ ;;
+ *)
+ command="$i"
+ __git_cmd_idx="$c"
+ break
+ ;;
esac
((c++))
done
@@ -3434,7 +3441,8 @@ __git_main ()
;;
esac
case "$cur" in
- --*) __gitcomp "
+ --*)
+ __gitcomp "
--paginate
--no-pager
--git-dir=
diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh
index 4640a1535d..db7c0068fb 100644
--- a/contrib/completion/git-prompt.sh
+++ b/contrib/completion/git-prompt.sh
@@ -433,8 +433,8 @@ __git_ps1 ()
fi
local sparse=""
- if [ -z "${GIT_PS1_COMPRESSSPARSESTATE}" ] &&
- [ -z "${GIT_PS1_OMITSPARSESTATE}" ] &&
+ if [ -z "${GIT_PS1_COMPRESSSPARSESTATE-}" ] &&
+ [ -z "${GIT_PS1_OMITSPARSESTATE-}" ] &&
[ "$(git config --bool core.sparseCheckout)" = "true" ]; then
sparse="|SPARSE"
fi
@@ -543,7 +543,7 @@ __git_ps1 ()
u="%${ZSH_VERSION+%}"
fi
- if [ -n "${GIT_PS1_COMPRESSSPARSESTATE}" ] &&
+ if [ -n "${GIT_PS1_COMPRESSSPARSESTATE-}" ] &&
[ "$(git config --bool core.sparseCheckout)" = "true" ]; then
h="?"
fi
diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh
index 868e18b9a1..b06782bc79 100755
--- a/contrib/subtree/git-subtree.sh
+++ b/contrib/subtree/git-subtree.sh
@@ -4,208 +4,261 @@
#
# Copyright (C) 2009 Avery Pennarun <apenwarr@gmail.com>
#
-if test $# -eq 0
+
+if test -z "$GIT_EXEC_PATH" || test "${PATH#"${GIT_EXEC_PATH}:"}" = "$PATH" || ! test -f "$GIT_EXEC_PATH/git-sh-setup"
then
- set -- -h
+ echo >&2 'It looks like either your git installation or your'
+ echo >&2 'git-subtree installation is broken.'
+ echo >&2
+ echo >&2 "Tips:"
+ echo >&2 " - If \`git --exec-path\` does not print the correct path to"
+ echo >&2 " your git install directory, then set the GIT_EXEC_PATH"
+ echo >&2 " environment variable to the correct directory."
+ echo >&2 " - Make sure that your \`${0##*/}\` file is either in your"
+ echo >&2 " PATH or in your git exec path (\`$(git --exec-path)\`)."
+ echo >&2 " - You should run git-subtree as \`git ${0##*/git-}\`,"
+ echo >&2 " not as \`${0##*/}\`." >&2
+ exit 126
fi
+
OPTS_SPEC="\
git subtree add --prefix=<prefix> <commit>
git subtree add --prefix=<prefix> <repository> <ref>
git subtree merge --prefix=<prefix> <commit>
+git subtree split --prefix=<prefix> [<commit>]
git subtree pull --prefix=<prefix> <repository> <ref>
-git subtree push --prefix=<prefix> <repository> <ref>
-git subtree split --prefix=<prefix> <commit>
+git subtree push --prefix=<prefix> <repository> <refspec>
--
h,help show the help
q quiet
d show debug messages
P,prefix= the name of the subdir to split out
-m,message= use the given message as the commit message for the merge commit
- options for 'split'
+ options for 'split' (also: 'push')
annotate= add a prefix to commit message of new commits
b,branch= create a new branch from the split subtree
ignore-joins ignore prior --rejoin commits
onto= try connecting new tree to an existing one
rejoin merge the new branch back into HEAD
- options for 'add', 'merge', and 'pull'
+ options for 'add' and 'merge' (also: 'pull', 'split --rejoin', and 'push --rejoin')
squash merge subtree changes as a single commit
+m,message= use the given message as the commit message for the merge commit
"
-eval "$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
-PATH=$PATH:$(git --exec-path)
-. git-sh-setup
-
-require_work_tree
-
-quiet=
-branch=
-debug=
-command=
-onto=
-rejoin=
-ignore_joins=
-annotate=
-squash=
-message=
-prefix=
+indent=0
+# Usage: debug [MSG...]
debug () {
- if test -n "$debug"
- then
- printf "%s\n" "$*" >&2
- fi
-}
-
-say () {
- if test -z "$quiet"
+ if test -n "$arg_debug"
then
- printf "%s\n" "$*" >&2
+ printf "%$(($indent * 2))s%s\n" '' "$*" >&2
fi
}
+# Usage: progress [MSG...]
progress () {
- if test -z "$quiet"
+ if test -z "$GIT_QUIET"
then
- printf "%s\r" "$*" >&2
+ if test -z "$arg_debug"
+ then
+ # Debug mode is off.
+ #
+ # Print one progress line that we keep updating (use
+ # "\r" to return to the beginning of the line, rather
+ # than "\n" to start a new line). This only really
+ # works when stderr is a terminal.
+ printf "%s\r" "$*" >&2
+ else
+ # Debug mode is on. The `debug` function is regularly
+ # printing to stderr.
+ #
+ # Don't do the one-line-with-"\r" thing, because on a
+ # terminal the debug output would overwrite and hide the
+ # progress output. Add a "progress:" prefix to make the
+ # progress output and the debug output easy to
+ # distinguish. This ensures maximum readability whether
+ # stderr is a terminal or a file.
+ printf "progress: %s\n" "$*" >&2
+ fi
fi
}
+# Usage: assert CMD...
assert () {
if ! "$@"
then
- die "assertion failed: " "$@"
+ die "assertion failed: $*"
fi
}
-ensure_single_rev () {
- if test $# -ne 1
+main () {
+ if test $# -eq 0
then
- die "You must provide exactly one revision. Got: '$@'"
+ set -- -h
fi
-}
-
-while test $# -gt 0
-do
- opt="$1"
- shift
+ set_args="$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
+ eval "$set_args"
+ . git-sh-setup
+ require_work_tree
- case "$opt" in
- -q)
- quiet=1
- ;;
- -d)
- debug=1
- ;;
- --annotate)
- annotate="$1"
- shift
- ;;
- --no-annotate)
- annotate=
- ;;
- -b)
- branch="$1"
- shift
- ;;
- -P)
- prefix="${1%/}"
+ # First figure out the command and whether we use --rejoin, so
+ # that we can provide more helpful validation when we do the
+ # "real" flag parsing.
+ arg_split_rejoin=
+ allow_split=
+ allow_addmerge=
+ while test $# -gt 0
+ do
+ opt="$1"
shift
+ case "$opt" in
+ --annotate|-b|-P|-m|--onto)
+ shift
+ ;;
+ --rejoin)
+ arg_split_rejoin=1
+ ;;
+ --no-rejoin)
+ arg_split_rejoin=
+ ;;
+ --)
+ break
+ ;;
+ esac
+ done
+ arg_command=$1
+ case "$arg_command" in
+ add|merge|pull)
+ allow_addmerge=1
;;
- -m)
- message="$1"
- shift
+ split|push)
+ allow_split=1
+ allow_addmerge=$arg_split_rejoin
;;
- --no-prefix)
- prefix=
+ *)
+ die "Unknown command '$arg_command'"
;;
- --onto)
- onto="$1"
+ esac
+ # Reset the arguments array for "real" flag parsing.
+ eval "$set_args"
+
+ # Begin "real" flag parsing.
+ arg_debug=
+ arg_prefix=
+ arg_split_branch=
+ arg_split_onto=
+ arg_split_ignore_joins=
+ arg_split_annotate=
+ arg_addmerge_squash=
+ arg_addmerge_message=
+ while test $# -gt 0
+ do
+ opt="$1"
shift
- ;;
- --no-onto)
- onto=
- ;;
- --rejoin)
- rejoin=1
- ;;
- --no-rejoin)
- rejoin=
- ;;
- --ignore-joins)
- ignore_joins=1
- ;;
- --no-ignore-joins)
- ignore_joins=
- ;;
- --squash)
- squash=1
- ;;
- --no-squash)
- squash=
- ;;
- --)
- break
+
+ case "$opt" in
+ -q)
+ GIT_QUIET=1
+ ;;
+ -d)
+ arg_debug=1
+ ;;
+ --annotate)
+ test -n "$allow_split" || die "The '$opt' flag does not make sense with 'git subtree $arg_command'."
+ arg_split_annotate="$1"
+ shift
+ ;;
+ --no-annotate)
+ test -n "$allow_split" || die "The '$opt' flag does not make sense with 'git subtree $arg_command'."
+ arg_split_annotate=
+ ;;
+ -b)
+ test -n "$allow_split" || die "The '$opt' flag does not make sense with 'git subtree $arg_command'."
+ arg_split_branch="$1"
+ shift
+ ;;
+ -P)
+ arg_prefix="${1%/}"
+ shift
+ ;;
+ -m)
+ test -n "$allow_addmerge" || die "The '$opt' flag does not make sense with 'git subtree $arg_command'."
+ arg_addmerge_message="$1"
+ shift
+ ;;
+ --no-prefix)
+ arg_prefix=
+ ;;
+ --onto)
+ test -n "$allow_split" || die "The '$opt' flag does not make sense with 'git subtree $arg_command'."
+ arg_split_onto="$1"
+ shift
+ ;;
+ --no-onto)
+ test -n "$allow_split" || die "The '$opt' flag does not make sense with 'git subtree $arg_command'."
+ arg_split_onto=
+ ;;
+ --rejoin)
+ test -n "$allow_split" || die "The '$opt' flag does not make sense with 'git subtree $arg_command'."
+ ;;
+ --no-rejoin)
+ test -n "$allow_split" || die "The '$opt' flag does not make sense with 'git subtree $arg_command'."
+ ;;
+ --ignore-joins)
+ test -n "$allow_split" || die "The '$opt' flag does not make sense with 'git subtree $arg_command'."
+ arg_split_ignore_joins=1
+ ;;
+ --no-ignore-joins)
+ test -n "$allow_split" || die "The '$opt' flag does not make sense with 'git subtree $arg_command'."
+ arg_split_ignore_joins=
+ ;;
+ --squash)
+ test -n "$allow_addmerge" || die "The '$opt' flag does not make sense with 'git subtree $arg_command'."
+ arg_addmerge_squash=1
+ ;;
+ --no-squash)
+ test -n "$allow_addmerge" || die "The '$opt' flag does not make sense with 'git subtree $arg_command'."
+ arg_addmerge_squash=
+ ;;
+ --)
+ break
+ ;;
+ *)
+ die "Unexpected option: $opt"
+ ;;
+ esac
+ done
+ shift
+
+ if test -z "$arg_prefix"
+ then
+ die "You must provide the --prefix option."
+ fi
+
+ case "$arg_command" in
+ add)
+ test -e "$arg_prefix" &&
+ die "prefix '$arg_prefix' already exists."
;;
*)
- die "Unexpected option: $opt"
+ test -e "$arg_prefix" ||
+ die "'$arg_prefix' does not exist; use 'git subtree add'"
;;
esac
-done
-
-command="$1"
-shift
-
-case "$command" in
-add|merge|pull)
- default=
- ;;
-split|push)
- default="--default HEAD"
- ;;
-*)
- die "Unknown command '$command'"
- ;;
-esac
-
-if test -z "$prefix"
-then
- die "You must provide the --prefix option."
-fi
-case "$command" in
-add)
- test -e "$prefix" &&
- die "prefix '$prefix' already exists."
- ;;
-*)
- test -e "$prefix" ||
- die "'$prefix' does not exist; use 'git subtree add'"
- ;;
-esac
-
-dir="$(dirname "$prefix/.")"
-
-if test "$command" != "pull" &&
- test "$command" != "add" &&
- test "$command" != "push"
-then
- revs=$(git rev-parse $default --revs-only "$@") || exit $?
- dirs=$(git rev-parse --no-revs --no-flags "$@") || exit $?
- ensure_single_rev $revs
- if test -n "$dirs"
- then
- die "Error: Use --prefix instead of bare filenames."
- fi
-fi
+ dir="$(dirname "$arg_prefix/.")"
-debug "command: {$command}"
-debug "quiet: {$quiet}"
-debug "revs: {$revs}"
-debug "dir: {$dir}"
-debug "opts: {$*}"
-debug
+ debug "command: {$arg_command}"
+ debug "quiet: {$GIT_QUIET}"
+ debug "dir: {$dir}"
+ debug "opts: {$*}"
+ debug
+
+ "cmd_$arg_command" "$@"
+}
+# Usage: cache_setup
cache_setup () {
+ assert test $# = 0
cachedir="$GIT_DIR/subtree-cache/$$"
rm -rf "$cachedir" ||
die "Can't delete old cachedir: $cachedir"
@@ -216,6 +269,7 @@ cache_setup () {
debug "Using cachedir: $cachedir" >&2
}
+# Usage: cache_get [REVS...]
cache_get () {
for oldrev in "$@"
do
@@ -227,6 +281,7 @@ cache_get () {
done
}
+# Usage: cache_miss [REVS...]
cache_miss () {
for oldrev in "$@"
do
@@ -237,24 +292,30 @@ cache_miss () {
done
}
+# Usage: check_parents PARENTS_EXPR
check_parents () {
- missed=$(cache_miss "$1")
- local indent=$(($2 + 1))
+ assert test $# = 1
+ missed=$(cache_miss "$1") || exit $?
+ local indent=$(($indent + 1))
for miss in $missed
do
if ! test -r "$cachedir/notree/$miss"
then
- debug " incorrect order: $miss"
- process_split_commit "$miss" "" "$indent"
+ debug "incorrect order: $miss"
+ process_split_commit "$miss" ""
fi
done
}
+# Usage: set_notree REV
set_notree () {
+ assert test $# = 1
echo "1" > "$cachedir/notree/$1"
}
+# Usage: cache_set OLDREV NEWREV
cache_set () {
+ assert test $# = 2
oldrev="$1"
newrev="$2"
if test "$oldrev" != "latest_old" &&
@@ -266,7 +327,9 @@ cache_set () {
echo "$newrev" >"$cachedir/$oldrev"
}
+# Usage: rev_exists REV
rev_exists () {
+ assert test $# = 1
if git rev-parse "$1" >/dev/null 2>&1
then
return 0
@@ -275,32 +338,25 @@ rev_exists () {
fi
}
-rev_is_descendant_of_branch () {
- newrev="$1"
- branch="$2"
- branch_hash=$(git rev-parse "$branch")
- match=$(git rev-list -1 "$branch_hash" "^$newrev")
-
- if test -z "$match"
- then
- return 0
- else
- return 1
- fi
-}
-
-# if a commit doesn't have a parent, this might not work. But we only want
+# Usage: try_remove_previous REV
+#
+# If a commit doesn't have a parent, this might not work. But we only want
# to remove the parent from the rev-list, and since it doesn't exist, it won't
# be there anyway, so do nothing in that case.
try_remove_previous () {
+ assert test $# = 1
if rev_exists "$1^"
then
echo "^$1^"
fi
}
+# Usage: find_latest_squash DIR
find_latest_squash () {
+ assert test $# = 1
debug "Looking for latest squash ($dir)..."
+ local indent=$(($indent + 1))
+
dir="$1"
sq=
main=
@@ -319,7 +375,7 @@ find_latest_squash () {
main="$b"
;;
git-subtree-split:)
- sub="$(git rev-parse "$b^0")" ||
+ sub="$(git rev-parse "$b^{commit}")" ||
die "could not rev-parse split hash $b from commit $sq"
;;
END)
@@ -329,7 +385,8 @@ find_latest_squash () {
then
# a rejoin commit?
# Pretend its sub was a squash.
- sq="$sub"
+ sq=$(git rev-parse --verify "$sq^2") ||
+ die
fi
debug "Squash found: $sq $sub"
echo "$sq" "$sub"
@@ -340,22 +397,26 @@ find_latest_squash () {
sub=
;;
esac
- done
+ done || exit $?
}
+# Usage: find_existing_splits DIR REV
find_existing_splits () {
+ assert test $# = 2
debug "Looking for prior splits..."
+ local indent=$(($indent + 1))
+
dir="$1"
- revs="$2"
+ rev="$2"
main=
sub=
local grep_format="^git-subtree-dir: $dir/*\$"
- if test -n "$ignore_joins"
+ if test -n "$arg_split_ignore_joins"
then
grep_format="^Add '$dir/' from commit '"
fi
git log --grep="$grep_format" \
- --no-show-signature --pretty=format:'START %H%n%s%n%n%b%nEND%n' $revs |
+ --no-show-signature --pretty=format:'START %H%n%s%n%n%b%nEND%n' "$rev" |
while read a b junk
do
case "$a" in
@@ -366,11 +427,11 @@ find_existing_splits () {
main="$b"
;;
git-subtree-split:)
- sub="$(git rev-parse "$b^0")" ||
+ sub="$(git rev-parse "$b^{commit}")" ||
die "could not rev-parse split hash $b from commit $sq"
;;
END)
- debug " Main is: '$main'"
+ debug "Main is: '$main'"
if test -z "$main" -a -n "$sub"
then
# squash commits refer to a subtree
@@ -389,10 +450,12 @@ find_existing_splits () {
sub=
;;
esac
- done
+ done || exit $?
}
+# Usage: copy_commit REV TREE FLAGS_STR
copy_commit () {
+ assert test $# = 3
# We're going to set some environment vars here, so
# do it in a subshell to get rid of them safely later
debug copy_commit "{$1}" "{$2}" "{$3}"
@@ -411,23 +474,32 @@ copy_commit () {
GIT_COMMITTER_EMAIL \
GIT_COMMITTER_DATE
(
- printf "%s" "$annotate"
+ printf "%s" "$arg_split_annotate"
cat
) |
git commit-tree "$2" $3 # reads the rest of stdin
) || die "Can't copy commit $1"
}
+# Usage: add_msg DIR LATEST_OLD LATEST_NEW
add_msg () {
+ assert test $# = 3
dir="$1"
latest_old="$2"
latest_new="$3"
- if test -n "$message"
+ if test -n "$arg_addmerge_message"
then
- commit_message="$message"
+ commit_message="$arg_addmerge_message"
else
commit_message="Add '$dir/' from commit '$latest_new'"
fi
+ if test -n "$arg_split_rejoin"
+ then
+ # If this is from a --rejoin, then rejoin_msg has
+ # already inserted the `git-subtree-xxx:` tags
+ echo "$commit_message"
+ return
+ fi
cat <<-EOF
$commit_message
@@ -437,22 +509,26 @@ add_msg () {
EOF
}
+# Usage: add_squashed_msg REV DIR
add_squashed_msg () {
- if test -n "$message"
+ assert test $# = 2
+ if test -n "$arg_addmerge_message"
then
- echo "$message"
+ echo "$arg_addmerge_message"
else
echo "Merge commit '$1' as '$2'"
fi
}
+# Usage: rejoin_msg DIR LATEST_OLD LATEST_NEW
rejoin_msg () {
+ assert test $# = 3
dir="$1"
latest_old="$2"
latest_new="$3"
- if test -n "$message"
+ if test -n "$arg_addmerge_message"
then
- commit_message="$message"
+ commit_message="$arg_addmerge_message"
else
commit_message="Split '$dir/' into commit '$latest_new'"
fi
@@ -465,7 +541,9 @@ rejoin_msg () {
EOF
}
+# Usage: squash_msg DIR OLD_SUBTREE_COMMIT NEW_SUBTREE_COMMIT
squash_msg () {
+ assert test $# = 3
dir="$1"
oldsub="$2"
newsub="$3"
@@ -487,12 +565,16 @@ squash_msg () {
echo "git-subtree-split: $newsub"
}
+# Usage: toptree_for_commit COMMIT
toptree_for_commit () {
+ assert test $# = 1
commit="$1"
git rev-parse --verify "$commit^{tree}" || exit $?
}
+# Usage: subtree_for_commit COMMIT DIR
subtree_for_commit () {
+ assert test $# = 2
commit="$1"
dir="$2"
git ls-tree "$commit" -- "$dir" |
@@ -503,17 +585,19 @@ subtree_for_commit () {
test "$type" = "commit" && continue # ignore submodules
echo $tree
break
- done
+ done || exit $?
}
+# Usage: tree_changed TREE [PARENTS...]
tree_changed () {
+ assert test $# -gt 0
tree=$1
shift
if test $# -ne 1
then
return 0 # weird parents, consider it changed
else
- ptree=$(toptree_for_commit $1)
+ ptree=$(toptree_for_commit $1) || exit $?
if test "$ptree" != "$tree"
then
return 0 # changed
@@ -523,7 +607,9 @@ tree_changed () {
fi
}
+# Usage: new_squash_commit OLD_SQUASHED_COMMIT OLD_NONSQUASHED_COMMIT NEW_NONSQUASHED_COMMIT
new_squash_commit () {
+ assert test $# = 3
old="$1"
oldsub="$2"
newsub="$3"
@@ -538,7 +624,9 @@ new_squash_commit () {
fi
}
+# Usage: copy_or_skip REV TREE NEWPARENTS
copy_or_skip () {
+ assert test $# = 3
rev="$1"
tree="$2"
newparents="$3"
@@ -613,7 +701,9 @@ copy_or_skip () {
fi
}
+# Usage: ensure_clean
ensure_clean () {
+ assert test $# = 0
if ! git diff-index HEAD --exit-code --quiet 2>&1
then
die "Working tree has modifications. Cannot add."
@@ -624,15 +714,18 @@ ensure_clean () {
fi
}
+# Usage: ensure_valid_ref_format REF
ensure_valid_ref_format () {
+ assert test $# = 1
git check-ref-format "refs/heads/$1" ||
die "'$1' does not look like a ref"
}
+# Usage: process_split_commit REV PARENTS
process_split_commit () {
+ assert test $# = 2
local rev="$1"
local parents="$2"
- local indent=$3
if test $indent -eq 0
then
@@ -647,20 +740,21 @@ process_split_commit () {
progress "$revcount/$revmax ($createcount) [$extracount]"
debug "Processing commit: $rev"
- exists=$(cache_get "$rev")
+ local indent=$(($indent + 1))
+ exists=$(cache_get "$rev") || exit $?
if test -n "$exists"
then
- debug " prior: $exists"
+ debug "prior: $exists"
return
fi
createcount=$(($createcount + 1))
- debug " parents: $parents"
- check_parents "$parents" "$indent"
- newparents=$(cache_get $parents)
- debug " newparents: $newparents"
+ debug "parents: $parents"
+ check_parents "$parents"
+ newparents=$(cache_get $parents) || exit $?
+ debug "newparents: $newparents"
- tree=$(subtree_for_commit "$rev" "$dir")
- debug " tree is: $tree"
+ tree=$(subtree_for_commit "$rev" "$dir") || exit $?
+ debug "tree is: $tree"
# ugly. is there no better way to tell if this is a subtree
# vs. a mainline commit? Does it matter?
@@ -675,17 +769,15 @@ process_split_commit () {
fi
newrev=$(copy_or_skip "$rev" "$tree" "$newparents") || exit $?
- debug " newrev is: $newrev"
+ debug "newrev is: $newrev"
cache_set "$rev" "$newrev"
cache_set latest_new "$newrev"
cache_set latest_old "$rev"
}
+# Usage: cmd_add REV
+# Or: cmd_add REPOSITORY REF
cmd_add () {
- if test -e "$dir"
- then
- die "'$dir' already exists. Cannot add."
- fi
ensure_clean
@@ -707,27 +799,35 @@ cmd_add () {
cmd_add_repository "$@"
else
- say "error: parameters were '$@'"
+ say >&2 "error: parameters were '$*'"
die "Provide either a commit or a repository and commit."
fi
}
+# Usage: cmd_add_repository REPOSITORY REFSPEC
cmd_add_repository () {
+ assert test $# = 2
echo "git fetch" "$@"
repository=$1
refspec=$2
git fetch "$@" || exit $?
- revs=FETCH_HEAD
- set -- $revs
- cmd_add_commit "$@"
+ cmd_add_commit FETCH_HEAD
}
+# Usage: cmd_add_commit REV
cmd_add_commit () {
- rev=$(git rev-parse $default --revs-only "$@") || exit $?
- ensure_single_rev $rev
+ # The rev has already been validated by cmd_add(), we just
+ # need to normalize it.
+ assert test $# = 1
+ rev=$(git rev-parse --verify "$1^{commit}") || exit $?
debug "Adding $dir as '$rev'..."
- git read-tree --prefix="$dir" $rev || exit $?
+ if test -z "$arg_split_rejoin"
+ then
+ # Only bother doing this if this is a genuine 'add',
+ # not a synthetic 'add' from '--rejoin'.
+ git read-tree --prefix="$dir" $rev || exit $?
+ fi
git checkout -- "$dir" || exit $?
tree=$(git write-tree) || exit $?
@@ -739,44 +839,61 @@ cmd_add_commit () {
headp=
fi
- if test -n "$squash"
+ if test -n "$arg_addmerge_squash"
then
rev=$(new_squash_commit "" "" "$rev") || exit $?
commit=$(add_squashed_msg "$rev" "$dir" |
git commit-tree "$tree" $headp -p "$rev") || exit $?
else
- revp=$(peel_committish "$rev") &&
+ revp=$(peel_committish "$rev") || exit $?
commit=$(add_msg "$dir" $headrev "$rev" |
git commit-tree "$tree" $headp -p "$revp") || exit $?
fi
git reset "$commit" || exit $?
- say "Added dir '$dir'"
+ say >&2 "Added dir '$dir'"
}
+# Usage: cmd_split [REV]
cmd_split () {
+ if test $# -eq 0
+ then
+ rev=$(git rev-parse HEAD)
+ elif test $# -eq 1
+ then
+ rev=$(git rev-parse -q --verify "$1^{commit}") ||
+ die "'$1' does not refer to a commit"
+ else
+ die "You must provide exactly one revision. Got: '$*'"
+ fi
+
+ if test -n "$arg_split_rejoin"
+ then
+ ensure_clean
+ fi
+
debug "Splitting $dir..."
cache_setup || exit $?
- if test -n "$onto"
+ if test -n "$arg_split_onto"
then
- debug "Reading history for --onto=$onto..."
- git rev-list $onto |
+ debug "Reading history for --onto=$arg_split_onto..."
+ git rev-list $arg_split_onto |
while read rev
do
# the 'onto' history is already just the subdir, so
# any parent we find there can be used verbatim
- debug " cache: $rev"
+ debug "cache: $rev"
cache_set "$rev" "$rev"
- done
+ done || exit $?
fi
- unrevs="$(find_existing_splits "$dir" "$revs")"
+ unrevs="$(find_existing_splits "$dir" "$rev")" || exit $?
# We can't restrict rev-list to only $dir here, because some of our
# parents have the $dir contents the root, and those won't match.
# (and rev-list --follow doesn't seem to solve this)
- grl='git rev-list --topo-order --reverse --parents $revs $unrevs'
+ grl='git rev-list --topo-order --reverse --parents $rev $unrevs'
revmax=$(eval "$grl" | wc -l)
revcount=0
createcount=0
@@ -784,52 +901,58 @@ cmd_split () {
eval "$grl" |
while read rev parents
do
- process_split_commit "$rev" "$parents" 0
+ process_split_commit "$rev" "$parents"
done || exit $?
- latest_new=$(cache_get latest_new)
+ latest_new=$(cache_get latest_new) || exit $?
if test -z "$latest_new"
then
die "No new revisions were found"
fi
- if test -n "$rejoin"
+ if test -n "$arg_split_rejoin"
then
debug "Merging split branch into HEAD..."
- latest_old=$(cache_get latest_old)
- git merge -s ours \
- --allow-unrelated-histories \
- -m "$(rejoin_msg "$dir" "$latest_old" "$latest_new")" \
- "$latest_new" >&2 || exit $?
+ latest_old=$(cache_get latest_old) || exit $?
+ arg_addmerge_message="$(rejoin_msg "$dir" "$latest_old" "$latest_new")" || exit $?
+ if test -z "$(find_latest_squash "$dir")"
+ then
+ cmd_add "$latest_new" >&2 || exit $?
+ else
+ cmd_merge "$latest_new" >&2 || exit $?
+ fi
fi
- if test -n "$branch"
+ if test -n "$arg_split_branch"
then
- if rev_exists "refs/heads/$branch"
+ if rev_exists "refs/heads/$arg_split_branch"
then
- if ! rev_is_descendant_of_branch "$latest_new" "$branch"
+ if ! git merge-base --is-ancestor "$arg_split_branch" "$latest_new"
then
- die "Branch '$branch' is not an ancestor of commit '$latest_new'."
+ die "Branch '$arg_split_branch' is not an ancestor of commit '$latest_new'."
fi
action='Updated'
else
action='Created'
fi
git update-ref -m 'subtree split' \
- "refs/heads/$branch" "$latest_new" || exit $?
- say "$action branch '$branch'"
+ "refs/heads/$arg_split_branch" "$latest_new" || exit $?
+ say >&2 "$action branch '$arg_split_branch'"
fi
echo "$latest_new"
exit 0
}
+# Usage: cmd_merge REV
cmd_merge () {
- rev=$(git rev-parse $default --revs-only "$@") || exit $?
- ensure_single_rev $rev
+ test $# -eq 1 ||
+ die "You must provide exactly one revision. Got: '$*'"
+ rev=$(git rev-parse -q --verify "$1^{commit}") ||
+ die "'$1' does not refer to a commit"
ensure_clean
- if test -n "$squash"
+ if test -n "$arg_addmerge_squash"
then
- first_split="$(find_latest_squash "$dir")"
+ first_split="$(find_latest_squash "$dir")" || exit $?
if test -z "$first_split"
then
die "Can't squash-merge: '$dir' was never added."
@@ -839,7 +962,7 @@ cmd_merge () {
sub=$2
if test "$sub" = "$rev"
then
- say "Subtree is already at commit $rev."
+ say >&2 "Subtree is already at commit $rev."
exit 0
fi
new=$(new_squash_commit "$old" "$sub" "$rev") || exit $?
@@ -847,26 +970,16 @@ cmd_merge () {
rev="$new"
fi
- version=$(git version)
- if test "$version" \< "git version 1.7"
+ if test -n "$arg_addmerge_message"
then
- if test -n "$message"
- then
- git merge -s subtree --message="$message" "$rev"
- else
- git merge -s subtree "$rev"
- fi
+ git merge -Xsubtree="$arg_prefix" \
+ --message="$arg_addmerge_message" "$rev"
else
- if test -n "$message"
- then
- git merge -Xsubtree="$prefix" \
- --message="$message" "$rev"
- else
- git merge -Xsubtree="$prefix" $rev
- fi
+ git merge -Xsubtree="$arg_prefix" $rev
fi
}
+# Usage: cmd_pull REPOSITORY REMOTEREF
cmd_pull () {
if test $# -ne 2
then
@@ -875,27 +988,36 @@ cmd_pull () {
ensure_clean
ensure_valid_ref_format "$2"
git fetch "$@" || exit $?
- revs=FETCH_HEAD
- set -- $revs
- cmd_merge "$@"
+ cmd_merge FETCH_HEAD
}
+# Usage: cmd_push REPOSITORY [+][LOCALREV:]REMOTEREF
cmd_push () {
if test $# -ne 2
then
- die "You must provide <repository> <ref>"
+ die "You must provide <repository> <refspec>"
fi
- ensure_valid_ref_format "$2"
if test -e "$dir"
then
repository=$1
- refspec=$2
+ refspec=${2#+}
+ remoteref=${refspec#*:}
+ if test "$remoteref" = "$refspec"
+ then
+ localrevname_presplit=HEAD
+ else
+ localrevname_presplit=${refspec%%:*}
+ fi
+ ensure_valid_ref_format "$remoteref"
+ localrev_presplit=$(git rev-parse -q --verify "$localrevname_presplit^{commit}") ||
+ die "'$localrevname_presplit' does not refer to a commit"
+
echo "git push using: " "$repository" "$refspec"
- localrev=$(git subtree split --prefix="$prefix") || die
- git push "$repository" "$localrev":"refs/heads/$refspec"
+ localrev=$(cmd_split "$localrev_presplit") || die
+ git push "$repository" "$localrev":"refs/heads/$remoteref"
else
die "'$dir' must already exist. Try 'git subtree add'."
fi
}
-"cmd_$command" "$@"
+main "$@"
diff --git a/contrib/subtree/git-subtree.txt b/contrib/subtree/git-subtree.txt
index 0db02fe3c0..9cddfa2654 100644
--- a/contrib/subtree/git-subtree.txt
+++ b/contrib/subtree/git-subtree.txt
@@ -9,13 +9,14 @@ git-subtree - Merge subtrees together and split repository into subtrees
SYNOPSIS
--------
[verse]
-'git subtree' add -P <prefix> <commit>
-'git subtree' add -P <prefix> <repository> <ref>
-'git subtree' pull -P <prefix> <repository> <ref>
-'git subtree' push -P <prefix> <repository> <ref>
-'git subtree' merge -P <prefix> <commit>
-'git subtree' split -P <prefix> [OPTIONS] [<commit>]
+'git subtree' [<options>] -P <prefix> add <local-commit>
+'git subtree' [<options>] -P <prefix> add <repository> <remote-ref>
+'git subtree' [<options>] -P <prefix> merge <local-commit>
+'git subtree' [<options>] -P <prefix> split [<local-commit>]
+[verse]
+'git subtree' [<options>] -P <prefix> pull <repository> <remote-ref>
+'git subtree' [<options>] -P <prefix> push <repository> <refspec>
DESCRIPTION
-----------
@@ -28,7 +29,7 @@ as a subdirectory of your application.
Subtrees are not to be confused with submodules, which are meant for
the same task. Unlike submodules, subtrees do not need any special
-constructions (like .gitmodules files or gitlinks) be present in
+constructions (like '.gitmodules' files or gitlinks) be present in
your repository, and do not force end-users of your
repository to do anything special or to understand how subtrees
work. A subtree is just a subdirectory that can be
@@ -59,70 +60,71 @@ project as much as possible. That is, if you make a change that
affects both the library and the main application, commit it in
two pieces. That way, when you split the library commits out
later, their descriptions will still make sense. But if this
-isn't important to you, it's not *necessary*. git subtree will
+isn't important to you, it's not *necessary*. 'git subtree' will
simply leave out the non-library-related parts of the commit
when it splits it out into the subproject later.
COMMANDS
--------
-add::
+add <local-commit>::
+add <repository> <remote-ref>::
Create the <prefix> subtree by importing its contents
- from the given <commit> or <repository> and remote <ref>.
+ from the given <local-commit> or <repository> and <remote-ref>.
A new commit is created automatically, joining the imported
- project's history with your own. With '--squash', imports
+ project's history with your own. With '--squash', import
only a single commit from the subproject, rather than its
entire history.
-merge::
- Merge recent changes up to <commit> into the <prefix>
+merge <local-commit>::
+ Merge recent changes up to <local-commit> into the <prefix>
subtree. As with normal 'git merge', this doesn't
remove your own local changes; it just merges those
- changes into the latest <commit>. With '--squash',
- creates only one commit that contains all the changes,
+ changes into the latest <local-commit>. With '--squash',
+ create only one commit that contains all the changes,
rather than merging in the entire history.
+
If you use '--squash', the merge direction doesn't always have to be
forward; you can use this command to go back in time from v2.5 to v2.4,
for example. If your merge introduces a conflict, you can resolve it in
the usual ways.
-
-pull::
- Exactly like 'merge', but parallels 'git pull' in that
- it fetches the given ref from the specified remote
- repository.
-
-push::
- Does a 'split' (see below) using the <prefix> supplied
- and then does a 'git push' to push the result to the
- repository and ref. This can be used to push your
- subtree to different branches of the remote repository.
-
-split::
+
+split [<local-commit>]::
Extract a new, synthetic project history from the
- history of the <prefix> subtree. The new history
+ history of the <prefix> subtree of <local-commit>, or of
+ HEAD if no <local-commit> is given. The new history
includes only the commits (including merges) that
affected <prefix>, and each of those commits now has the
contents of <prefix> at the root of the project instead
of in a subdirectory. Thus, the newly created history
is suitable for export as a separate git repository.
+
-After splitting successfully, a single commit id is printed to stdout.
+After splitting successfully, a single commit ID is printed to stdout.
This corresponds to the HEAD of the newly created tree, which you can
manipulate however you want.
+
Repeated splits of exactly the same history are guaranteed to be
-identical (i.e. to produce the same commit ids). Because of this, if
-you add new commits and then re-split, the new commits will be attached
-as commits on top of the history you generated last time, so 'git merge'
-and friends will work as expected.
-+
-Note that if you use '--squash' when you merge, you should usually not
-just '--rejoin' when you split.
+identical (i.e. to produce the same commit IDs) as long as the
+settings passed to 'split' (such as '--annotate') are the same.
+Because of this, if you add new commits and then re-split, the new
+commits will be attached as commits on top of the history you
+generated last time, so 'git merge' and friends will work as expected.
+pull <repository> <remote-ref>::
+ Exactly like 'merge', but parallels 'git pull' in that
+ it fetches the given ref from the specified remote
+ repository.
+
+push <repository> [+][<local-commit>:]<remote-ref>::
+ Does a 'split' using the <prefix> subtree of <local-commit>
+ and then does a 'git push' to push the result to the
+ <repository> and <remote-ref>. This can be used to push your
+ subtree to different branches of the remote repository. Just
+ as with 'split', if no <local-commit> is given, then HEAD is
+ used. The optional leading '+' is ignored.
-OPTIONS
--------
+OPTIONS FOR ALL COMMANDS
+------------------------
-q::
--quiet::
Suppress unnecessary output messages on stderr.
@@ -137,21 +139,16 @@ OPTIONS
want to manipulate. This option is mandatory
for all commands.
--m <message>::
---message=<message>::
- This option is only valid for add, merge, pull, and split --rejoin.
- Specify <message> as the commit message for the merge commit.
-
+OPTIONS FOR 'add' AND 'merge' (ALSO: 'pull', 'split --rejoin', AND 'push --rejoin')
+-----------------------------------------------------------------------------------
+These options for 'add' and 'merge' may also be given to 'pull' (which
+wraps 'merge'), 'split --rejoin' (which wraps either 'add' or 'merge'
+as appropriate), and 'push --rejoin' (which wraps 'split --rejoin').
-OPTIONS FOR add, merge, and pull
---------------------------------
--squash::
- This option is only valid for add, merge, and pull
- commands.
-+
-Instead of merging the entire history from the subtree project, produce
-only a single commit that contains all the differences you want to
-merge, and then merge that new commit into your project.
+ Instead of merging the entire history from the subtree project, produce
+ only a single commit that contains all the differences you want to
+ merge, and then merge that new commit into your project.
+
Using this option helps to reduce log clutter. People rarely want to see
every change that happened between v1.0 and v1.1 of the library they're
@@ -174,57 +171,53 @@ Whether or not you use '--squash', changes made in your local repository
remain intact and can be later split and send upstream to the
subproject.
+-m <message>::
+--message=<message>::
+ Specify <message> as the commit message for the merge commit.
+
+OPTIONS FOR 'split' (ALSO: 'push')
+----------------------------------
+These options for 'split' may also be given to 'push' (which wraps
+'split').
-OPTIONS FOR split
------------------
--annotate=<annotation>::
- This option is only valid for the split command.
-+
-When generating synthetic history, add <annotation> as a prefix to each
-commit message. Since we're creating new commits with the same commit
-message, but possibly different content, from the original commits, this
-can help to differentiate them and avoid confusion.
+ When generating synthetic history, add <annotation> as a prefix to each
+ commit message. Since we're creating new commits with the same commit
+ message, but possibly different content, from the original commits, this
+ can help to differentiate them and avoid confusion.
+
Whenever you split, you need to use the same <annotation>, or else you
don't have a guarantee that the new re-created history will be identical
to the old one. That will prevent merging from working correctly. git
-subtree tries to make it work anyway, particularly if you use --rejoin,
+subtree tries to make it work anyway, particularly if you use '--rejoin',
but it may not always be effective.
-b <branch>::
--branch=<branch>::
- This option is only valid for the split command.
-+
-After generating the synthetic history, create a new branch called
-<branch> that contains the new history. This is suitable for immediate
-pushing upstream. <branch> must not already exist.
+ After generating the synthetic history, create a new branch called
+ <branch> that contains the new history. This is suitable for immediate
+ pushing upstream. <branch> must not already exist.
--ignore-joins::
- This option is only valid for the split command.
-+
-If you use '--rejoin', git subtree attempts to optimize its history
-reconstruction to generate only the new commits since the last
-'--rejoin'. '--ignore-join' disables this behaviour, forcing it to
-regenerate the entire history. In a large project, this can take a long
-time.
+ If you use '--rejoin', git subtree attempts to optimize its history
+ reconstruction to generate only the new commits since the last
+ '--rejoin'. '--ignore-joins' disables this behavior, forcing it to
+ regenerate the entire history. In a large project, this can take a long
+ time.
--onto=<onto>::
- This option is only valid for the split command.
-+
-If your subtree was originally imported using something other than git
-subtree, its history may not match what git subtree is expecting. In
-that case, you can specify the commit id <onto> that corresponds to the
-first revision of the subproject's history that was imported into your
-project, and git subtree will attempt to build its history from there.
+ If your subtree was originally imported using something other than git
+ subtree, its history may not match what git subtree is expecting. In
+ that case, you can specify the commit ID <onto> that corresponds to the
+ first revision of the subproject's history that was imported into your
+ project, and git subtree will attempt to build its history from there.
+
If you used 'git subtree add', you should never need this option.
--rejoin::
- This option is only valid for the split command.
-+
-After splitting, merge the newly created synthetic history back into
-your main project. That way, future splits can search only the part of
-history that has been added since the most recent --rejoin.
+ After splitting, merge the newly created synthetic history back into
+ your main project. That way, future splits can search only the part of
+ history that has been added since the most recent '--rejoin'.
+
If your split commits end up merged into the upstream subproject, and
then you want to get the latest upstream version, this will allow git's
@@ -235,13 +228,12 @@ Unfortunately, using this option results in 'git log' showing an extra
copy of every new commit that was created (the original, and the
synthetic one).
+
-If you do all your merges with '--squash', don't use '--rejoin' when you
-split, because you don't want the subproject's history to be part of
-your project anyway.
+If you do all your merges with '--squash', make sure you also use
+'--squash' when you 'split --rejoin'.
-EXAMPLE 1. Add command
-----------------------
+EXAMPLE 1. 'add' command
+------------------------
Let's assume that you have a local repository that you would like
to add an external vendor library to. In this case we will add the
git-subtree repository as a subdirectory of your already existing
@@ -253,15 +245,15 @@ git-extensions repository in ~/git-extensions/:
'master' needs to be a valid remote ref and can be a different branch
name
-You can omit the --squash flag, but doing so will increase the number
+You can omit the '--squash' flag, but doing so will increase the number
of commits that are included in your local repository.
We now have a ~/git-extensions/git-subtree directory containing code
from the master branch of git://github.com/apenwarr/git-subtree.git
in our git-extensions repository.
-EXAMPLE 2. Extract a subtree using commit, merge and pull
----------------------------------------------------------
+EXAMPLE 2. Extract a subtree using 'commit', 'merge' and 'pull'
+---------------------------------------------------------------
Let's use the repository for the git source code as an example.
First, get your own copy of the git.git repository:
@@ -269,7 +261,7 @@ First, get your own copy of the git.git repository:
$ cd test-git
gitweb (commit 1130ef3) was merged into git as of commit
-0a8f4f0, after which it was no longer maintained separately.
+0a8f4f0, after which it was no longer maintained separately.
But imagine it had been maintained separately, and we wanted to
extract git's changes to gitweb since that time, to share with
the upstream. You could do this:
@@ -279,14 +271,14 @@ the upstream. You could do this:
--branch gitweb-latest
$ gitk gitweb-latest
$ git push git@github.com:whatever/gitweb.git gitweb-latest:master
-
+
(We use '0a8f4f0^..' because that means "all the changes from
0a8f4f0 to the current version, including 0a8f4f0 itself.")
If gitweb had originally been merged using 'git subtree add' (or
-a previous split had already been done with --rejoin specified)
+a previous split had already been done with '--rejoin' specified)
then you can do all your splits without having to remember any
-weird commit ids:
+weird commit IDs:
$ git subtree split --prefix=gitweb --annotate='(split) ' --rejoin \
--branch gitweb-latest2
@@ -313,7 +305,7 @@ And fast forward again:
$ git subtree merge --prefix=gitweb --squash gitweb-latest
And notice that your change is still intact:
-
+
$ ls -l gitweb/myfile
And you can split it out and look at your changes versus
@@ -321,8 +313,8 @@ the standard gitweb:
git log gitweb-latest..$(git subtree split --prefix=gitweb)
-EXAMPLE 3. Extract a subtree using branch
------------------------------------------
+EXAMPLE 3. Extract a subtree using a branch
+-------------------------------------------
Suppose you have a source directory with many files and
subdirectories, and you want to extract the lib directory to its own
git project. Here's a short way to do it:
diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh
index 57ff4b25c1..4153b65321 100755
--- a/contrib/subtree/t/t7900-subtree.sh
+++ b/contrib/subtree/t/t7900-subtree.sh
@@ -5,72 +5,24 @@
#
test_description='Basic porcelain support for subtrees
-This test verifies the basic operation of the add, pull, merge
-and split subcommands of git subtree.
+This test verifies the basic operation of the add, merge, split, pull,
+and push subcommands of git subtree.
'
TEST_DIRECTORY=$(pwd)/../../../t
-export TEST_DIRECTORY
+. "$TEST_DIRECTORY"/test-lib.sh
-. ../../../t/test-lib.sh
-
-subtree_test_create_repo()
-{
+# Use our own wrapper around test-lib.sh's test_create_repo, in order
+# to set log.date=relative. `git subtree` parses the output of `git
+# log`, and so it must be careful to not be affected by settings that
+# change the `git log` output. We test this by setting
+# log.date=relative for every repo in the tests.
+subtree_test_create_repo () {
test_create_repo "$1" &&
- (
- cd "$1" &&
- git config log.date relative
- )
-}
-
-create()
-{
- echo "$1" >"$1" &&
- git add "$1"
-}
-
-check_equal()
-{
- test_debug 'echo'
- test_debug "echo \"check a:\" \"{$1}\""
- test_debug "echo \" b:\" \"{$2}\""
- if [ "$1" = "$2" ]; then
- return 0
- else
- return 1
- fi
-}
-
-undo()
-{
- git reset --hard HEAD~
+ git -C "$1" config log.date relative
}
-# Make sure no patch changes more than one file.
-# The original set of commits changed only one file each.
-# A multi-file change would imply that we pruned commits
-# too aggressively.
-join_commits()
-{
- commit=
- all=
- while read x y; do
- if [ -z "$x" ]; then
- continue
- elif [ "$x" = "commit:" ]; then
- if [ -n "$commit" ]; then
- echo "$commit $all"
- all=
- fi
- commit="$y"
- else
- all="$all $y"
- fi
- done
- echo "$commit $all"
-}
-
-test_create_commit() (
+test_create_commit () (
repo=$1 &&
commit=$2 &&
cd "$repo" &&
@@ -81,98 +33,116 @@ test_create_commit() (
git commit -m "$commit" || error "Could not commit"
)
-last_commit_message()
-{
- git log --pretty=format:%s -1
+test_wrong_flag() {
+ test_must_fail "$@" >out 2>err &&
+ test_must_be_empty out &&
+ grep "flag does not make sense with" err
}
-subtree_test_count=0
-next_test() {
- subtree_test_count=$(($subtree_test_count+1))
+last_commit_subject () {
+ git log --pretty=format:%s -1
}
+test_expect_success 'shows short help text for -h' '
+ test_expect_code 129 git subtree -h >out 2>err &&
+ test_must_be_empty err &&
+ grep -e "^ *or: git subtree pull" out &&
+ grep -e --annotate out
+'
+
#
# Tests for 'git subtree add'
#
-next_test
test_expect_success 'no merge from non-existent subtree' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
test_must_fail git subtree merge --prefix="sub dir" FETCH_HEAD
)
'
-next_test
test_expect_success 'no pull from non-existent subtree' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
- test_must_fail git subtree pull --prefix="sub dir" ./"sub proj" master
- )'
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ test_must_fail git subtree pull --prefix="sub dir" ./"sub proj" HEAD
+ )
+'
+
+test_expect_success 'add rejects flags for split' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ test_wrong_flag git subtree add --prefix="sub dir" --annotate=foo FETCH_HEAD &&
+ test_wrong_flag git subtree add --prefix="sub dir" --branch=foo FETCH_HEAD &&
+ test_wrong_flag git subtree add --prefix="sub dir" --ignore-joins FETCH_HEAD &&
+ test_wrong_flag git subtree add --prefix="sub dir" --onto=foo FETCH_HEAD &&
+ test_wrong_flag git subtree add --prefix="sub dir" --rejoin FETCH_HEAD
+ )
+'
-next_test
test_expect_success 'add subproj as subtree into sub dir/ with --prefix' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree add --prefix="sub dir" FETCH_HEAD &&
- check_equal "$(last_commit_message)" "Add '\''sub dir/'\'' from commit '\''$(git rev-parse FETCH_HEAD)'\''"
+ test "$(last_commit_subject)" = "Add '\''sub dir/'\'' from commit '\''$(git rev-parse FETCH_HEAD)'\''"
)
'
-next_test
test_expect_success 'add subproj as subtree into sub dir/ with --prefix and --message' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree add --prefix="sub dir" --message="Added subproject" FETCH_HEAD &&
- check_equal "$(last_commit_message)" "Added subproject"
+ test "$(last_commit_subject)" = "Added subproject"
)
'
-next_test
test_expect_success 'add subproj as subtree into sub dir/ with --prefix as -P and --message as -m' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree add -P "sub dir" -m "Added subproject" FETCH_HEAD &&
- check_equal "$(last_commit_message)" "Added subproject"
+ test "$(last_commit_subject)" = "Added subproject"
)
'
-next_test
test_expect_success 'add subproj as subtree into sub dir/ with --squash and --prefix and --message' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree add --prefix="sub dir" --message="Added subproject with squash" --squash FETCH_HEAD &&
- check_equal "$(last_commit_message)" "Added subproject with squash"
+ test "$(last_commit_subject)" = "Added subproject with squash"
)
'
@@ -180,100 +150,117 @@ test_expect_success 'add subproj as subtree into sub dir/ with --squash and --pr
# Tests for 'git subtree merge'
#
-next_test
+test_expect_success 'merge rejects flags for split' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ test_wrong_flag git subtree merge --prefix="sub dir" --annotate=foo FETCH_HEAD &&
+ test_wrong_flag git subtree merge --prefix="sub dir" --branch=foo FETCH_HEAD &&
+ test_wrong_flag git subtree merge --prefix="sub dir" --ignore-joins FETCH_HEAD &&
+ test_wrong_flag git subtree merge --prefix="sub dir" --onto=foo FETCH_HEAD &&
+ test_wrong_flag git subtree merge --prefix="sub dir" --rejoin FETCH_HEAD
+ )
+'
+
test_expect_success 'merge new subproj history into sub dir/ with --prefix' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree add --prefix="sub dir" FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count/sub proj" sub2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree merge --prefix="sub dir" FETCH_HEAD &&
- check_equal "$(last_commit_message)" "Merge commit '\''$(git rev-parse FETCH_HEAD)'\''"
+ test "$(last_commit_subject)" = "Merge commit '\''$(git rev-parse FETCH_HEAD)'\''"
)
'
-next_test
test_expect_success 'merge new subproj history into sub dir/ with --prefix and --message' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree add --prefix="sub dir" FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count/sub proj" sub2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree merge --prefix="sub dir" --message="Merged changes from subproject" FETCH_HEAD &&
- check_equal "$(last_commit_message)" "Merged changes from subproject"
+ test "$(last_commit_subject)" = "Merged changes from subproject"
)
'
-next_test
test_expect_success 'merge new subproj history into sub dir/ with --squash and --prefix and --message' '
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- subtree_test_create_repo "$subtree_test_count" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ subtree_test_create_repo "$test_count" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree add --prefix="sub dir" FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count/sub proj" sub2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree merge --prefix="sub dir" --message="Merged changes from subproject using squash" --squash FETCH_HEAD &&
- check_equal "$(last_commit_message)" "Merged changes from subproject using squash"
+ test "$(last_commit_subject)" = "Merged changes from subproject using squash"
)
'
-next_test
test_expect_success 'merge the added subproj again, should do nothing' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree add --prefix="sub dir" FETCH_HEAD &&
# this shouldn not actually do anything, since FETCH_HEAD
# is already a parent
result=$(git merge -s ours -m "merge -s -ours" FETCH_HEAD) &&
- check_equal "${result}" "Already up to date."
+ test "${result}" = "Already up to date."
)
'
-next_test
test_expect_success 'merge new subproj history into subdir/ with a slash appended to the argument of --prefix' '
- test_create_repo "$test_count" &&
- test_create_repo "$test_count/subproj" &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/subproj" &&
test_create_commit "$test_count" main1 &&
test_create_commit "$test_count/subproj" sub1 &&
(
cd "$test_count" &&
- git fetch ./subproj master &&
+ git fetch ./subproj HEAD &&
git subtree add --prefix=subdir/ FETCH_HEAD
) &&
test_create_commit "$test_count/subproj" sub2 &&
(
cd "$test_count" &&
- git fetch ./subproj master &&
+ git fetch ./subproj HEAD &&
git subtree merge --prefix=subdir/ FETCH_HEAD &&
- check_equal "$(last_commit_message)" "Merge commit '\''$(git rev-parse FETCH_HEAD)'\''"
+ test "$(last_commit_subject)" = "Merge commit '\''$(git rev-parse FETCH_HEAD)'\''"
)
'
@@ -281,18 +268,17 @@ test_expect_success 'merge new subproj history into subdir/ with a slash appende
# Tests for 'git subtree split'
#
-next_test
test_expect_success 'split requires option --prefix' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree add --prefix="sub dir" FETCH_HEAD &&
- echo "You must provide the --prefix option." > expected &&
- test_must_fail git subtree split > actual 2>&1 &&
+ echo "You must provide the --prefix option." >expected &&
+ test_must_fail git subtree split >actual 2>&1 &&
test_debug "printf '"expected: "'" &&
test_debug "cat expected" &&
test_debug "printf '"actual: "'" &&
@@ -301,18 +287,17 @@ test_expect_success 'split requires option --prefix' '
)
'
-next_test
test_expect_success 'split requires path given by option --prefix must exist' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree add --prefix="sub dir" FETCH_HEAD &&
- echo "'\''non-existent-directory'\'' does not exist; use '\''git subtree add'\''" > expected &&
- test_must_fail git subtree split --prefix=non-existent-directory > actual 2>&1 &&
+ echo "'\''non-existent-directory'\'' does not exist; use '\''git subtree add'\''" >expected &&
+ test_must_fail git subtree split --prefix=non-existent-directory >actual 2>&1 &&
test_debug "printf '"expected: "'" &&
test_debug "cat expected" &&
test_debug "printf '"actual: "'" &&
@@ -321,222 +306,696 @@ test_expect_success 'split requires path given by option --prefix must exist' '
)
'
-next_test
+test_expect_success 'split rejects flags for add' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ split_hash=$(git subtree split --prefix="sub dir" --annotate="*") &&
+ test_wrong_flag git subtree split --prefix="sub dir" --squash &&
+ test_wrong_flag git subtree split --prefix="sub dir" --message=foo
+ )
+'
+
test_expect_success 'split sub dir/ with --rejoin' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree add --prefix="sub dir" FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub1 &&
- test_create_commit "$subtree_test_count" main2 &&
- test_create_commit "$subtree_test_count/sub proj" sub2 &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree merge --prefix="sub dir" FETCH_HEAD &&
split_hash=$(git subtree split --prefix="sub dir" --annotate="*") &&
git subtree split --prefix="sub dir" --annotate="*" --rejoin &&
- check_equal "$(last_commit_message)" "Split '\''sub dir/'\'' into commit '\''$split_hash'\''"
+ test "$(last_commit_subject)" = "Split '\''sub dir/'\'' into commit '\''$split_hash'\''"
)
- '
+'
-next_test
test_expect_success 'split sub dir/ with --rejoin from scratch' '
- subtree_test_create_repo "$subtree_test_count" &&
- test_create_commit "$subtree_test_count" main1 &&
+ subtree_test_create_repo "$test_count" &&
+ test_create_commit "$test_count" main1 &&
(
- cd "$subtree_test_count" &&
+ cd "$test_count" &&
mkdir "sub dir" &&
echo file >"sub dir"/file &&
git add "sub dir/file" &&
git commit -m"sub dir file" &&
split_hash=$(git subtree split --prefix="sub dir" --rejoin) &&
git subtree split --prefix="sub dir" --rejoin &&
- check_equal "$(last_commit_message)" "Split '\''sub dir/'\'' into commit '\''$split_hash'\''"
+ test "$(last_commit_subject)" = "Split '\''sub dir/'\'' into commit '\''$split_hash'\''"
)
- '
+'
-next_test
test_expect_success 'split sub dir/ with --rejoin and --message' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree add --prefix="sub dir" FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub1 &&
- test_create_commit "$subtree_test_count" main2 &&
- test_create_commit "$subtree_test_count/sub proj" sub2 &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree merge --prefix="sub dir" FETCH_HEAD &&
git subtree split --prefix="sub dir" --message="Split & rejoin" --annotate="*" --rejoin &&
- check_equal "$(last_commit_message)" "Split & rejoin"
+ test "$(last_commit_subject)" = "Split & rejoin"
+ )
+'
+
+test_expect_success 'split "sub dir"/ with --rejoin and --squash' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" --squash FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git subtree pull --prefix="sub dir" --squash ./"sub proj" HEAD &&
+ MAIN=$(git rev-parse --verify HEAD) &&
+ SUB=$(git -C "sub proj" rev-parse --verify HEAD) &&
+
+ SPLIT=$(git subtree split --prefix="sub dir" --annotate="*" --rejoin --squash) &&
+
+ test_must_fail git merge-base --is-ancestor $SUB HEAD &&
+ test_must_fail git merge-base --is-ancestor $SPLIT HEAD &&
+ git rev-list HEAD ^$MAIN >commit-list &&
+ test_line_count = 2 commit-list &&
+ test "$(git rev-parse --verify HEAD:)" = "$(git rev-parse --verify $MAIN:)" &&
+ test "$(git rev-parse --verify HEAD:"sub dir")" = "$(git rev-parse --verify $SPLIT:)" &&
+ test "$(git rev-parse --verify HEAD^1)" = $MAIN &&
+ test "$(git rev-parse --verify HEAD^2)" != $SPLIT &&
+ test "$(git rev-parse --verify HEAD^2:)" = "$(git rev-parse --verify $SPLIT:)" &&
+ test "$(last_commit_subject)" = "Split '\''sub dir/'\'' into commit '\''$SPLIT'\''"
)
'
-next_test
+test_expect_success 'split then pull "sub dir"/ with --rejoin and --squash' '
+ # 1. "add"
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ git -C "$test_count" subtree --prefix="sub dir" add --squash ./"sub proj" HEAD &&
+
+ # 2. commit from parent
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+
+ # 3. "split --rejoin --squash"
+ git -C "$test_count" subtree --prefix="sub dir" split --rejoin --squash &&
+
+ # 4. "pull --squash"
+ test_create_commit "$test_count/sub proj" sub2 &&
+ git -C "$test_count" subtree -d --prefix="sub dir" pull --squash ./"sub proj" HEAD &&
+
+ test_must_fail git merge-base HEAD FETCH_HEAD
+'
+
test_expect_success 'split "sub dir"/ with --branch' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree add --prefix="sub dir" FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub1 &&
- test_create_commit "$subtree_test_count" main2 &&
- test_create_commit "$subtree_test_count/sub proj" sub2 &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree merge --prefix="sub dir" FETCH_HEAD &&
split_hash=$(git subtree split --prefix="sub dir" --annotate="*") &&
git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br &&
- check_equal "$(git rev-parse subproj-br)" "$split_hash"
+ test "$(git rev-parse subproj-br)" = "$split_hash"
)
'
-next_test
test_expect_success 'check hash of split' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree add --prefix="sub dir" FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub1 &&
- test_create_commit "$subtree_test_count" main2 &&
- test_create_commit "$subtree_test_count/sub proj" sub2 &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree merge --prefix="sub dir" FETCH_HEAD &&
split_hash=$(git subtree split --prefix="sub dir" --annotate="*") &&
git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br &&
- check_equal "$(git rev-parse subproj-br)" "$split_hash" &&
+ test "$(git rev-parse subproj-br)" = "$split_hash" &&
# Check hash of split
new_hash=$(git rev-parse subproj-br^2) &&
(
cd ./"sub proj" &&
subdir_hash=$(git rev-parse HEAD) &&
- check_equal ''"$new_hash"'' "$subdir_hash"
+ test "$new_hash" = "$subdir_hash"
)
)
'
-next_test
test_expect_success 'split "sub dir"/ with --branch for an existing branch' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git branch subproj-br FETCH_HEAD &&
git subtree add --prefix="sub dir" FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub1 &&
- test_create_commit "$subtree_test_count" main2 &&
- test_create_commit "$subtree_test_count/sub proj" sub2 &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree merge --prefix="sub dir" FETCH_HEAD &&
split_hash=$(git subtree split --prefix="sub dir" --annotate="*") &&
git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br &&
- check_equal "$(git rev-parse subproj-br)" "$split_hash"
+ test "$(git rev-parse subproj-br)" = "$split_hash"
)
'
-next_test
test_expect_success 'split "sub dir"/ with --branch for an incompatible branch' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
+ cd "$test_count" &&
git branch init HEAD &&
- git fetch ./"sub proj" master &&
+ git fetch ./"sub proj" HEAD &&
git subtree add --prefix="sub dir" FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub1 &&
- test_create_commit "$subtree_test_count" main2 &&
- test_create_commit "$subtree_test_count/sub proj" sub2 &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree merge --prefix="sub dir" FETCH_HEAD &&
test_must_fail git subtree split --prefix="sub dir" --branch init
)
'
#
+# Tests for 'git subtree pull'
+#
+
+test_expect_success 'pull requires option --prefix' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ (
+ cd "$test_count" &&
+ test_must_fail git subtree pull ./"sub proj" HEAD >out 2>err &&
+
+ echo "You must provide the --prefix option." >expected &&
+ test_must_be_empty out &&
+ test_cmp expected err
+ )
+'
+
+test_expect_success 'pull requires path given by option --prefix must exist' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ test_must_fail git subtree pull --prefix="sub dir" ./"sub proj" HEAD >out 2>err &&
+
+ echo "'\''sub dir'\'' does not exist; use '\''git subtree add'\''" >expected &&
+ test_must_be_empty out &&
+ test_cmp expected err
+ )
+'
+
+test_expect_success 'pull basic operation' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ (
+ cd "$test_count" &&
+ exp=$(git -C "sub proj" rev-parse --verify HEAD:) &&
+ git subtree pull --prefix="sub dir" ./"sub proj" HEAD &&
+ act=$(git rev-parse --verify HEAD:"sub dir") &&
+ test "$act" = "$exp"
+ )
+'
+
+test_expect_success 'pull rejects flags for split' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ (
+ test_must_fail git subtree pull --prefix="sub dir" --annotate=foo ./"sub proj" HEAD &&
+ test_must_fail git subtree pull --prefix="sub dir" --branch=foo ./"sub proj" HEAD &&
+ test_must_fail git subtree pull --prefix="sub dir" --ignore-joins ./"sub proj" HEAD &&
+ test_must_fail git subtree pull --prefix="sub dir" --onto=foo ./"sub proj" HEAD &&
+ test_must_fail git subtree pull --prefix="sub dir" --rejoin ./"sub proj" HEAD
+ )
+'
+
+#
+# Tests for 'git subtree push'
+#
+
+test_expect_success 'push requires option --prefix' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD &&
+ echo "You must provide the --prefix option." >expected &&
+ test_must_fail git subtree push "./sub proj" from-mainline >actual 2>&1 &&
+ test_debug "printf '"expected: "'" &&
+ test_debug "cat expected" &&
+ test_debug "printf '"actual: "'" &&
+ test_debug "cat actual" &&
+ test_cmp expected actual
+ )
+'
+
+test_expect_success 'push requires path given by option --prefix must exist' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD &&
+ echo "'\''non-existent-directory'\'' does not exist; use '\''git subtree add'\''" >expected &&
+ test_must_fail git subtree push --prefix=non-existent-directory "./sub proj" from-mainline >actual 2>&1 &&
+ test_debug "printf '"expected: "'" &&
+ test_debug "cat expected" &&
+ test_debug "printf '"actual: "'" &&
+ test_debug "cat actual" &&
+ test_cmp expected actual
+ )
+'
+
+test_expect_success 'push rejects flags for add' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ test_wrong_flag git subtree split --prefix="sub dir" --squash ./"sub proj" from-mainline &&
+ test_wrong_flag git subtree split --prefix="sub dir" --message=foo ./"sub proj" from-mainline
+ )
+'
+
+test_expect_success 'push basic operation' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ before=$(git rev-parse --verify HEAD) &&
+ split_hash=$(git subtree split --prefix="sub dir") &&
+ git subtree push --prefix="sub dir" ./"sub proj" from-mainline &&
+ test "$before" = "$(git rev-parse --verify HEAD)" &&
+ test "$split_hash" = "$(git -C "sub proj" rev-parse --verify refs/heads/from-mainline)"
+ )
+'
+
+test_expect_success 'push sub dir/ with --rejoin' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ split_hash=$(git subtree split --prefix="sub dir" --annotate="*") &&
+ git subtree push --prefix="sub dir" --annotate="*" --rejoin ./"sub proj" from-mainline &&
+ test "$(last_commit_subject)" = "Split '\''sub dir/'\'' into commit '\''$split_hash'\''" &&
+ test "$split_hash" = "$(git -C "sub proj" rev-parse --verify refs/heads/from-mainline)"
+ )
+'
+
+test_expect_success 'push sub dir/ with --rejoin from scratch' '
+ subtree_test_create_repo "$test_count" &&
+ test_create_commit "$test_count" main1 &&
+ (
+ cd "$test_count" &&
+ mkdir "sub dir" &&
+ echo file >"sub dir"/file &&
+ git add "sub dir/file" &&
+ git commit -m"sub dir file" &&
+ split_hash=$(git subtree split --prefix="sub dir" --rejoin) &&
+ git init --bare "sub proj.git" &&
+ git subtree push --prefix="sub dir" --rejoin ./"sub proj.git" from-mainline &&
+ test "$(last_commit_subject)" = "Split '\''sub dir/'\'' into commit '\''$split_hash'\''" &&
+ test "$split_hash" = "$(git -C "sub proj.git" rev-parse --verify refs/heads/from-mainline)"
+ )
+'
+
+test_expect_success 'push sub dir/ with --rejoin and --message' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ git subtree push --prefix="sub dir" --message="Split & rejoin" --annotate="*" --rejoin ./"sub proj" from-mainline &&
+ test "$(last_commit_subject)" = "Split & rejoin" &&
+ split_hash="$(git rev-parse --verify HEAD^2)" &&
+ test "$split_hash" = "$(git -C "sub proj" rev-parse --verify refs/heads/from-mainline)"
+ )
+'
+
+test_expect_success 'push "sub dir"/ with --rejoin and --squash' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" --squash FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git subtree pull --prefix="sub dir" --squash ./"sub proj" HEAD &&
+ MAIN=$(git rev-parse --verify HEAD) &&
+ SUB=$(git -C "sub proj" rev-parse --verify HEAD) &&
+
+ SPLIT=$(git subtree split --prefix="sub dir" --annotate="*") &&
+ git subtree push --prefix="sub dir" --annotate="*" --rejoin --squash ./"sub proj" from-mainline &&
+
+ test_must_fail git merge-base --is-ancestor $SUB HEAD &&
+ test_must_fail git merge-base --is-ancestor $SPLIT HEAD &&
+ git rev-list HEAD ^$MAIN >commit-list &&
+ test_line_count = 2 commit-list &&
+ test "$(git rev-parse --verify HEAD:)" = "$(git rev-parse --verify $MAIN:)" &&
+ test "$(git rev-parse --verify HEAD:"sub dir")" = "$(git rev-parse --verify $SPLIT:)" &&
+ test "$(git rev-parse --verify HEAD^1)" = $MAIN &&
+ test "$(git rev-parse --verify HEAD^2)" != $SPLIT &&
+ test "$(git rev-parse --verify HEAD^2:)" = "$(git rev-parse --verify $SPLIT:)" &&
+ test "$(last_commit_subject)" = "Split '\''sub dir/'\'' into commit '\''$SPLIT'\''" &&
+ test "$SPLIT" = "$(git -C "sub proj" rev-parse --verify refs/heads/from-mainline)"
+ )
+'
+
+test_expect_success 'push "sub dir"/ with --branch' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ split_hash=$(git subtree split --prefix="sub dir" --annotate="*") &&
+ git subtree push --prefix="sub dir" --annotate="*" --branch subproj-br ./"sub proj" from-mainline &&
+ test "$(git rev-parse subproj-br)" = "$split_hash" &&
+ test "$split_hash" = "$(git -C "sub proj" rev-parse --verify refs/heads/from-mainline)"
+ )
+'
+
+test_expect_success 'check hash of push' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ split_hash=$(git subtree split --prefix="sub dir" --annotate="*") &&
+ git subtree push --prefix="sub dir" --annotate="*" --branch subproj-br ./"sub proj" from-mainline &&
+ test "$(git rev-parse subproj-br)" = "$split_hash" &&
+ # Check hash of split
+ new_hash=$(git rev-parse subproj-br^2) &&
+ (
+ cd ./"sub proj" &&
+ subdir_hash=$(git rev-parse HEAD) &&
+ test "$new_hash" = "$subdir_hash"
+ ) &&
+ test "$split_hash" = "$(git -C "sub proj" rev-parse --verify refs/heads/from-mainline)"
+ )
+'
+
+test_expect_success 'push "sub dir"/ with --branch for an existing branch' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git branch subproj-br FETCH_HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ split_hash=$(git subtree split --prefix="sub dir" --annotate="*") &&
+ git subtree push --prefix="sub dir" --annotate="*" --branch subproj-br ./"sub proj" from-mainline &&
+ test "$(git rev-parse subproj-br)" = "$split_hash" &&
+ test "$split_hash" = "$(git -C "sub proj" rev-parse --verify refs/heads/from-mainline)"
+ )
+'
+
+test_expect_success 'push "sub dir"/ with --branch for an incompatible branch' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git branch init HEAD &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ test_must_fail git subtree push --prefix="sub dir" --branch init "./sub proj" from-mainline
+ )
+'
+
+test_expect_success 'push "sub dir"/ with a local rev' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ bad_tree=$(git rev-parse --verify HEAD:"sub dir") &&
+ good_tree=$(git rev-parse --verify HEAD^:"sub dir") &&
+ git subtree push --prefix="sub dir" --annotate="*" ./"sub proj" HEAD^:from-mainline &&
+ split_tree=$(git -C "sub proj" rev-parse --verify refs/heads/from-mainline:) &&
+ test "$split_tree" = "$good_tree"
+ )
+'
+
+#
# Validity checking
#
-next_test
test_expect_success 'make sure exactly the right set of files ends up in the subproj' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree add --prefix="sub dir" FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub1 &&
- test_create_commit "$subtree_test_count" main2 &&
- test_create_commit "$subtree_test_count/sub proj" sub2 &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree merge --prefix="sub dir" FETCH_HEAD &&
git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
) &&
- test_create_commit "$subtree_test_count/sub proj" sub3 &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub3 &&
+ test_create_commit "$test_count/sub proj" sub3 &&
+ test_create_commit "$test_count" "sub dir"/main-sub3 &&
(
- cd "$subtree_test_count/sub proj" &&
+ cd "$test_count/sub proj" &&
git fetch .. subproj-br &&
git merge FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count/sub proj" sub4 &&
+ test_create_commit "$test_count/sub proj" sub4 &&
(
- cd "$subtree_test_count" &&
+ cd "$test_count" &&
git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
) &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub4 &&
+ test_create_commit "$test_count" "sub dir"/main-sub4 &&
(
- cd "$subtree_test_count" &&
+ cd "$test_count" &&
git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
) &&
(
- cd "$subtree_test_count/sub proj" &&
+ cd "$test_count/sub proj" &&
git fetch .. subproj-br &&
git merge FETCH_HEAD &&
@@ -547,46 +1006,45 @@ test_expect_success 'make sure exactly the right set of files ends up in the sub
)
'
-next_test
test_expect_success 'make sure the subproj *only* contains commits that affect the "sub dir"' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree add --prefix="sub dir" FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub1 &&
- test_create_commit "$subtree_test_count" main2 &&
- test_create_commit "$subtree_test_count/sub proj" sub2 &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree merge --prefix="sub dir" FETCH_HEAD &&
git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
) &&
- test_create_commit "$subtree_test_count/sub proj" sub3 &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub3 &&
+ test_create_commit "$test_count/sub proj" sub3 &&
+ test_create_commit "$test_count" "sub dir"/main-sub3 &&
(
- cd "$subtree_test_count/sub proj" &&
+ cd "$test_count/sub proj" &&
git fetch .. subproj-br &&
git merge FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count/sub proj" sub4 &&
+ test_create_commit "$test_count/sub proj" sub4 &&
(
- cd "$subtree_test_count" &&
+ cd "$test_count" &&
git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
) &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub4 &&
+ test_create_commit "$test_count" "sub dir"/main-sub4 &&
(
- cd "$subtree_test_count" &&
+ cd "$test_count" &&
git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
) &&
(
- cd "$subtree_test_count/sub proj" &&
+ cd "$test_count/sub proj" &&
git fetch .. subproj-br &&
git merge FETCH_HEAD &&
@@ -598,52 +1056,51 @@ test_expect_success 'make sure the subproj *only* contains commits that affect t
)
'
-next_test
test_expect_success 'make sure exactly the right set of files ends up in the mainline' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree add --prefix="sub dir" FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub1 &&
- test_create_commit "$subtree_test_count" main2 &&
- test_create_commit "$subtree_test_count/sub proj" sub2 &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree merge --prefix="sub dir" FETCH_HEAD &&
git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
) &&
- test_create_commit "$subtree_test_count/sub proj" sub3 &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub3 &&
+ test_create_commit "$test_count/sub proj" sub3 &&
+ test_create_commit "$test_count" "sub dir"/main-sub3 &&
(
- cd "$subtree_test_count/sub proj" &&
+ cd "$test_count/sub proj" &&
git fetch .. subproj-br &&
git merge FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count/sub proj" sub4 &&
+ test_create_commit "$test_count/sub proj" sub4 &&
(
- cd "$subtree_test_count" &&
+ cd "$test_count" &&
git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
) &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub4 &&
+ test_create_commit "$test_count" "sub dir"/main-sub4 &&
(
- cd "$subtree_test_count" &&
+ cd "$test_count" &&
git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
) &&
(
- cd "$subtree_test_count/sub proj" &&
+ cd "$test_count/sub proj" &&
git fetch .. subproj-br &&
git merge FETCH_HEAD
) &&
(
- cd "$subtree_test_count" &&
- git subtree pull --prefix="sub dir" ./"sub proj" master &&
+ cd "$test_count" &&
+ git subtree pull --prefix="sub dir" ./"sub proj" HEAD &&
test_write_lines main1 main2 >chkm &&
test_write_lines main-sub1 main-sub2 main-sub3 main-sub4 >chkms &&
@@ -657,53 +1114,52 @@ test_expect_success 'make sure exactly the right set of files ends up in the mai
)
'
-next_test
test_expect_success 'make sure each filename changed exactly once in the entire history' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
+ cd "$test_count" &&
git config log.date relative &&
- git fetch ./"sub proj" master &&
+ git fetch ./"sub proj" HEAD &&
git subtree add --prefix="sub dir" FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub1 &&
- test_create_commit "$subtree_test_count" main2 &&
- test_create_commit "$subtree_test_count/sub proj" sub2 &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree merge --prefix="sub dir" FETCH_HEAD &&
git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
) &&
- test_create_commit "$subtree_test_count/sub proj" sub3 &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub3 &&
+ test_create_commit "$test_count/sub proj" sub3 &&
+ test_create_commit "$test_count" "sub dir"/main-sub3 &&
(
- cd "$subtree_test_count/sub proj" &&
+ cd "$test_count/sub proj" &&
git fetch .. subproj-br &&
git merge FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count/sub proj" sub4 &&
+ test_create_commit "$test_count/sub proj" sub4 &&
(
- cd "$subtree_test_count" &&
+ cd "$test_count" &&
git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
) &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub4 &&
+ test_create_commit "$test_count" "sub dir"/main-sub4 &&
(
- cd "$subtree_test_count" &&
+ cd "$test_count" &&
git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
) &&
(
- cd "$subtree_test_count/sub proj" &&
+ cd "$test_count/sub proj" &&
git fetch .. subproj-br &&
git merge FETCH_HEAD
) &&
(
- cd "$subtree_test_count" &&
- git subtree pull --prefix="sub dir" ./"sub proj" master &&
+ cd "$test_count" &&
+ git subtree pull --prefix="sub dir" ./"sub proj" HEAD &&
test_write_lines main1 main2 >chkm &&
test_write_lines sub1 sub2 sub3 sub4 >chks &&
@@ -723,105 +1179,103 @@ test_expect_success 'make sure each filename changed exactly once in the entire
)
'
-next_test
test_expect_success 'make sure the --rejoin commits never make it into subproj' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree add --prefix="sub dir" FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub1 &&
- test_create_commit "$subtree_test_count" main2 &&
- test_create_commit "$subtree_test_count/sub proj" sub2 &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree merge --prefix="sub dir" FETCH_HEAD &&
git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
) &&
- test_create_commit "$subtree_test_count/sub proj" sub3 &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub3 &&
+ test_create_commit "$test_count/sub proj" sub3 &&
+ test_create_commit "$test_count" "sub dir"/main-sub3 &&
(
- cd "$subtree_test_count/sub proj" &&
+ cd "$test_count/sub proj" &&
git fetch .. subproj-br &&
git merge FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count/sub proj" sub4 &&
+ test_create_commit "$test_count/sub proj" sub4 &&
(
- cd "$subtree_test_count" &&
+ cd "$test_count" &&
git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
) &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub4 &&
+ test_create_commit "$test_count" "sub dir"/main-sub4 &&
(
- cd "$subtree_test_count" &&
+ cd "$test_count" &&
git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
) &&
(
- cd "$subtree_test_count/sub proj" &&
+ cd "$test_count/sub proj" &&
git fetch .. subproj-br &&
git merge FETCH_HEAD
) &&
(
- cd "$subtree_test_count" &&
- git subtree pull --prefix="sub dir" ./"sub proj" master &&
- check_equal "$(git log --pretty=format:"%s" HEAD^2 | grep -i split)" ""
+ cd "$test_count" &&
+ git subtree pull --prefix="sub dir" ./"sub proj" HEAD &&
+ test "$(git log --pretty=format:"%s" HEAD^2 | grep -i split)" = ""
)
'
-next_test
test_expect_success 'make sure no "git subtree" tagged commits make it into subproj' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree add --prefix="sub dir" FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub1 &&
- test_create_commit "$subtree_test_count" main2 &&
- test_create_commit "$subtree_test_count/sub proj" sub2 &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree merge --prefix="sub dir" FETCH_HEAD &&
git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
) &&
- test_create_commit "$subtree_test_count/sub proj" sub3 &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub3 &&
+ test_create_commit "$test_count/sub proj" sub3 &&
+ test_create_commit "$test_count" "sub dir"/main-sub3 &&
(
- cd "$subtree_test_count/sub proj" &&
+ cd "$test_count/sub proj" &&
git fetch .. subproj-br &&
git merge FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count/sub proj" sub4 &&
+ test_create_commit "$test_count/sub proj" sub4 &&
(
- cd "$subtree_test_count" &&
+ cd "$test_count" &&
git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
) &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub4 &&
+ test_create_commit "$test_count" "sub dir"/main-sub4 &&
(
- cd "$subtree_test_count" &&
+ cd "$test_count" &&
git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
) &&
(
- cd "$subtree_test_count/sub proj" &&
+ cd "$test_count/sub proj" &&
git fetch .. subproj-br &&
git merge FETCH_HEAD
) &&
(
- cd "$subtree_test_count" &&
- git subtree pull --prefix="sub dir" ./"sub proj" master &&
+ cd "$test_count" &&
+ git subtree pull --prefix="sub dir" ./"sub proj" HEAD &&
# They are meaningless to subproj since one side of the merge refers to the mainline
- check_equal "$(git log --pretty=format:"%s%n%b" HEAD^2 | grep "git-subtree.*:")" ""
+ test "$(git log --pretty=format:"%s%n%b" HEAD^2 | grep "git-subtree.*:")" = ""
)
'
@@ -829,145 +1283,135 @@ test_expect_success 'make sure no "git subtree" tagged commits make it into subp
# A new set of tests
#
-next_test
test_expect_success 'make sure "git subtree split" find the correct parent' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree add --prefix="sub dir" FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count/sub proj" sub2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git branch subproj-ref FETCH_HEAD &&
git subtree merge --prefix="sub dir" FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
(
- cd "$subtree_test_count" &&
+ cd "$test_count" &&
git subtree split --prefix="sub dir" --branch subproj-br &&
# at this point, the new commit parent should be subproj-ref, if it is
- # not, something went wrong (the "newparent" of "master~" commit should
+ # not, something went wrong (the "newparent" of "HEAD~" commit should
# have been sub2, but it was not, because its cache was not set to
# itself)
- check_equal "$(git log --pretty=format:%P -1 subproj-br)" "$(git rev-parse subproj-ref)"
+ test "$(git log --pretty=format:%P -1 subproj-br)" = "$(git rev-parse subproj-ref)"
)
'
-next_test
test_expect_success 'split a new subtree without --onto option' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree add --prefix="sub dir" FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count/sub proj" sub2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree merge --prefix="sub dir" FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
(
- cd "$subtree_test_count" &&
+ cd "$test_count" &&
git subtree split --prefix="sub dir" --branch subproj-br
) &&
- mkdir "$subtree_test_count"/"sub dir2" &&
- test_create_commit "$subtree_test_count" "sub dir2"/main-sub2 &&
+ mkdir "$test_count"/"sub dir2" &&
+ test_create_commit "$test_count" "sub dir2"/main-sub2 &&
(
- cd "$subtree_test_count" &&
+ cd "$test_count" &&
# also test that we still can split out an entirely new subtree
# if the parent of the first commit in the tree is not empty,
# then the new subtree has accidentally been attached to something
git subtree split --prefix="sub dir2" --branch subproj2-br &&
- check_equal "$(git log --pretty=format:%P -1 subproj2-br)" ""
+ test "$(git log --pretty=format:%P -1 subproj2-br)" = ""
)
'
-next_test
test_expect_success 'verify one file change per commit' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git branch sub1 FETCH_HEAD &&
git subtree add --prefix="sub dir" sub1
) &&
- test_create_commit "$subtree_test_count/sub proj" sub2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree merge --prefix="sub dir" FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
(
- cd "$subtree_test_count" &&
+ cd "$test_count" &&
git subtree split --prefix="sub dir" --branch subproj-br
) &&
- mkdir "$subtree_test_count"/"sub dir2" &&
- test_create_commit "$subtree_test_count" "sub dir2"/main-sub2 &&
+ mkdir "$test_count"/"sub dir2" &&
+ test_create_commit "$test_count" "sub dir2"/main-sub2 &&
(
- cd "$subtree_test_count" &&
+ cd "$test_count" &&
git subtree split --prefix="sub dir2" --branch subproj2-br &&
- x= &&
- git log --pretty=format:"commit: %H" | join_commits |
- (
- while read commit a b; do
- test_debug "echo Verifying commit $commit"
- test_debug "echo a: $a"
- test_debug "echo b: $b"
- check_equal "$b" ""
- x=1
- done
- check_equal "$x" 1
- )
+ git log --format="%H" >commit-list &&
+ while read commit
+ do
+ git log -n1 --format="" --name-only "$commit" >file-list &&
+ test_line_count -le 1 file-list || return 1
+ done <commit-list
)
'
-next_test
test_expect_success 'push split to subproj' '
- subtree_test_create_repo "$subtree_test_count" &&
- subtree_test_create_repo "$subtree_test_count/sub proj" &&
- test_create_commit "$subtree_test_count" main1 &&
- test_create_commit "$subtree_test_count/sub proj" sub1 &&
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
(
- cd "$subtree_test_count" &&
- git fetch ./"sub proj" master &&
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
git subtree add --prefix="sub dir" FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub1 &&
- test_create_commit "$subtree_test_count" main2 &&
- test_create_commit "$subtree_test_count/sub proj" sub2 &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub2 &&
- (
- cd $subtree_test_count/"sub proj" &&
- git branch sub-branch-1 &&
- cd .. &&
- git fetch ./"sub proj" master &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd $test_count/"sub proj" &&
+ git branch sub-branch-1 &&
+ cd .. &&
+ git fetch ./"sub proj" HEAD &&
git subtree merge --prefix="sub dir" FETCH_HEAD
) &&
- test_create_commit "$subtree_test_count" "sub dir"/main-sub3 &&
- (
- cd "$subtree_test_count" &&
- git subtree push ./"sub proj" --prefix "sub dir" sub-branch-1 &&
- cd ./"sub proj" &&
- git checkout sub-branch-1 &&
- check_equal "$(last_commit_message)" "sub dir/main-sub3"
+ test_create_commit "$test_count" "sub dir"/main-sub3 &&
+ (
+ cd "$test_count" &&
+ git subtree push ./"sub proj" --prefix "sub dir" sub-branch-1 &&
+ cd ./"sub proj" &&
+ git checkout sub-branch-1 &&
+ test "$(last_commit_subject)" = "sub dir/main-sub3"
)
'
@@ -991,43 +1435,43 @@ test_expect_success 'push split to subproj' '
# set of commits.
#
-next_test
test_expect_success 'subtree descendant check' '
- subtree_test_create_repo "$subtree_test_count" &&
- test_create_commit "$subtree_test_count" folder_subtree/a &&
+ subtree_test_create_repo "$test_count" &&
+ defaultBranch=$(sed "s,ref: refs/heads/,," "$test_count/.git/HEAD") &&
+ test_create_commit "$test_count" folder_subtree/a &&
(
- cd "$subtree_test_count" &&
+ cd "$test_count" &&
git branch branch
) &&
- test_create_commit "$subtree_test_count" folder_subtree/0 &&
- test_create_commit "$subtree_test_count" folder_subtree/b &&
- cherry=$(cd "$subtree_test_count"; git rev-parse HEAD) &&
+ test_create_commit "$test_count" folder_subtree/0 &&
+ test_create_commit "$test_count" folder_subtree/b &&
+ cherry=$(cd "$test_count"; git rev-parse HEAD) &&
(
- cd "$subtree_test_count" &&
+ cd "$test_count" &&
git checkout branch
) &&
- test_create_commit "$subtree_test_count" commit_on_branch &&
+ test_create_commit "$test_count" commit_on_branch &&
(
- cd "$subtree_test_count" &&
+ cd "$test_count" &&
git cherry-pick $cherry &&
- git checkout master &&
+ git checkout $defaultBranch &&
git merge -m "merge should be kept on subtree" branch &&
git branch no_subtree_work_branch
) &&
- test_create_commit "$subtree_test_count" folder_subtree/d &&
+ test_create_commit "$test_count" folder_subtree/d &&
(
- cd "$subtree_test_count" &&
+ cd "$test_count" &&
git checkout no_subtree_work_branch
) &&
- test_create_commit "$subtree_test_count" not_a_subtree_change &&
+ test_create_commit "$test_count" not_a_subtree_change &&
(
- cd "$subtree_test_count" &&
- git checkout master &&
+ cd "$test_count" &&
+ git checkout $defaultBranch &&
git merge -m "merge should be skipped on subtree" no_subtree_work_branch &&
- git subtree split --prefix folder_subtree/ --branch subtree_tip master &&
+ git subtree split --prefix folder_subtree/ --branch subtree_tip $defaultBranch &&
git subtree split --prefix folder_subtree/ --branch subtree_branch branch &&
- check_equal $(git rev-list --count subtree_tip..subtree_branch) 0
+ test $(git rev-list --count subtree_tip..subtree_branch) = 0
)
'
diff --git a/contrib/subtree/todo b/contrib/subtree/todo
index 0d0e777651..32d2ce3a40 100644
--- a/contrib/subtree/todo
+++ b/contrib/subtree/todo
@@ -23,9 +23,9 @@
"pull" and "merge" commands should fail if you've never merged
that --prefix before
-
+
docs should provide an example of "add"
-
+
note that the initial split doesn't *have* to have a commitid
specified... that's just an optimization
@@ -33,7 +33,7 @@
get a misleading "prefix must end with /" message from
one of the other git tools that git-subtree calls. Should
detect this situation and print the *real* problem.
-
+
"pull --squash" should do fetch-synthesize-merge, but instead just
does "pull" directly, which doesn't work at all.
diff --git a/convert.c b/convert.c
index ee360c2f07..fd9c84b025 100644
--- a/convert.c
+++ b/convert.c
@@ -24,17 +24,6 @@
#define CONVERT_STAT_BITS_TXT_CRLF 0x2
#define CONVERT_STAT_BITS_BIN 0x4
-enum crlf_action {
- CRLF_UNDEFINED,
- CRLF_BINARY,
- CRLF_TEXT,
- CRLF_TEXT_INPUT,
- CRLF_TEXT_CRLF,
- CRLF_AUTO,
- CRLF_AUTO_INPUT,
- CRLF_AUTO_CRLF
-};
-
struct text_stat {
/* NUL, CR, LF and CRLF counts */
unsigned nul, lonecr, lonelf, crlf;
@@ -138,7 +127,7 @@ static const char *gather_convert_stats_ascii(const char *data, unsigned long si
}
}
-const char *get_cached_convert_stats_ascii(const struct index_state *istate,
+const char *get_cached_convert_stats_ascii(struct index_state *istate,
const char *path)
{
const char *ret;
@@ -172,7 +161,7 @@ static int text_eol_is_crlf(void)
return 0;
}
-static enum eol output_eol(enum crlf_action crlf_action)
+static enum eol output_eol(enum convert_crlf_action crlf_action)
{
switch (crlf_action) {
case CRLF_BINARY:
@@ -222,7 +211,7 @@ static void check_global_conv_flags_eol(const char *path,
}
}
-static int has_crlf_in_index(const struct index_state *istate, const char *path)
+static int has_crlf_in_index(struct index_state *istate, const char *path)
{
unsigned long sz;
void *data;
@@ -246,7 +235,7 @@ static int has_crlf_in_index(const struct index_state *istate, const char *path)
}
static int will_convert_lf_to_crlf(struct text_stat *stats,
- enum crlf_action crlf_action)
+ enum convert_crlf_action crlf_action)
{
if (output_eol(crlf_action) != EOL_CRLF)
return 0;
@@ -496,10 +485,10 @@ static int encode_to_worktree(const char *path, const char *src, size_t src_len,
return 1;
}
-static int crlf_to_git(const struct index_state *istate,
+static int crlf_to_git(struct index_state *istate,
const char *path, const char *src, size_t len,
struct strbuf *buf,
- enum crlf_action crlf_action, int conv_flags)
+ enum convert_crlf_action crlf_action, int conv_flags)
{
struct text_stat stats;
char *dst;
@@ -585,8 +574,8 @@ static int crlf_to_git(const struct index_state *istate,
return 1;
}
-static int crlf_to_worktree(const char *src, size_t len,
- struct strbuf *buf, enum crlf_action crlf_action)
+static int crlf_to_worktree(const char *src, size_t len, struct strbuf *buf,
+ enum convert_crlf_action crlf_action)
{
char *to_free = NULL;
struct text_stat stats;
@@ -884,9 +873,13 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len
goto done;
if (fd >= 0)
- err = write_packetized_from_fd(fd, process->in);
+ err = write_packetized_from_fd_no_flush(fd, process->in);
else
- err = write_packetized_from_buf(src, len, process->in);
+ err = write_packetized_from_buf_no_flush(src, len, process->in);
+ if (err)
+ goto done;
+
+ err = packet_flush_gently(process->in);
if (err)
goto done;
@@ -903,7 +896,8 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len
if (err)
goto done;
- err = read_packetized_to_strbuf(process->out, &nbuf) < 0;
+ err = read_packetized_to_strbuf(process->out, &nbuf,
+ PACKET_READ_GENTLE_ON_EOF) < 0;
if (err)
goto done;
@@ -1028,7 +1022,7 @@ static int read_convert_config(const char *var, const char *value, void *cb)
if (!strncmp(drv->name, name, namelen) && !drv->name[namelen])
break;
if (!drv) {
- drv = xcalloc(1, sizeof(struct convert_driver));
+ CALLOC_ARRAY(drv, 1);
drv->name = xmemdupz(name, namelen);
*user_convert_tail = drv;
user_convert_tail = &(drv->next);
@@ -1247,7 +1241,7 @@ static const char *git_path_check_encoding(struct attr_check_item *check)
return value;
}
-static enum crlf_action git_path_check_crlf(struct attr_check_item *check)
+static enum convert_crlf_action git_path_check_crlf(struct attr_check_item *check)
{
const char *value = check->value;
@@ -1297,18 +1291,10 @@ static int git_path_check_ident(struct attr_check_item *check)
return !!ATTR_TRUE(value);
}
-struct conv_attrs {
- struct convert_driver *drv;
- enum crlf_action attr_action; /* What attr says */
- enum crlf_action crlf_action; /* When no attr is set, use core.autocrlf */
- int ident;
- const char *working_tree_encoding; /* Supported encoding or default encoding if NULL */
-};
-
static struct attr_check *check;
-static void convert_attrs(const struct index_state *istate,
- struct conv_attrs *ca, const char *path)
+void convert_attrs(struct index_state *istate,
+ struct conv_attrs *ca, const char *path)
{
struct attr_check_item *ccheck = NULL;
@@ -1369,7 +1355,7 @@ void reset_parsed_attributes(void)
user_convert_tail = NULL;
}
-int would_convert_to_git_filter_fd(const struct index_state *istate, const char *path)
+int would_convert_to_git_filter_fd(struct index_state *istate, const char *path)
{
struct conv_attrs ca;
@@ -1388,7 +1374,7 @@ int would_convert_to_git_filter_fd(const struct index_state *istate, const char
return apply_filter(path, NULL, 0, -1, NULL, ca.drv, CAP_CLEAN, NULL, NULL);
}
-const char *get_convert_attr_ascii(const struct index_state *istate, const char *path)
+const char *get_convert_attr_ascii(struct index_state *istate, const char *path)
{
struct conv_attrs ca;
@@ -1414,7 +1400,7 @@ const char *get_convert_attr_ascii(const struct index_state *istate, const char
return "";
}
-int convert_to_git(const struct index_state *istate,
+int convert_to_git(struct index_state *istate,
const char *path, const char *src, size_t len,
struct strbuf *dst, int conv_flags)
{
@@ -1448,7 +1434,7 @@ int convert_to_git(const struct index_state *istate,
return ret | ident_to_git(src, len, dst, ca.ident);
}
-void convert_to_git_filter_fd(const struct index_state *istate,
+void convert_to_git_filter_fd(struct index_state *istate,
const char *path, int fd, struct strbuf *dst,
int conv_flags)
{
@@ -1456,7 +1442,6 @@ void convert_to_git_filter_fd(const struct index_state *istate,
convert_attrs(istate, &ca, path);
assert(ca.drv);
- assert(ca.drv->clean || ca.drv->process);
if (!apply_filter(path, NULL, 0, fd, dst, ca.drv, CAP_CLEAN, NULL, NULL))
die(_("%s: clean filter '%s' failed"), path, ca.drv->name);
@@ -1466,19 +1451,16 @@ void convert_to_git_filter_fd(const struct index_state *istate,
ident_to_git(dst->buf, dst->len, dst, ca.ident);
}
-static int convert_to_working_tree_internal(const struct index_state *istate,
- const char *path, const char *src,
- size_t len, struct strbuf *dst,
- int normalizing,
- const struct checkout_metadata *meta,
- struct delayed_checkout *dco)
+static int convert_to_working_tree_ca_internal(const struct conv_attrs *ca,
+ const char *path, const char *src,
+ size_t len, struct strbuf *dst,
+ int normalizing,
+ const struct checkout_metadata *meta,
+ struct delayed_checkout *dco)
{
int ret = 0, ret_filter = 0;
- struct conv_attrs ca;
- convert_attrs(istate, &ca, path);
-
- ret |= ident_to_worktree(src, len, dst, ca.ident);
+ ret |= ident_to_worktree(src, len, dst, ca->ident);
if (ret) {
src = dst->buf;
len = dst->len;
@@ -1488,49 +1470,56 @@ static int convert_to_working_tree_internal(const struct index_state *istate,
* is a smudge or process filter (even if the process filter doesn't
* support smudge). The filters might expect CRLFs.
*/
- if ((ca.drv && (ca.drv->smudge || ca.drv->process)) || !normalizing) {
- ret |= crlf_to_worktree(src, len, dst, ca.crlf_action);
+ if ((ca->drv && (ca->drv->smudge || ca->drv->process)) || !normalizing) {
+ ret |= crlf_to_worktree(src, len, dst, ca->crlf_action);
if (ret) {
src = dst->buf;
len = dst->len;
}
}
- ret |= encode_to_worktree(path, src, len, dst, ca.working_tree_encoding);
+ ret |= encode_to_worktree(path, src, len, dst, ca->working_tree_encoding);
if (ret) {
src = dst->buf;
len = dst->len;
}
ret_filter = apply_filter(
- path, src, len, -1, dst, ca.drv, CAP_SMUDGE, meta, dco);
- if (!ret_filter && ca.drv && ca.drv->required)
- die(_("%s: smudge filter %s failed"), path, ca.drv->name);
+ path, src, len, -1, dst, ca->drv, CAP_SMUDGE, meta, dco);
+ if (!ret_filter && ca->drv && ca->drv->required)
+ die(_("%s: smudge filter %s failed"), path, ca->drv->name);
return ret | ret_filter;
}
-int async_convert_to_working_tree(const struct index_state *istate,
- const char *path, const char *src,
- size_t len, struct strbuf *dst,
- const struct checkout_metadata *meta,
- void *dco)
+int async_convert_to_working_tree_ca(const struct conv_attrs *ca,
+ const char *path, const char *src,
+ size_t len, struct strbuf *dst,
+ const struct checkout_metadata *meta,
+ void *dco)
{
- return convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, dco);
+ return convert_to_working_tree_ca_internal(ca, path, src, len, dst, 0,
+ meta, dco);
}
-int convert_to_working_tree(const struct index_state *istate,
- const char *path, const char *src,
- size_t len, struct strbuf *dst,
- const struct checkout_metadata *meta)
+int convert_to_working_tree_ca(const struct conv_attrs *ca,
+ const char *path, const char *src,
+ size_t len, struct strbuf *dst,
+ const struct checkout_metadata *meta)
{
- return convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, NULL);
+ return convert_to_working_tree_ca_internal(ca, path, src, len, dst, 0,
+ meta, NULL);
}
-int renormalize_buffer(const struct index_state *istate, const char *path,
+int renormalize_buffer(struct index_state *istate, const char *path,
const char *src, size_t len, struct strbuf *dst)
{
- int ret = convert_to_working_tree_internal(istate, path, src, len, dst, 1, NULL, NULL);
+ struct conv_attrs ca;
+ int ret;
+
+ convert_attrs(istate, &ca, path);
+ ret = convert_to_working_tree_ca_internal(&ca, path, src, len, dst, 1,
+ NULL, NULL);
if (ret) {
src = dst->buf;
len = dst->len;
@@ -1957,34 +1946,25 @@ static struct stream_filter *ident_filter(const struct object_id *oid)
}
/*
- * Return an appropriately constructed filter for the path, or NULL if
+ * Return an appropriately constructed filter for the given ca, or NULL if
* the contents cannot be filtered without reading the whole thing
* in-core.
*
* Note that you would be crazy to set CRLF, smudge/clean or ident to a
* large binary blob you would want us not to slurp into the memory!
*/
-struct stream_filter *get_stream_filter(const struct index_state *istate,
- const char *path,
- const struct object_id *oid)
+struct stream_filter *get_stream_filter_ca(const struct conv_attrs *ca,
+ const struct object_id *oid)
{
- struct conv_attrs ca;
struct stream_filter *filter = NULL;
- convert_attrs(istate, &ca, path);
- if (ca.drv && (ca.drv->process || ca.drv->smudge || ca.drv->clean))
+ if (classify_conv_attrs(ca) != CA_CLASS_STREAMABLE)
return NULL;
- if (ca.working_tree_encoding)
- return NULL;
-
- if (ca.crlf_action == CRLF_AUTO || ca.crlf_action == CRLF_AUTO_CRLF)
- return NULL;
-
- if (ca.ident)
+ if (ca->ident)
filter = ident_filter(oid);
- if (output_eol(ca.crlf_action) == EOL_CRLF)
+ if (output_eol(ca->crlf_action) == EOL_CRLF)
filter = cascade_filter(filter, lf_to_crlf_filter());
else
filter = cascade_filter(filter, &null_filter_singleton);
@@ -1992,6 +1972,15 @@ struct stream_filter *get_stream_filter(const struct index_state *istate,
return filter;
}
+struct stream_filter *get_stream_filter(struct index_state *istate,
+ const char *path,
+ const struct object_id *oid)
+{
+ struct conv_attrs ca;
+ convert_attrs(istate, &ca, path);
+ return get_stream_filter_ca(&ca, oid);
+}
+
void free_stream_filter(struct stream_filter *filter)
{
filter->vtbl->free(filter);
@@ -2025,3 +2014,21 @@ void clone_checkout_metadata(struct checkout_metadata *dst,
if (blob)
oidcpy(&dst->blob, blob);
}
+
+enum conv_attrs_classification classify_conv_attrs(const struct conv_attrs *ca)
+{
+ if (ca->drv) {
+ if (ca->drv->process)
+ return CA_CLASS_INCORE_PROCESS;
+ if (ca->drv->smudge || ca->drv->clean)
+ return CA_CLASS_INCORE_FILTER;
+ }
+
+ if (ca->working_tree_encoding)
+ return CA_CLASS_INCORE;
+
+ if (ca->crlf_action == CRLF_AUTO || ca->crlf_action == CRLF_AUTO_CRLF)
+ return CA_CLASS_INCORE;
+
+ return CA_CLASS_STREAMABLE;
+}
diff --git a/convert.h b/convert.h
index e29d1026a6..5ee1c32205 100644
--- a/convert.h
+++ b/convert.h
@@ -63,43 +63,86 @@ struct checkout_metadata {
struct object_id blob;
};
+enum convert_crlf_action {
+ CRLF_UNDEFINED,
+ CRLF_BINARY,
+ CRLF_TEXT,
+ CRLF_TEXT_INPUT,
+ CRLF_TEXT_CRLF,
+ CRLF_AUTO,
+ CRLF_AUTO_INPUT,
+ CRLF_AUTO_CRLF
+};
+
+struct convert_driver;
+
+struct conv_attrs {
+ struct convert_driver *drv;
+ enum convert_crlf_action attr_action; /* What attr says */
+ enum convert_crlf_action crlf_action; /* When no attr is set, use core.autocrlf */
+ int ident;
+ const char *working_tree_encoding; /* Supported encoding or default encoding if NULL */
+};
+
+void convert_attrs(struct index_state *istate,
+ struct conv_attrs *ca, const char *path);
+
extern enum eol core_eol;
extern char *check_roundtrip_encoding;
-const char *get_cached_convert_stats_ascii(const struct index_state *istate,
+const char *get_cached_convert_stats_ascii(struct index_state *istate,
const char *path);
const char *get_wt_convert_stats_ascii(const char *path);
-const char *get_convert_attr_ascii(const struct index_state *istate,
+const char *get_convert_attr_ascii(struct index_state *istate,
const char *path);
/* returns 1 if *dst was used */
-int convert_to_git(const struct index_state *istate,
+int convert_to_git(struct index_state *istate,
const char *path, const char *src, size_t len,
struct strbuf *dst, int conv_flags);
-int convert_to_working_tree(const struct index_state *istate,
- const char *path, const char *src,
- size_t len, struct strbuf *dst,
- const struct checkout_metadata *meta);
-int async_convert_to_working_tree(const struct index_state *istate,
- const char *path, const char *src,
- size_t len, struct strbuf *dst,
- const struct checkout_metadata *meta,
- void *dco);
+int convert_to_working_tree_ca(const struct conv_attrs *ca,
+ const char *path, const char *src,
+ size_t len, struct strbuf *dst,
+ const struct checkout_metadata *meta);
+int async_convert_to_working_tree_ca(const struct conv_attrs *ca,
+ const char *path, const char *src,
+ size_t len, struct strbuf *dst,
+ const struct checkout_metadata *meta,
+ void *dco);
+static inline int convert_to_working_tree(struct index_state *istate,
+ const char *path, const char *src,
+ size_t len, struct strbuf *dst,
+ const struct checkout_metadata *meta)
+{
+ struct conv_attrs ca;
+ convert_attrs(istate, &ca, path);
+ return convert_to_working_tree_ca(&ca, path, src, len, dst, meta);
+}
+static inline int async_convert_to_working_tree(struct index_state *istate,
+ const char *path, const char *src,
+ size_t len, struct strbuf *dst,
+ const struct checkout_metadata *meta,
+ void *dco)
+{
+ struct conv_attrs ca;
+ convert_attrs(istate, &ca, path);
+ return async_convert_to_working_tree_ca(&ca, path, src, len, dst, meta, dco);
+}
int async_query_available_blobs(const char *cmd,
struct string_list *available_paths);
-int renormalize_buffer(const struct index_state *istate,
+int renormalize_buffer(struct index_state *istate,
const char *path, const char *src, size_t len,
struct strbuf *dst);
-static inline int would_convert_to_git(const struct index_state *istate,
+static inline int would_convert_to_git(struct index_state *istate,
const char *path)
{
return convert_to_git(istate, path, NULL, 0, NULL, 0);
}
/* Precondition: would_convert_to_git_filter_fd(path) == true */
-void convert_to_git_filter_fd(const struct index_state *istate,
+void convert_to_git_filter_fd(struct index_state *istate,
const char *path, int fd,
struct strbuf *dst,
int conv_flags);
-int would_convert_to_git_filter_fd(const struct index_state *istate,
+int would_convert_to_git_filter_fd(struct index_state *istate,
const char *path);
/*
@@ -133,9 +176,11 @@ void reset_parsed_attributes(void);
struct stream_filter; /* opaque */
-struct stream_filter *get_stream_filter(const struct index_state *istate,
+struct stream_filter *get_stream_filter(struct index_state *istate,
const char *path,
const struct object_id *);
+struct stream_filter *get_stream_filter_ca(const struct conv_attrs *ca,
+ const struct object_id *oid);
void free_stream_filter(struct stream_filter *);
int is_null_stream_filter(struct stream_filter *);
@@ -155,4 +200,37 @@ int stream_filter(struct stream_filter *,
const char *input, size_t *isize_p,
char *output, size_t *osize_p);
+enum conv_attrs_classification {
+ /*
+ * The blob must be loaded into a buffer before it can be
+ * smudged. All smudging is done in-proc.
+ */
+ CA_CLASS_INCORE,
+
+ /*
+ * The blob must be loaded into a buffer, but uses a
+ * single-file driver filter, such as rot13.
+ */
+ CA_CLASS_INCORE_FILTER,
+
+ /*
+ * The blob must be loaded into a buffer, but uses a
+ * long-running driver process, such as LFS. This might or
+ * might not use delayed operations. (The important thing is
+ * that there is a single subordinate long-running process
+ * handling all associated blobs and in case of delayed
+ * operations, may hold per-blob state.)
+ */
+ CA_CLASS_INCORE_PROCESS,
+
+ /*
+ * The blob can be streamed and smudged without needing to
+ * completely read it into a buffer.
+ */
+ CA_CLASS_STREAMABLE,
+};
+
+enum conv_attrs_classification classify_conv_attrs(
+ const struct conv_attrs *ca);
+
#endif /* CONVERT_H */
diff --git a/csum-file.c b/csum-file.c
index 0f35fa5ee4..7510950fa3 100644
--- a/csum-file.c
+++ b/csum-file.c
@@ -89,32 +89,35 @@ int finalize_hashfile(struct hashfile *f, unsigned char *result, unsigned int fl
void hashwrite(struct hashfile *f, const void *buf, unsigned int count)
{
while (count) {
- unsigned offset = f->offset;
- unsigned left = sizeof(f->buffer) - offset;
+ unsigned left = sizeof(f->buffer) - f->offset;
unsigned nr = count > left ? left : count;
- const void *data;
if (f->do_crc)
f->crc32 = crc32(f->crc32, buf, nr);
if (nr == sizeof(f->buffer)) {
- /* process full buffer directly without copy */
- data = buf;
+ /*
+ * Flush a full batch worth of data directly
+ * from the input, skipping the memcpy() to
+ * the hashfile's buffer. In this block,
+ * f->offset is necessarily zero.
+ */
+ the_hash_algo->update_fn(&f->ctx, buf, nr);
+ flush(f, buf, nr);
} else {
- memcpy(f->buffer + offset, buf, nr);
- data = f->buffer;
+ /*
+ * Copy to the hashfile's buffer, flushing only
+ * if it became full.
+ */
+ memcpy(f->buffer + f->offset, buf, nr);
+ f->offset += nr;
+ left -= nr;
+ if (!left)
+ hashflush(f);
}
count -= nr;
- offset += nr;
buf = (char *) buf + nr;
- left -= nr;
- if (!left) {
- the_hash_algo->update_fn(&f->ctx, data, offset);
- flush(f, data, offset);
- offset = 0;
- }
- f->offset = offset;
}
}
diff --git a/daemon.c b/daemon.c
index 2ab7ea82eb..5c4cbad62d 100644
--- a/daemon.c
+++ b/daemon.c
@@ -566,14 +566,14 @@ static void parse_host_and_port(char *hostport, char **host,
/*
* Sanitize a string from the client so that it's OK to be inserted into a
- * filesystem path. Specifically, we disallow slashes, runs of "..", and
- * trailing and leading dots, which means that the client cannot escape
- * our base path via ".." traversal.
+ * filesystem path. Specifically, we disallow directory separators, runs
+ * of "..", and trailing and leading dots, which means that the client
+ * cannot escape our base path via ".." traversal.
*/
static void sanitize_client(struct strbuf *out, const char *in)
{
for (; *in; in++) {
- if (*in == '/')
+ if (is_dir_sep(*in))
continue;
if (*in == '.' && (!out->len || out->buf[out->len - 1] == '.'))
continue;
@@ -840,7 +840,7 @@ static void add_child(struct child_process *cld, struct sockaddr *addr, socklen_
{
struct child *newborn, **cradle;
- newborn = xcalloc(1, sizeof(*newborn));
+ CALLOC_ARRAY(newborn, 1);
live_children++;
memcpy(&newborn->cld, cld, sizeof(*cld));
memcpy(&newborn->address, addr, addrlen);
@@ -1148,7 +1148,7 @@ static int service_loop(struct socketlist *socklist)
struct pollfd *pfd;
int i;
- pfd = xcalloc(socklist->nr, sizeof(struct pollfd));
+ CALLOC_ARRAY(pfd, socklist->nr);
for (i = 0; i < socklist->nr; i++) {
pfd[i].fd = socklist->list[i];
diff --git a/decorate.c b/decorate.c
index a605b1b5f4..2036d15967 100644
--- a/decorate.c
+++ b/decorate.c
@@ -39,7 +39,7 @@ static void grow_decoration(struct decoration *n)
struct decoration_entry *old_entries = n->entries;
n->size = (old_size + 1000) * 3 / 2;
- n->entries = xcalloc(n->size, sizeof(struct decoration_entry));
+ CALLOC_ARRAY(n->entries, n->size);
n->nr = 0;
for (i = 0; i < old_size; i++) {
diff --git a/diff-lib.c b/diff-lib.c
index b73cc1859a..c2ac9250fe 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -28,9 +28,10 @@
* exists for ce that is a submodule -- it is a submodule that is not
* checked out). Return negative for an error.
*/
-static int check_removed(const struct cache_entry *ce, struct stat *st)
+static int check_removed(const struct index_state *istate, const struct cache_entry *ce, struct stat *st)
{
- if (lstat(ce->name, st) < 0) {
+ assert(is_fsmonitor_refreshed(istate));
+ if (!(ce->ce_flags & CE_FSMONITOR_VALID) && lstat(ce->name, st) < 0) {
if (!is_missing_file_error(errno))
return -1;
return 1;
@@ -136,7 +137,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
memset(&(dpath->parent[0]), 0,
sizeof(struct combine_diff_parent)*5);
- changed = check_removed(ce, &st);
+ changed = check_removed(istate, ce, &st);
if (!changed)
wt_mode = ce_mode_from_stat(ce, st.st_mode);
else {
@@ -216,7 +217,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
} else {
struct stat st;
- changed = check_removed(ce, &st);
+ changed = check_removed(istate, ce, &st);
if (changed) {
if (changed < 0) {
perror(ce->name);
@@ -231,7 +232,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
ce_intent_to_add(ce)) {
newmode = ce_mode_from_stat(ce, st.st_mode);
diff_addremove(&revs->diffopt, '+', newmode,
- &null_oid, 0, ce->name, 0);
+ null_oid(), 0, ce->name, 0);
continue;
}
@@ -248,7 +249,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
}
oldmode = ce->ce_mode;
old_oid = &ce->oid;
- new_oid = changed ? &null_oid : &ce->oid;
+ new_oid = changed ? null_oid() : &ce->oid;
diff_change(&revs->diffopt, oldmode, newmode,
old_oid, new_oid,
!is_null_oid(old_oid),
@@ -278,7 +279,8 @@ static void diff_index_show_file(struct rev_info *revs,
oid, oid_valid, ce->name, dirty_submodule);
}
-static int get_stat_data(const struct cache_entry *ce,
+static int get_stat_data(const struct index_state *istate,
+ const struct cache_entry *ce,
const struct object_id **oidp,
unsigned int *modep,
int cached, int match_missing,
@@ -290,7 +292,7 @@ static int get_stat_data(const struct cache_entry *ce,
if (!cached && !ce_uptodate(ce)) {
int changed;
struct stat st;
- changed = check_removed(ce, &st);
+ changed = check_removed(istate, ce, &st);
if (changed < 0)
return -1;
else if (changed) {
@@ -305,7 +307,7 @@ static int get_stat_data(const struct cache_entry *ce,
0, dirty_submodule);
if (changed) {
mode = ce_mode_from_stat(ce, st.st_mode);
- oid = &null_oid;
+ oid = null_oid();
}
}
@@ -321,12 +323,13 @@ static void show_new_file(struct rev_info *revs,
const struct object_id *oid;
unsigned int mode;
unsigned dirty_submodule = 0;
+ struct index_state *istate = revs->diffopt.repo->index;
/*
* New file in the index: it might actually be different in
* the working tree.
*/
- if (get_stat_data(new_file, &oid, &mode, cached, match_missing,
+ if (get_stat_data(istate, new_file, &oid, &mode, cached, match_missing,
&dirty_submodule, &revs->diffopt) < 0)
return;
@@ -342,8 +345,9 @@ static int show_modified(struct rev_info *revs,
unsigned int mode, oldmode;
const struct object_id *oid;
unsigned dirty_submodule = 0;
+ struct index_state *istate = revs->diffopt.repo->index;
- if (get_stat_data(new_entry, &oid, &mode, cached, match_missing,
+ if (get_stat_data(istate, new_entry, &oid, &mode, cached, match_missing,
&dirty_submodule, &revs->diffopt) < 0) {
if (report_missing)
diff_index_show_file(revs, "-", old_entry,
@@ -574,6 +578,7 @@ int run_diff_index(struct rev_info *revs, unsigned int option)
struct object_id oid;
const char *name;
char merge_base_hex[GIT_MAX_HEXSZ + 1];
+ struct index_state *istate = revs->diffopt.repo->index;
if (revs->pending.nr != 1)
BUG("run_diff_index must be passed exactly one tree");
@@ -581,6 +586,8 @@ int run_diff_index(struct rev_info *revs, unsigned int option)
trace_performance_enter();
ent = revs->pending.objects;
+ refresh_fsmonitor(istate);
+
if (merge_base) {
diff_get_merge_base(revs, &oid);
name = oid_to_hex_r(merge_base_hex, &oid);
diff --git a/diff-merges.c b/diff-merges.c
index 146bb50316..f3a9daed7e 100644
--- a/diff-merges.c
+++ b/diff-merges.c
@@ -2,6 +2,11 @@
#include "revision.h"
+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 void suppress(struct rev_info *revs)
{
revs->separate_merges = 0;
@@ -29,10 +34,10 @@ static void set_m(struct rev_info *revs)
{
/*
* To "diff-index", "-m" means "match missing", and to the "log"
- * family of commands, it means "show full diff for merges". Set
+ * family of commands, it means "show default diff for merges". Set
* both fields appropriately.
*/
- set_separate(revs);
+ set_to_default(revs);
revs->match_missing = 1;
}
@@ -50,33 +55,52 @@ static void set_dense_combined(struct rev_info *revs)
revs->dense_combined_merges = 1;
}
-static void set_diff_merges(struct rev_info *revs, const char *optarg)
+static diff_merges_setup_func_t func_by_opt(const char *optarg)
{
- if (!strcmp(optarg, "off") || !strcmp(optarg, "none")) {
- suppress(revs);
- /* Return early to leave revs->merges_need_diff unset */
- return;
- }
-
+ if (!strcmp(optarg, "off") || !strcmp(optarg, "none"))
+ return suppress;
if (!strcmp(optarg, "1") || !strcmp(optarg, "first-parent"))
- set_first_parent(revs);
- else if (!strcmp(optarg, "m") || !strcmp(optarg, "separate"))
- set_separate(revs);
+ return set_first_parent;
+ else if (!strcmp(optarg, "separate"))
+ return set_separate;
else if (!strcmp(optarg, "c") || !strcmp(optarg, "combined"))
- set_combined(revs);
+ return set_combined;
else if (!strcmp(optarg, "cc") || !strcmp(optarg, "dense-combined"))
- set_dense_combined(revs);
- else
+ return set_dense_combined;
+ else if (!strcmp(optarg, "m") || !strcmp(optarg, "on"))
+ return set_to_default;
+ return NULL;
+}
+
+static void set_diff_merges(struct rev_info *revs, const char *optarg)
+{
+ diff_merges_setup_func_t func = func_by_opt(optarg);
+
+ if (!func)
die(_("unknown value for --diff-merges: %s"), optarg);
- /* The flag is cleared by set_xxx() functions, so don't move this up */
- revs->merges_need_diff = 1;
+ func(revs);
+
+ /* NOTE: the merges_need_diff flag is cleared by func() call */
+ if (func != suppress)
+ revs->merges_need_diff = 1;
}
/*
* Public functions. They are in the order they are called.
*/
+int diff_merges_config(const char *value)
+{
+ diff_merges_setup_func_t func = func_by_opt(value);
+
+ if (!func)
+ return -1;
+
+ set_to_default = func;
+ return 0;
+}
+
int diff_merges_parse_opts(struct rev_info *revs, const char **argv)
{
int argcount = 1;
diff --git a/diff-merges.h b/diff-merges.h
index 659467c99a..09d9a6c9a4 100644
--- a/diff-merges.h
+++ b/diff-merges.h
@@ -9,6 +9,8 @@
struct rev_info;
+int diff_merges_config(const char *value);
+
int diff_merges_parse_opts(struct rev_info *revs, const char **argv);
void diff_merges_suppress(struct rev_info *revs);
diff --git a/diff-no-index.c b/diff-no-index.c
index 7814eabfe0..9a8b09346b 100644
--- a/diff-no-index.c
+++ b/diff-no-index.c
@@ -26,9 +26,8 @@ static int read_directory_contents(const char *path, struct string_list *list)
if (!(dir = opendir(path)))
return error("Could not open directory %s", path);
- while ((e = readdir(dir)))
- if (!is_dot_or_dotdot(e->d_name))
- string_list_insert(list, e->d_name);
+ while ((e = readdir_skip_dot_and_dotdot(dir)))
+ string_list_insert(list, e->d_name);
closedir(dir);
return 0;
@@ -83,7 +82,7 @@ static struct diff_filespec *noindex_filespec(const char *name, int mode)
if (!name)
name = "/dev/null";
s = alloc_filespec(name);
- fill_filespec(s, &null_oid, 0, mode);
+ fill_filespec(s, null_oid(), 0, mode);
if (name == file_from_standard_input)
populate_from_stdin(s);
return s;
diff --git a/diff.c b/diff.c
index 6956f5e335..52c791574b 100644
--- a/diff.c
+++ b/diff.c
@@ -2053,7 +2053,7 @@ static void fn_out_diff_words_aux(void *priv,
static int find_word_boundaries(mmfile_t *buffer, regex_t *word_regex,
int *begin, int *end)
{
- if (word_regex && *begin < buffer->size) {
+ while (word_regex && *begin < buffer->size) {
regmatch_t match[1];
if (!regexec_buf(word_regex, buffer->ptr + *begin,
buffer->size - *begin, 1, match, 0)) {
@@ -2061,9 +2061,13 @@ static int find_word_boundaries(mmfile_t *buffer, regex_t *word_regex,
'\n', match[0].rm_eo - match[0].rm_so);
*end = p ? p - buffer->ptr : match[0].rm_eo + *begin;
*begin += match[0].rm_so;
- return *begin >= *end;
+ if (*begin == *end)
+ (*begin)++;
+ else
+ return *begin > *end;
+ } else {
+ return -1;
}
- return -1;
}
/* find the next word */
@@ -2233,14 +2237,12 @@ static void init_diff_words_data(struct emit_callback *ecbdata,
struct diff_options *o = xmalloc(sizeof(struct diff_options));
memcpy(o, orig_opts, sizeof(struct diff_options));
- ecbdata->diff_words =
- xcalloc(1, sizeof(struct diff_words_data));
+ CALLOC_ARRAY(ecbdata->diff_words, 1);
ecbdata->diff_words->type = o->word_diff;
ecbdata->diff_words->opt = o;
if (orig_opts->emitted_symbols)
- o->emitted_symbols =
- xcalloc(1, sizeof(struct emitted_diff_symbols));
+ CALLOC_ARRAY(o->emitted_symbols, 1);
if (!o->word_regex)
o->word_regex = userdiff_word_regex(one, o->repo->index);
@@ -2509,7 +2511,7 @@ static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
const char *name_b)
{
struct diffstat_file *x;
- x = xcalloc(1, sizeof(*x));
+ CALLOC_ARRAY(x, 1);
ALLOC_GROW(diffstat->files, diffstat->nr + 1, diffstat->alloc);
diffstat->files[diffstat->nr++] = x;
if (name_b) {
@@ -4192,7 +4194,7 @@ static struct diff_tempfile *prepare_temp_file(struct repository *r,
die_errno("readlink(%s)", name);
prep_temp_blob(r->index, name, temp, sb.buf, sb.len,
(one->oid_valid ?
- &one->oid : &null_oid),
+ &one->oid : null_oid()),
(one->oid_valid ?
one->mode : S_IFLNK));
strbuf_release(&sb);
@@ -4201,7 +4203,7 @@ static struct diff_tempfile *prepare_temp_file(struct repository *r,
/* we can borrow from the file in the work tree */
temp->name = name;
if (!one->oid_valid)
- oid_to_hex_r(temp->hex, &null_oid);
+ oid_to_hex_r(temp->hex, null_oid());
else
oid_to_hex_r(temp->hex, &one->oid);
/* Even though we may sometimes borrow the
@@ -4918,7 +4920,7 @@ static int diff_opt_find_object(const struct option *option,
return error(_("unable to resolve '%s'"), arg);
if (!opt->objfind)
- opt->objfind = xcalloc(1, sizeof(*opt->objfind));
+ CALLOC_ARRAY(opt->objfind, 1);
opt->pickaxe_opts |= DIFF_PICKAXE_KIND_OBJFIND;
opt->flags.recursive = 1;
@@ -6236,7 +6238,7 @@ static int diff_get_patch_id(struct diff_options *options, struct object_id *oid
}
if (!stable)
- the_hash_algo->final_fn(oid->hash, &ctx);
+ the_hash_algo->final_oid_fn(oid, &ctx);
return 0;
}
diff --git a/diffcore-rename.c b/diffcore-rename.c
index 41558185ae..963ca58221 100644
--- a/diffcore-rename.c
+++ b/diffcore-rename.c
@@ -367,6 +367,361 @@ static int find_exact_renames(struct diff_options *options)
return renames;
}
+struct dir_rename_info {
+ struct strintmap idx_map;
+ struct strmap dir_rename_guess;
+ struct strmap *dir_rename_count;
+ struct strintmap *relevant_source_dirs;
+ unsigned setup;
+};
+
+static char *get_dirname(const char *filename)
+{
+ char *slash = strrchr(filename, '/');
+ return slash ? xstrndup(filename, slash - filename) : xstrdup("");
+}
+
+static void dirname_munge(char *filename)
+{
+ char *slash = strrchr(filename, '/');
+ if (!slash)
+ slash = filename;
+ *slash = '\0';
+}
+
+static const char *get_highest_rename_path(struct strintmap *counts)
+{
+ int highest_count = 0;
+ const char *highest_destination_dir = NULL;
+ struct hashmap_iter iter;
+ struct strmap_entry *entry;
+
+ strintmap_for_each_entry(counts, &iter, entry) {
+ const char *destination_dir = entry->key;
+ intptr_t count = (intptr_t)entry->value;
+ if (count > highest_count) {
+ highest_count = count;
+ highest_destination_dir = destination_dir;
+ }
+ }
+ return highest_destination_dir;
+}
+
+static char *UNKNOWN_DIR = "/"; /* placeholder -- short, illegal directory */
+
+static int dir_rename_already_determinable(struct strintmap *counts)
+{
+ struct hashmap_iter iter;
+ struct strmap_entry *entry;
+ int first = 0, second = 0, unknown = 0;
+ strintmap_for_each_entry(counts, &iter, entry) {
+ const char *destination_dir = entry->key;
+ intptr_t count = (intptr_t)entry->value;
+ if (!strcmp(destination_dir, UNKNOWN_DIR)) {
+ unknown = count;
+ } else if (count >= first) {
+ second = first;
+ first = count;
+ } else if (count >= second) {
+ second = count;
+ }
+ }
+ return first > second + unknown;
+}
+
+static void increment_count(struct dir_rename_info *info,
+ char *old_dir,
+ char *new_dir)
+{
+ struct strintmap *counts;
+ struct strmap_entry *e;
+
+ /* Get the {new_dirs -> counts} mapping using old_dir */
+ e = strmap_get_entry(info->dir_rename_count, old_dir);
+ if (e) {
+ counts = e->value;
+ } else {
+ counts = xmalloc(sizeof(*counts));
+ strintmap_init_with_options(counts, 0, NULL, 1);
+ strmap_put(info->dir_rename_count, old_dir, counts);
+ }
+
+ /* Increment the count for new_dir */
+ strintmap_incr(counts, new_dir, 1);
+}
+
+static void update_dir_rename_counts(struct dir_rename_info *info,
+ struct strintmap *dirs_removed,
+ const char *oldname,
+ const char *newname)
+{
+ char *old_dir = xstrdup(oldname);
+ char *new_dir = xstrdup(newname);
+ char new_dir_first_char = new_dir[0];
+ int first_time_in_loop = 1;
+
+ if (!info->setup)
+ /*
+ * info->setup is 0 here in two cases: (1) all auxiliary
+ * vars (like dirs_removed) were NULL so
+ * initialize_dir_rename_info() returned early, or (2)
+ * either break detection or copy detection are active so
+ * that we never called initialize_dir_rename_info(). In
+ * the former case, we don't have enough info to know if
+ * directories were renamed (because dirs_removed lets us
+ * know about a necessary prerequisite, namely if they were
+ * removed), and in the latter, we don't care about
+ * directory renames or find_basename_matches.
+ *
+ * This matters because both basename and inexact matching
+ * will also call update_dir_rename_counts(). In either of
+ * the above two cases info->dir_rename_counts will not
+ * have been properly initialized which prevents us from
+ * updating it, but in these two cases we don't care about
+ * dir_rename_counts anyway, so we can just exit early.
+ */
+ return;
+
+ while (1) {
+ int drd_flag = NOT_RELEVANT;
+
+ /* Get old_dir, skip if its directory isn't relevant. */
+ dirname_munge(old_dir);
+ if (info->relevant_source_dirs &&
+ !strintmap_contains(info->relevant_source_dirs, old_dir))
+ break;
+
+ /* Get new_dir */
+ dirname_munge(new_dir);
+
+ /*
+ * When renaming
+ * "a/b/c/d/e/foo.c" -> "a/b/some/thing/else/e/foo.c"
+ * then this suggests that both
+ * a/b/c/d/e/ => a/b/some/thing/else/e/
+ * a/b/c/d/ => a/b/some/thing/else/
+ * so we want to increment counters for both. We do NOT,
+ * however, also want to suggest that there was the following
+ * rename:
+ * a/b/c/ => a/b/some/thing/
+ * so we need to quit at that point.
+ *
+ * Note the when first_time_in_loop, we only strip off the
+ * basename, and we don't care if that's different.
+ */
+ if (!first_time_in_loop) {
+ char *old_sub_dir = strchr(old_dir, '\0')+1;
+ char *new_sub_dir = strchr(new_dir, '\0')+1;
+ if (!*new_dir) {
+ /*
+ * Special case when renaming to root directory,
+ * i.e. when new_dir == "". In this case, we had
+ * something like
+ * a/b/subdir => subdir
+ * and so dirname_munge() sets things up so that
+ * old_dir = "a/b\0subdir\0"
+ * new_dir = "\0ubdir\0"
+ * We didn't have a '/' to overwrite a '\0' onto
+ * in new_dir, so we have to compare differently.
+ */
+ if (new_dir_first_char != old_sub_dir[0] ||
+ strcmp(old_sub_dir+1, new_sub_dir))
+ break;
+ } else {
+ if (strcmp(old_sub_dir, new_sub_dir))
+ break;
+ }
+ }
+
+ /*
+ * Above we suggested that we'd keep recording renames for
+ * all ancestor directories where the trailing directories
+ * matched, i.e. for
+ * "a/b/c/d/e/foo.c" -> "a/b/some/thing/else/e/foo.c"
+ * we'd increment rename counts for each of
+ * a/b/c/d/e/ => a/b/some/thing/else/e/
+ * a/b/c/d/ => a/b/some/thing/else/
+ * However, we only need the rename counts for directories
+ * in dirs_removed whose value is RELEVANT_FOR_SELF.
+ * However, we add one special case of also recording it for
+ * first_time_in_loop because find_basename_matches() can
+ * use that as a hint to find a good pairing.
+ */
+ if (dirs_removed)
+ drd_flag = strintmap_get(dirs_removed, old_dir);
+ if (drd_flag == RELEVANT_FOR_SELF || first_time_in_loop)
+ increment_count(info, old_dir, new_dir);
+
+ first_time_in_loop = 0;
+ if (drd_flag == NOT_RELEVANT)
+ break;
+ /* If we hit toplevel directory ("") for old or new dir, quit */
+ if (!*old_dir || !*new_dir)
+ break;
+ }
+
+ /* Free resources we don't need anymore */
+ free(old_dir);
+ free(new_dir);
+}
+
+static void initialize_dir_rename_info(struct dir_rename_info *info,
+ struct strintmap *relevant_sources,
+ struct strintmap *dirs_removed,
+ struct strmap *dir_rename_count)
+{
+ struct hashmap_iter iter;
+ struct strmap_entry *entry;
+ int i;
+
+ if (!dirs_removed && !relevant_sources) {
+ info->setup = 0;
+ return;
+ }
+ info->setup = 1;
+
+ info->dir_rename_count = dir_rename_count;
+ if (!info->dir_rename_count) {
+ info->dir_rename_count = xmalloc(sizeof(*dir_rename_count));
+ strmap_init(info->dir_rename_count);
+ }
+ strintmap_init_with_options(&info->idx_map, -1, NULL, 0);
+ strmap_init_with_options(&info->dir_rename_guess, NULL, 0);
+
+ /* Setup info->relevant_source_dirs */
+ info->relevant_source_dirs = NULL;
+ if (dirs_removed || !relevant_sources) {
+ info->relevant_source_dirs = dirs_removed; /* might be NULL */
+ } else {
+ info->relevant_source_dirs = xmalloc(sizeof(struct strintmap));
+ strintmap_init(info->relevant_source_dirs, 0 /* unused */);
+ strintmap_for_each_entry(relevant_sources, &iter, entry) {
+ char *dirname = get_dirname(entry->key);
+ if (!dirs_removed ||
+ strintmap_contains(dirs_removed, dirname))
+ strintmap_set(info->relevant_source_dirs,
+ dirname, 0 /* value irrelevant */);
+ free(dirname);
+ }
+ }
+
+ /*
+ * Loop setting up both info->idx_map, and doing setup of
+ * info->dir_rename_count.
+ */
+ for (i = 0; i < rename_dst_nr; ++i) {
+ /*
+ * For non-renamed files, make idx_map contain mapping of
+ * filename -> index (index within rename_dst, that is)
+ */
+ if (!rename_dst[i].is_rename) {
+ char *filename = rename_dst[i].p->two->path;
+ strintmap_set(&info->idx_map, filename, i);
+ continue;
+ }
+
+ /*
+ * For everything else (i.e. renamed files), make
+ * dir_rename_count contain a map of a map:
+ * old_directory -> {new_directory -> count}
+ * In other words, for every pair look at the directories for
+ * the old filename and the new filename and count how many
+ * times that pairing occurs.
+ */
+ update_dir_rename_counts(info, dirs_removed,
+ rename_dst[i].p->one->path,
+ rename_dst[i].p->two->path);
+ }
+
+ /*
+ * Now we collapse
+ * dir_rename_count: old_directory -> {new_directory -> count}
+ * down to
+ * dir_rename_guess: old_directory -> best_new_directory
+ * where best_new_directory is the one with the highest count.
+ */
+ strmap_for_each_entry(info->dir_rename_count, &iter, entry) {
+ /* entry->key is source_dir */
+ struct strintmap *counts = entry->value;
+ char *best_newdir;
+
+ best_newdir = xstrdup(get_highest_rename_path(counts));
+ strmap_put(&info->dir_rename_guess, entry->key,
+ best_newdir);
+ }
+}
+
+void partial_clear_dir_rename_count(struct strmap *dir_rename_count)
+{
+ struct hashmap_iter iter;
+ struct strmap_entry *entry;
+
+ strmap_for_each_entry(dir_rename_count, &iter, entry) {
+ struct strintmap *counts = entry->value;
+ strintmap_clear(counts);
+ }
+ strmap_partial_clear(dir_rename_count, 1);
+}
+
+static void cleanup_dir_rename_info(struct dir_rename_info *info,
+ struct strintmap *dirs_removed,
+ int keep_dir_rename_count)
+{
+ struct hashmap_iter iter;
+ struct strmap_entry *entry;
+ struct string_list to_remove = STRING_LIST_INIT_NODUP;
+ int i;
+
+ if (!info->setup)
+ return;
+
+ /* idx_map */
+ strintmap_clear(&info->idx_map);
+
+ /* dir_rename_guess */
+ strmap_clear(&info->dir_rename_guess, 1);
+
+ /* relevant_source_dirs */
+ if (info->relevant_source_dirs &&
+ info->relevant_source_dirs != dirs_removed) {
+ strintmap_clear(info->relevant_source_dirs);
+ FREE_AND_NULL(info->relevant_source_dirs);
+ }
+
+ /* dir_rename_count */
+ if (!keep_dir_rename_count) {
+ partial_clear_dir_rename_count(info->dir_rename_count);
+ strmap_clear(info->dir_rename_count, 1);
+ FREE_AND_NULL(info->dir_rename_count);
+ return;
+ }
+
+ /*
+ * Although dir_rename_count was passed in
+ * diffcore_rename_extended() and we want to keep it around and
+ * return it to that caller, we first want to remove any counts in
+ * the maps associated with UNKNOWN_DIR entries and any data
+ * associated with directories that weren't renamed.
+ */
+ strmap_for_each_entry(info->dir_rename_count, &iter, entry) {
+ const char *source_dir = entry->key;
+ struct strintmap *counts = entry->value;
+
+ if (!strintmap_get(dirs_removed, source_dir)) {
+ string_list_append(&to_remove, source_dir);
+ strintmap_clear(counts);
+ continue;
+ }
+
+ if (strintmap_contains(counts, UNKNOWN_DIR))
+ strintmap_remove(counts, UNKNOWN_DIR);
+ }
+ for (i = 0; i < to_remove.nr; ++i)
+ strmap_remove(info->dir_rename_count,
+ to_remove.items[i].string, 1);
+ string_list_clear(&to_remove, 0);
+}
+
static const char *get_basename(const char *filename)
{
/*
@@ -379,8 +734,88 @@ static const char *get_basename(const char *filename)
return base ? base + 1 : filename;
}
+static int idx_possible_rename(char *filename, struct dir_rename_info *info)
+{
+ /*
+ * Our comparison of files with the same basename (see
+ * find_basename_matches() below), is only helpful when after exact
+ * rename detection we have exactly one file with a given basename
+ * among the rename sources and also only exactly one file with
+ * that basename among the rename destinations. When we have
+ * multiple files with the same basename in either set, we do not
+ * know which to compare against. However, there are some
+ * filenames that occur in large numbers (particularly
+ * build-related filenames such as 'Makefile', '.gitignore', or
+ * 'build.gradle' that potentially exist within every single
+ * subdirectory), and for performance we want to be able to quickly
+ * find renames for these files too.
+ *
+ * The reason basename comparisons are a useful heuristic was that it
+ * is common for people to move files across directories while keeping
+ * their filename the same. If we had a way of determining or even
+ * making a good educated guess about which directory these non-unique
+ * basename files had moved the file to, we could check it.
+ * Luckily...
+ *
+ * When an entire directory is in fact renamed, we have two factors
+ * helping us out:
+ * (a) the original directory disappeared giving us a hint
+ * about when we can apply an extra heuristic.
+ * (a) we often have several files within that directory and
+ * subdirectories that are renamed without changes
+ * So, rules for a heuristic:
+ * (0) If there basename matches are non-unique (the condition under
+ * which this function is called) AND
+ * (1) the directory in which the file was found has disappeared
+ * (i.e. dirs_removed is non-NULL and has a relevant entry) THEN
+ * (2) use exact renames of files within the directory to determine
+ * where the directory is likely to have been renamed to. IF
+ * there is at least one exact rename from within that
+ * directory, we can proceed.
+ * (3) If there are multiple places the directory could have been
+ * renamed to based on exact renames, ignore all but one of them.
+ * Just use the destination with the most renames going to it.
+ * (4) Check if applying that directory rename to the original file
+ * would result in a destination filename that is in the
+ * potential rename set. If so, return the index of the
+ * destination file (the index within rename_dst).
+ * (5) Compare the original file and returned destination for
+ * similarity, and if they are sufficiently similar, record the
+ * rename.
+ *
+ * This function, idx_possible_rename(), is only responsible for (4).
+ * The conditions/steps in (1)-(3) are handled via setting up
+ * dir_rename_count and dir_rename_guess in
+ * initialize_dir_rename_info(). Steps (0) and (5) are handled by
+ * the caller of this function.
+ */
+ char *old_dir, *new_dir;
+ struct strbuf new_path = STRBUF_INIT;
+ int idx;
+
+ if (!info->setup)
+ return -1;
+
+ old_dir = get_dirname(filename);
+ new_dir = strmap_get(&info->dir_rename_guess, old_dir);
+ free(old_dir);
+ if (!new_dir)
+ return -1;
+
+ strbuf_addstr(&new_path, new_dir);
+ strbuf_addch(&new_path, '/');
+ strbuf_addstr(&new_path, get_basename(filename));
+
+ idx = strintmap_get(&info->idx_map, new_path.buf);
+ strbuf_release(&new_path);
+ return idx;
+}
+
static int find_basename_matches(struct diff_options *options,
- int minimum_score)
+ int minimum_score,
+ struct dir_rename_info *info,
+ struct strintmap *relevant_sources,
+ struct strintmap *dirs_removed)
{
/*
* When I checked in early 2020, over 76% of file renames in linux
@@ -415,8 +850,6 @@ static int find_basename_matches(struct diff_options *options,
int i, renames = 0;
struct strintmap sources;
struct strintmap dests;
- struct hashmap_iter iter;
- struct strmap_entry *entry;
/*
* The prefeteching stuff wants to know if it can skip prefetching
@@ -466,17 +899,44 @@ static int find_basename_matches(struct diff_options *options,
}
/* Now look for basename matchups and do similarity estimation */
- strintmap_for_each_entry(&sources, &iter, entry) {
- const char *base = entry->key;
- intptr_t src_index = (intptr_t)entry->value;
+ for (i = 0; i < rename_src_nr; ++i) {
+ char *filename = rename_src[i].p->one->path;
+ const char *base = NULL;
+ intptr_t src_index;
intptr_t dst_index;
- if (src_index == -1)
+
+ /* Skip irrelevant sources */
+ if (relevant_sources &&
+ !strintmap_contains(relevant_sources, filename))
continue;
- if (0 <= (dst_index = strintmap_get(&dests, base))) {
+ /*
+ * If the basename is unique among remaining sources, then
+ * src_index will equal 'i' and we can attempt to match it
+ * to a unique basename in the destinations. Otherwise,
+ * use directory rename heuristics, if possible.
+ */
+ base = get_basename(filename);
+ src_index = strintmap_get(&sources, base);
+ assert(src_index == -1 || src_index == i);
+
+ if (strintmap_contains(&dests, base)) {
struct diff_filespec *one, *two;
int score;
+ /* Find a matching destination, if possible */
+ dst_index = strintmap_get(&dests, base);
+ if (src_index == -1 || dst_index == -1) {
+ src_index = i;
+ dst_index = idx_possible_rename(filename, info);
+ }
+ if (dst_index == -1)
+ continue;
+
+ /* Ignore this dest if already used in a rename */
+ if (rename_dst[dst_index].is_rename)
+ continue; /* already used previously */
+
/* Estimate the similarity */
one = rename_src[src_index].p->one;
two = rename_dst[dst_index].p->two;
@@ -488,6 +948,8 @@ static int find_basename_matches(struct diff_options *options,
continue;
record_rename_pair(dst_index, src_index, score);
renames++;
+ update_dir_rename_counts(info, dirs_removed,
+ one->path, two->path);
/*
* Found a rename so don't need text anymore; if we
@@ -571,7 +1033,12 @@ static int too_many_rename_candidates(int num_destinations, int num_sources,
return 1;
}
-static int find_renames(struct diff_score *mx, int dst_cnt, int minimum_score, int copies)
+static int find_renames(struct diff_score *mx,
+ int dst_cnt,
+ int minimum_score,
+ int copies,
+ struct dir_rename_info *info,
+ struct strintmap *dirs_removed)
{
int count = 0, i;
@@ -588,15 +1055,19 @@ static int find_renames(struct diff_score *mx, int dst_cnt, int minimum_score, i
continue;
record_rename_pair(mx[i].dst, mx[i].src, mx[i].score);
count++;
+ update_dir_rename_counts(info, dirs_removed,
+ rename_src[mx[i].src].p->one->path,
+ rename_dst[mx[i].dst].p->two->path);
}
return count;
}
-static void remove_unneeded_paths_from_src(int detecting_copies)
+static void remove_unneeded_paths_from_src(int detecting_copies,
+ struct strintmap *interesting)
{
int i, new_num_src;
- if (detecting_copies)
+ if (detecting_copies && !interesting)
return; /* nothing to remove */
if (break_idx)
return; /* culling incompatible with break detection */
@@ -623,12 +1094,18 @@ static void remove_unneeded_paths_from_src(int detecting_copies)
* from rename_src here.
*/
for (i = 0, new_num_src = 0; i < rename_src_nr; i++) {
+ struct diff_filespec *one = rename_src[i].p->one;
+
/*
* renames are stored in rename_dst, so if a rename has
* already been detected using this source, we can just
* remove the source knowing rename_dst has its info.
*/
- if (rename_src[i].p->one->rename_used)
+ if (!detecting_copies && one->rename_used)
+ continue;
+
+ /* If we don't care about the source path, skip it */
+ if (interesting && !strintmap_contains(interesting, one->path))
continue;
if (new_num_src < i)
@@ -640,7 +1117,137 @@ static void remove_unneeded_paths_from_src(int detecting_copies)
rename_src_nr = new_num_src;
}
-void diffcore_rename(struct diff_options *options)
+static void handle_early_known_dir_renames(struct dir_rename_info *info,
+ struct strintmap *relevant_sources,
+ struct strintmap *dirs_removed)
+{
+ /*
+ * Directory renames are determined via an aggregate of all renames
+ * under them and using a "majority wins" rule. The fact that
+ * "majority wins", though, means we don't need all the renames
+ * under the given directory, we only need enough to ensure we have
+ * a majority.
+ */
+
+ int i, new_num_src;
+ struct hashmap_iter iter;
+ struct strmap_entry *entry;
+
+ if (!dirs_removed || !relevant_sources)
+ return; /* nothing to cull */
+ if (break_idx)
+ return; /* culling incompatbile with break detection */
+
+ /*
+ * Supplement dir_rename_count with number of potential renames,
+ * marking all potential rename sources as mapping to UNKNOWN_DIR.
+ */
+ for (i = 0; i < rename_src_nr; i++) {
+ char *old_dir;
+ struct diff_filespec *one = rename_src[i].p->one;
+
+ /*
+ * sources that are part of a rename will have already been
+ * removed by a prior call to remove_unneeded_paths_from_src()
+ */
+ assert(!one->rename_used);
+
+ old_dir = get_dirname(one->path);
+ while (*old_dir != '\0' &&
+ NOT_RELEVANT != strintmap_get(dirs_removed, old_dir)) {
+ char *freeme = old_dir;
+
+ increment_count(info, old_dir, UNKNOWN_DIR);
+ old_dir = get_dirname(old_dir);
+
+ /* Free resources we don't need anymore */
+ free(freeme);
+ }
+ /*
+ * old_dir and new_dir free'd in increment_count, but
+ * get_dirname() gives us a new pointer we need to free for
+ * old_dir. Also, if the loop runs 0 times we need old_dir
+ * to be freed.
+ */
+ free(old_dir);
+ }
+
+ /*
+ * For any directory which we need a potential rename detected for
+ * (i.e. those marked as RELEVANT_FOR_SELF in dirs_removed), check
+ * whether we have enough renames to satisfy the "majority rules"
+ * requirement such that detecting any more renames of files under
+ * it won't change the result. For any such directory, mark that
+ * we no longer need to detect a rename for it. However, since we
+ * might need to still detect renames for an ancestor of that
+ * directory, use RELEVANT_FOR_ANCESTOR.
+ */
+ strmap_for_each_entry(info->dir_rename_count, &iter, entry) {
+ /* entry->key is source_dir */
+ struct strintmap *counts = entry->value;
+
+ if (strintmap_get(dirs_removed, entry->key) ==
+ RELEVANT_FOR_SELF &&
+ dir_rename_already_determinable(counts)) {
+ strintmap_set(dirs_removed, entry->key,
+ RELEVANT_FOR_ANCESTOR);
+ }
+ }
+
+ for (i = 0, new_num_src = 0; i < rename_src_nr; i++) {
+ struct diff_filespec *one = rename_src[i].p->one;
+ int val;
+
+ val = strintmap_get(relevant_sources, one->path);
+
+ /*
+ * sources that were not found in relevant_sources should
+ * have already been removed by a prior call to
+ * remove_unneeded_paths_from_src()
+ */
+ assert(val != -1);
+
+ if (val == RELEVANT_LOCATION) {
+ int removable = 1;
+ char *dir = get_dirname(one->path);
+ while (1) {
+ char *freeme = dir;
+ int res = strintmap_get(dirs_removed, dir);
+
+ /* Quit if not found or irrelevant */
+ if (res == NOT_RELEVANT)
+ break;
+ /* If RELEVANT_FOR_SELF, can't remove */
+ if (res == RELEVANT_FOR_SELF) {
+ removable = 0;
+ break;
+ }
+ /* Else continue searching upwards */
+ assert(res == RELEVANT_FOR_ANCESTOR);
+ dir = get_dirname(dir);
+ free(freeme);
+ }
+ free(dir);
+ if (removable) {
+ strintmap_set(relevant_sources, one->path,
+ RELEVANT_NO_MORE);
+ continue;
+ }
+ }
+
+ if (new_num_src < i)
+ memcpy(&rename_src[new_num_src], &rename_src[i],
+ sizeof(struct diff_rename_src));
+ new_num_src++;
+ }
+
+ rename_src_nr = new_num_src;
+}
+
+void diffcore_rename_extended(struct diff_options *options,
+ struct strintmap *relevant_sources,
+ struct strintmap *dirs_removed,
+ struct strmap *dir_rename_count)
{
int detect_rename = options->detect_rename;
int minimum_score = options->rename_score;
@@ -651,9 +1258,16 @@ void diffcore_rename(struct diff_options *options)
int num_destinations, dst_cnt;
int num_sources, want_copies;
struct progress *progress = NULL;
+ struct dir_rename_info info;
trace2_region_enter("diff", "setup", options->repo);
+ info.setup = 0;
+ assert(!dir_rename_count || strmap_empty(dir_rename_count));
want_copies = (detect_rename == DIFF_DETECT_COPY);
+ if (dirs_removed && (break_idx || want_copies))
+ BUG("dirs_removed incompatible with break/copy detection");
+ if (break_idx && relevant_sources)
+ BUG("break detection incompatible with source specification");
if (!minimum_score)
minimum_score = DEFAULT_RENAME_SCORE;
@@ -721,9 +1335,10 @@ void diffcore_rename(struct diff_options *options)
/*
* Cull sources:
* - remove ones corresponding to exact renames
+ * - remove ones not found in relevant_sources
*/
trace2_region_enter("diff", "cull after exact", options->repo);
- remove_unneeded_paths_from_src(want_copies);
+ remove_unneeded_paths_from_src(want_copies, relevant_sources);
trace2_region_leave("diff", "cull after exact", options->repo);
} else {
/* Determine minimum score to match basenames */
@@ -742,21 +1357,38 @@ void diffcore_rename(struct diff_options *options)
* - remove ones involved in renames (found via exact match)
*/
trace2_region_enter("diff", "cull after exact", options->repo);
- remove_unneeded_paths_from_src(want_copies);
+ remove_unneeded_paths_from_src(want_copies, NULL);
trace2_region_leave("diff", "cull after exact", options->repo);
+ /* Preparation for basename-driven matching. */
+ trace2_region_enter("diff", "dir rename setup", options->repo);
+ initialize_dir_rename_info(&info, relevant_sources,
+ dirs_removed, dir_rename_count);
+ trace2_region_leave("diff", "dir rename setup", options->repo);
+
/* Utilize file basenames to quickly find renames. */
trace2_region_enter("diff", "basename matches", options->repo);
rename_count += find_basename_matches(options,
- min_basename_score);
+ min_basename_score,
+ &info,
+ relevant_sources,
+ dirs_removed);
trace2_region_leave("diff", "basename matches", options->repo);
/*
* Cull sources, again:
* - remove ones involved in renames (found via basenames)
+ * - remove ones not found in relevant_sources
+ * and
+ * - remove ones in relevant_sources which are needed only
+ * for directory renames IF no ancestory directory
+ * actually needs to know any more individual path
+ * renames under them
*/
trace2_region_enter("diff", "cull basename", options->repo);
- remove_unneeded_paths_from_src(want_copies);
+ remove_unneeded_paths_from_src(want_copies, relevant_sources);
+ handle_early_known_dir_renames(&info, relevant_sources,
+ dirs_removed);
trace2_region_leave("diff", "cull basename", options->repo);
}
@@ -787,8 +1419,7 @@ void diffcore_rename(struct diff_options *options)
(uint64_t)num_destinations * (uint64_t)num_sources);
}
- mx = xcalloc(st_mult(NUM_CANDIDATE_PER_DST, num_destinations),
- sizeof(*mx));
+ CALLOC_ARRAY(mx, st_mult(NUM_CANDIDATE_PER_DST, num_destinations));
for (dst_cnt = i = 0; i < rename_dst_nr; i++) {
struct diff_filespec *two = rename_dst[i].p->two;
struct diff_score *m;
@@ -834,9 +1465,11 @@ void diffcore_rename(struct diff_options *options)
/* cost matrix sorted by most to least similar pair */
STABLE_QSORT(mx, dst_cnt * NUM_CANDIDATE_PER_DST, score_compare);
- rename_count += find_renames(mx, dst_cnt, minimum_score, 0);
+ rename_count += find_renames(mx, dst_cnt, minimum_score, 0,
+ &info, dirs_removed);
if (want_copies)
- rename_count += find_renames(mx, dst_cnt, minimum_score, 1);
+ rename_count += find_renames(mx, dst_cnt, minimum_score, 1,
+ &info, dirs_removed);
free(mx);
trace2_region_leave("diff", "inexact renames", options->repo);
@@ -912,6 +1545,7 @@ void diffcore_rename(struct diff_options *options)
if (rename_dst[i].filespec_to_free)
free_filespec(rename_dst[i].filespec_to_free);
+ cleanup_dir_rename_info(&info, dirs_removed, dir_rename_count != NULL);
FREE_AND_NULL(rename_dst);
rename_dst_nr = rename_dst_alloc = 0;
FREE_AND_NULL(rename_src);
@@ -923,3 +1557,8 @@ void diffcore_rename(struct diff_options *options)
trace2_region_leave("diff", "write back to queue", options->repo);
return;
}
+
+void diffcore_rename(struct diff_options *options)
+{
+ diffcore_rename_extended(options, NULL, NULL, NULL);
+}
diff --git a/diffcore.h b/diffcore.h
index c1592bcd01..f5c6de4841 100644
--- a/diffcore.h
+++ b/diffcore.h
@@ -8,6 +8,8 @@
struct diff_options;
struct repository;
+struct strintmap;
+struct strmap;
struct userdiff_driver;
/* This header file is internal between diff.c and its diff transformers
@@ -159,8 +161,27 @@ struct diff_filepair *diff_queue(struct diff_queue_struct *,
struct diff_filespec *);
void diff_q(struct diff_queue_struct *, struct diff_filepair *);
+/* dir_rename_relevance: the reason we want rename information for a dir */
+enum dir_rename_relevance {
+ NOT_RELEVANT = 0,
+ RELEVANT_FOR_ANCESTOR = 1,
+ RELEVANT_FOR_SELF = 2
+};
+/* file_rename_relevance: the reason(s) we want rename information for a file */
+enum file_rename_relevance {
+ RELEVANT_NO_MORE = 0, /* i.e. NOT relevant */
+ RELEVANT_CONTENT = 1,
+ RELEVANT_LOCATION = 2
+};
+
+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 strintmap *relevant_sources,
+ struct strintmap *dirs_removed,
+ struct strmap *dir_rename_count);
void diffcore_merge_broken(void);
void diffcore_pickaxe(struct diff_options *);
void diffcore_order(const char *orderfile);
diff --git a/dir.c b/dir.c
index fd8aa7c40f..ebe5ec046e 100644
--- a/dir.c
+++ b/dir.c
@@ -59,6 +59,17 @@ void dir_init(struct dir_struct *dir)
memset(dir, 0, sizeof(*dir));
}
+struct dirent *readdir_skip_dot_and_dotdot(DIR *dirp)
+{
+ struct dirent *e;
+
+ while ((e = readdir(dirp)) != NULL) {
+ if (!is_dot_or_dotdot(e->d_name))
+ break;
+ }
+ return e;
+}
+
int count_slashes(const char *s)
{
int cnt = 0;
@@ -306,7 +317,7 @@ static int do_read_blob(const struct object_id *oid, struct oid_stat *oid_stat,
* [1] Only if DO_MATCH_DIRECTORY is passed; otherwise, this is NOT a match.
* [2] Only if DO_MATCH_LEADING_PATHSPEC is passed; otherwise, not a match.
*/
-static int match_pathspec_item(const struct index_state *istate,
+static int match_pathspec_item(struct index_state *istate,
const struct pathspec_item *item, int prefix,
const char *name, int namelen, unsigned flags)
{
@@ -429,7 +440,7 @@ static int match_pathspec_item(const struct index_state *istate,
* pathspec did not match any names, which could indicate that the
* user mistyped the nth pathspec.
*/
-static int do_match_pathspec(const struct index_state *istate,
+static int do_match_pathspec(struct index_state *istate,
const struct pathspec *ps,
const char *name, int namelen,
int prefix, char *seen,
@@ -500,7 +511,7 @@ static int do_match_pathspec(const struct index_state *istate,
return retval;
}
-static int match_pathspec_with_flags(const struct index_state *istate,
+static int match_pathspec_with_flags(struct index_state *istate,
const struct pathspec *ps,
const char *name, int namelen,
int prefix, char *seen, unsigned flags)
@@ -516,7 +527,7 @@ static int match_pathspec_with_flags(const struct index_state *istate,
return negative ? 0 : positive;
}
-int match_pathspec(const struct index_state *istate,
+int match_pathspec(struct index_state *istate,
const struct pathspec *ps,
const char *name, int namelen,
int prefix, char *seen, int is_dir)
@@ -529,7 +540,7 @@ int match_pathspec(const struct index_state *istate,
/**
* Check if a submodule is a superset of the pathspec
*/
-int submodule_path_match(const struct index_state *istate,
+int submodule_path_match(struct index_state *istate,
const struct pathspec *ps,
const char *submodule_name,
char *seen)
@@ -892,7 +903,7 @@ void add_pattern(const char *string, const char *base,
add_pattern_to_hashsets(pl, pattern);
}
-static int read_skip_worktree_file_from_index(const struct index_state *istate,
+static int read_skip_worktree_file_from_index(struct index_state *istate,
const char *path,
size_t *size_out, char **data_out,
struct oid_stat *oid_stat)
@@ -1035,6 +1046,9 @@ static int add_patterns_from_buffer(char *buf, size_t size,
const char *base, int baselen,
struct pattern_list *pl);
+/* Flags for add_patterns() */
+#define PATTERN_NOFOLLOW (1<<0)
+
/*
* Given a file with name "fname", read it (either from disk, or from
* an index if 'istate' is non-null), parse it and store the
@@ -1046,7 +1060,7 @@ static int add_patterns_from_buffer(char *buf, size_t size,
*/
static int add_patterns(const char *fname, const char *base, int baselen,
struct pattern_list *pl, struct index_state *istate,
- struct oid_stat *oid_stat)
+ unsigned flags, struct oid_stat *oid_stat)
{
struct stat st;
int r;
@@ -1054,7 +1068,11 @@ static int add_patterns(const char *fname, const char *base, int baselen,
size_t size = 0;
char *buf;
- fd = open(fname, O_RDONLY);
+ if (flags & PATTERN_NOFOLLOW)
+ fd = open_nofollow(fname, O_RDONLY);
+ else
+ fd = open(fname, O_RDONLY);
+
if (fd < 0 || fstat(fd, &st) < 0) {
if (fd < 0)
warn_on_fopen_errors(fname);
@@ -1143,9 +1161,10 @@ static int add_patterns_from_buffer(char *buf, size_t size,
int add_patterns_from_file_to_list(const char *fname, const char *base,
int baselen, struct pattern_list *pl,
- struct index_state *istate)
+ struct index_state *istate,
+ unsigned flags)
{
- return add_patterns(fname, base, baselen, pl, istate, NULL);
+ return add_patterns(fname, base, baselen, pl, istate, flags, NULL);
}
int add_patterns_from_blob_to_list(
@@ -1194,7 +1213,7 @@ static void add_patterns_from_file_1(struct dir_struct *dir, const char *fname,
if (!dir->untracked)
dir->unmanaged_exclude_files++;
pl = add_pattern_list(dir, EXC_FILE, fname);
- if (add_patterns(fname, "", 0, pl, NULL, oid_stat) < 0)
+ if (add_patterns(fname, "", 0, pl, NULL, 0, oid_stat) < 0)
die(_("cannot use %s as an exclude file"), fname);
}
@@ -1488,7 +1507,7 @@ static void prep_exclude(struct dir_struct *dir,
const char *cp;
struct oid_stat oid_stat;
- stk = xcalloc(1, sizeof(*stk));
+ CALLOC_ARRAY(stk, 1);
if (current < 0) {
cp = base;
current = 0;
@@ -1558,6 +1577,7 @@ static void prep_exclude(struct dir_struct *dir,
strbuf_addstr(&sb, dir->exclude_per_dir);
pl->src = strbuf_detach(&sb, NULL);
add_patterns(pl->src, pl->src, stk->baselen, pl, istate,
+ PATTERN_NOFOLLOW,
untracked ? &oid_stat : NULL);
}
/*
@@ -1740,13 +1760,13 @@ static enum exist_status directory_exists_in_index(struct index_state *istate,
* Case 3: if we didn't have it in the index previously, we
* have a few sub-cases:
*
- * (a) if "show_other_directories" is true, we show it as
- * just a directory, unless "hide_empty_directories" is
+ * (a) if DIR_SHOW_OTHER_DIRECTORIES flag is set, we show it as
+ * just a directory, unless DIR_HIDE_EMPTY_DIRECTORIES is
* also true, in which case we need to check if it contains any
* untracked and / or ignored files.
- * (b) if it looks like a git directory, and we don't have
- * 'no_gitlinks' set we treat it as a gitlink, and show it
- * as a directory.
+ * (b) if it looks like a git directory and we don't have the
+ * DIR_NO_GITLINKS flag, then we treat it as a gitlink, and
+ * show it as a directory.
* (c) otherwise, we recurse into it.
*/
static enum path_treatment treat_directory(struct dir_struct *dir,
@@ -1834,7 +1854,7 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
return path_recurse;
}
- /* This is the "show_other_directories" case */
+ assert(dir->flags & DIR_SHOW_OTHER_DIRECTORIES);
/*
* If we have a pathspec which could match something _below_ this
@@ -1845,27 +1865,42 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
if (matches_how == MATCHED_RECURSIVELY_LEADING_PATHSPEC)
return path_recurse;
+ /* Special cases for where this directory is excluded/ignored */
+ if (excluded) {
+ /*
+ * If DIR_SHOW_OTHER_DIRECTORIES is set and we're not
+ * hiding empty directories, there is no need to
+ * recurse into an ignored directory.
+ */
+ if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
+ return path_excluded;
+
+ /*
+ * Even if we are hiding empty directories, we can still avoid
+ * recursing into ignored directories for DIR_SHOW_IGNORED_TOO
+ * if DIR_SHOW_IGNORED_TOO_MODE_MATCHING is also set.
+ */
+ if ((dir->flags & DIR_SHOW_IGNORED_TOO) &&
+ (dir->flags & DIR_SHOW_IGNORED_TOO_MODE_MATCHING))
+ return path_excluded;
+ }
+
/*
- * Other than the path_recurse case immediately above, we only need
- * to recurse into untracked/ignored directories if either of the
- * following bits is set:
- * - DIR_SHOW_IGNORED_TOO (because then we need to determine if
- * there are ignored entries below)
+ * Other than the path_recurse case above, we only need to
+ * recurse into untracked directories if any of the following
+ * bits is set:
+ * - DIR_SHOW_IGNORED (because then we need to determine if
+ * there are ignored entries below)
+ * - DIR_SHOW_IGNORED_TOO (same as above)
* - DIR_HIDE_EMPTY_DIRECTORIES (because we have to determine if
* the directory is empty)
*/
- if (!(dir->flags & (DIR_SHOW_IGNORED_TOO | DIR_HIDE_EMPTY_DIRECTORIES)))
- return excluded ? path_excluded : path_untracked;
-
- /*
- * ...and even if DIR_SHOW_IGNORED_TOO is set, we can still avoid
- * recursing into ignored directories if the path is excluded and
- * DIR_SHOW_IGNORED_TOO_MODE_MATCHING is also set.
- */
- if (excluded &&
- (dir->flags & DIR_SHOW_IGNORED_TOO) &&
- (dir->flags & DIR_SHOW_IGNORED_TOO_MODE_MATCHING))
- return path_excluded;
+ if (!excluded &&
+ !(dir->flags & (DIR_SHOW_IGNORED |
+ DIR_SHOW_IGNORED_TOO |
+ DIR_HIDE_EMPTY_DIRECTORIES))) {
+ return path_untracked;
+ }
/*
* Even if we don't want to know all the paths under an untracked or
@@ -2317,7 +2352,7 @@ static int read_cached_dir(struct cached_dir *cdir)
struct dirent *de;
if (cdir->fdir) {
- de = readdir(cdir->fdir);
+ de = readdir_skip_dot_and_dotdot(cdir->fdir);
if (!de) {
cdir->d_name = NULL;
cdir->d_type = DT_UNKNOWN;
@@ -2431,6 +2466,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
if (open_cached_dir(&cdir, dir, untracked, istate, &path, check_only))
goto out;
+ dir->visited_directories++;
if (untracked)
untracked->check_only = !!check_only;
@@ -2439,6 +2475,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
/* check how the file or directory should be treated */
state = treat_path(dir, untracked, &cdir, istate, &path,
baselen, pathspec);
+ dir->visited_paths++;
if (state > dir_state)
dir_state = state;
@@ -2751,15 +2788,53 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d
return root;
}
+static void emit_traversal_statistics(struct dir_struct *dir,
+ struct repository *repo,
+ const char *path,
+ int path_len)
+{
+ if (!trace2_is_enabled())
+ return;
+
+ if (!path_len) {
+ trace2_data_string("read_directory", repo, "path", "");
+ } else {
+ struct strbuf tmp = STRBUF_INIT;
+ strbuf_add(&tmp, path, path_len);
+ trace2_data_string("read_directory", repo, "path", tmp.buf);
+ strbuf_release(&tmp);
+ }
+
+ trace2_data_intmax("read_directory", repo,
+ "directories-visited", dir->visited_directories);
+ trace2_data_intmax("read_directory", repo,
+ "paths-visited", dir->visited_paths);
+
+ if (!dir->untracked)
+ return;
+ trace2_data_intmax("read_directory", repo,
+ "node-creation", dir->untracked->dir_created);
+ trace2_data_intmax("read_directory", repo,
+ "gitignore-invalidation",
+ dir->untracked->gitignore_invalidated);
+ trace2_data_intmax("read_directory", repo,
+ "directory-invalidation",
+ dir->untracked->dir_invalidated);
+ trace2_data_intmax("read_directory", repo,
+ "opendir", dir->untracked->dir_opened);
+}
+
int read_directory(struct dir_struct *dir, struct index_state *istate,
const char *path, int len, const struct pathspec *pathspec)
{
struct untracked_cache_dir *untracked;
- trace_performance_enter();
+ trace2_region_enter("dir", "read_directory", istate->repo);
+ dir->visited_paths = 0;
+ dir->visited_directories = 0;
if (has_symlink_leading_path(path, len)) {
- trace_performance_leave("read directory %.*s", len, path);
+ trace2_region_leave("dir", "read_directory", istate->repo);
return dir->nr;
}
@@ -2775,23 +2850,15 @@ int read_directory(struct dir_struct *dir, struct index_state *istate,
QSORT(dir->entries, dir->nr, cmp_dir_entry);
QSORT(dir->ignored, dir->ignored_nr, cmp_dir_entry);
- trace_performance_leave("read directory %.*s", len, path);
+ emit_traversal_statistics(dir, istate->repo, path, len);
+
+ trace2_region_leave("dir", "read_directory", istate->repo);
if (dir->untracked) {
static int force_untracked_cache = -1;
- static struct trace_key trace_untracked_stats = TRACE_KEY_INIT(UNTRACKED_STATS);
if (force_untracked_cache < 0)
force_untracked_cache =
git_env_bool("GIT_FORCE_UNTRACKED_CACHE", 0);
- trace_printf_key(&trace_untracked_stats,
- "node creation: %u\n"
- "gitignore invalidation: %u\n"
- "directory invalidation: %u\n"
- "opendir: %u\n",
- dir->untracked->dir_created,
- dir->untracked->gitignore_invalidated,
- dir->untracked->dir_invalidated,
- dir->untracked->dir_opened);
if (force_untracked_cache &&
dir->untracked == istate->untracked &&
(dir->untracked->dir_opened ||
@@ -2802,6 +2869,7 @@ int read_directory(struct dir_struct *dir, struct index_state *istate,
FREE_AND_NULL(dir->untracked);
}
}
+
return dir->nr;
}
@@ -2883,11 +2951,9 @@ int is_empty_dir(const char *path)
if (!dir)
return 0;
- while ((e = readdir(dir)) != NULL)
- if (!is_dot_or_dotdot(e->d_name)) {
- ret = 0;
- break;
- }
+ e = readdir_skip_dot_and_dotdot(dir);
+ if (e)
+ ret = 0;
closedir(dir);
return ret;
@@ -2927,10 +2993,8 @@ static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
strbuf_complete(path, '/');
len = path->len;
- while ((e = readdir(dir)) != NULL) {
+ while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL) {
struct stat st;
- if (is_dot_or_dotdot(e->d_name))
- continue;
strbuf_setlen(path, len);
strbuf_addstr(path, e->d_name);
@@ -3006,7 +3070,7 @@ int get_sparse_checkout_patterns(struct pattern_list *pl)
char *sparse_filename = get_sparse_checkout_filename();
pl->use_cone_patterns = core_sparse_checkout_cone;
- res = add_patterns_from_file_to_list(sparse_filename, "", 0, pl, NULL);
+ res = add_patterns_from_file_to_list(sparse_filename, "", 0, pl, NULL, 0);
free(sparse_filename);
return res;
@@ -3162,7 +3226,7 @@ void write_untracked_extension(struct strbuf *out, struct untracked_cache *untra
int varint_len;
const unsigned hashsz = the_hash_algo->rawsz;
- ouc = xcalloc(1, sizeof(*ouc));
+ CALLOC_ARRAY(ouc, 1);
stat_data_to_disk(&ouc->info_exclude_stat, &untracked->ss_info_exclude.stat);
stat_data_to_disk(&ouc->excludes_file_stat, &untracked->ss_excludes_file.stat);
ouc->dir_flags = htonl(untracked->dir_flags);
@@ -3335,7 +3399,7 @@ static void read_oid(size_t pos, void *cb)
rd->data = rd->end + 1;
return;
}
- hashcpy(ud->exclude_oid.hash, rd->data);
+ oidread(&ud->exclude_oid, rd->data);
rd->data += the_hash_algo->rawsz;
}
@@ -3343,7 +3407,7 @@ static void load_oid_stat(struct oid_stat *oid_stat, const unsigned char *data,
const unsigned char *sha1)
{
stat_data_from_disk(&oid_stat->stat, data);
- hashcpy(oid_stat->oid.hash, sha1);
+ oidread(&oid_stat->oid, sha1);
oid_stat->valid = 1;
}
@@ -3373,7 +3437,7 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long
if (next + exclude_per_dir_offset + 1 > end)
return NULL;
- uc = xcalloc(1, sizeof(*uc));
+ CALLOC_ARRAY(uc, 1);
strbuf_init(&uc->ident, ident_len);
strbuf_add(&uc->ident, ident, ident_len);
load_oid_stat(&uc->ss_info_exclude,
@@ -3533,6 +3597,8 @@ static void connect_wt_gitdir_in_nested(const char *sub_worktree,
if (repo_read_index(&subrepo) < 0)
die(_("index file corrupt in repo %s"), subrepo.gitdir);
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(subrepo.index);
for (i = 0; i < subrepo.index->cache_nr; i++) {
const struct cache_entry *ce = subrepo.index->cache[i];
@@ -3547,7 +3613,7 @@ static void connect_wt_gitdir_in_nested(const char *sub_worktree,
*/
i++;
- sub = submodule_from_path(&subrepo, &null_oid, ce->name);
+ sub = submodule_from_path(&subrepo, null_oid(), ce->name);
if (!sub || !is_submodule_active(&subrepo, ce->name))
/* .gitmodules broken or inactive sub */
continue;
diff --git a/dir.h b/dir.h
index facfae4740..e3db9b9ec6 100644
--- a/dir.h
+++ b/dir.h
@@ -336,8 +336,14 @@ struct dir_struct {
struct oid_stat ss_info_exclude;
struct oid_stat ss_excludes_file;
unsigned unmanaged_exclude_files;
+
+ /* Stats about the traversal */
+ unsigned visited_paths;
+ unsigned visited_directories;
};
+struct dirent *readdir_skip_dot_and_dotdot(DIR *dirp);
+
/*Count the number of slashes for string s*/
int count_slashes(const char *s);
@@ -354,7 +360,7 @@ int count_slashes(const char *s);
int simple_length(const char *match);
int no_wildcard(const char *string);
char *common_prefix(const struct pathspec *pathspec);
-int match_pathspec(const struct index_state *istate,
+int match_pathspec(struct index_state *istate,
const struct pathspec *pathspec,
const char *name, int namelen,
int prefix, char *seen, int is_dir);
@@ -420,7 +426,8 @@ int hashmap_contains_parent(struct hashmap *map,
struct pattern_list *add_pattern_list(struct dir_struct *dir,
int group_type, const char *src);
int add_patterns_from_file_to_list(const char *fname, const char *base, int baselen,
- struct pattern_list *pl, struct index_state *istate);
+ struct pattern_list *pl, struct index_state *istate,
+ unsigned flags);
void add_patterns_from_file(struct dir_struct *, const char *fname);
int add_patterns_from_blob_to_list(struct object_id *oid,
const char *base, int baselen,
@@ -492,12 +499,12 @@ int git_fnmatch(const struct pathspec_item *item,
const char *pattern, const char *string,
int prefix);
-int submodule_path_match(const struct index_state *istate,
+int submodule_path_match(struct index_state *istate,
const struct pathspec *ps,
const char *submodule_name,
char *seen);
-static inline int ce_path_match(const struct index_state *istate,
+static inline int ce_path_match(struct index_state *istate,
const struct cache_entry *ce,
const struct pathspec *pathspec,
char *seen)
@@ -506,7 +513,7 @@ static inline int ce_path_match(const struct index_state *istate,
S_ISDIR(ce->ce_mode) || S_ISGITLINK(ce->ce_mode));
}
-static inline int dir_path_match(const struct index_state *istate,
+static inline int dir_path_match(struct index_state *istate,
const struct dir_entry *ent,
const struct pathspec *pathspec,
int prefix, char *seen)
diff --git a/entry.c b/entry.c
index 7b9f43716f..711ee0693c 100644
--- a/entry.c
+++ b/entry.c
@@ -6,6 +6,8 @@
#include "submodule.h"
#include "progress.h"
#include "fsmonitor.h"
+#include "entry.h"
+#include "parallel-checkout.h"
static void create_directories(const char *path, int path_len,
const struct checkout *state)
@@ -56,12 +58,9 @@ static void remove_subtree(struct strbuf *path)
if (!dir)
die_errno("cannot opendir '%s'", path->buf);
- while ((de = readdir(dir)) != NULL) {
+ while ((de = readdir_skip_dot_and_dotdot(dir)) != NULL) {
struct stat st;
- if (is_dot_or_dotdot(de->d_name))
- continue;
-
strbuf_addch(path, '/');
strbuf_addstr(path, de->d_name);
if (lstat(path->buf, &st))
@@ -83,7 +82,7 @@ static int create_file(const char *path, unsigned int mode)
return open(path, O_WRONLY | O_CREAT | O_EXCL, mode);
}
-static void *read_blob_entry(const struct cache_entry *ce, unsigned long *size)
+void *read_blob_entry(const struct cache_entry *ce, unsigned long *size)
{
enum object_type type;
void *blob_data = read_object_file(&ce->oid, &type, size);
@@ -108,7 +107,7 @@ static int open_output_fd(char *path, const struct cache_entry *ce, int to_tempf
}
}
-static int fstat_output(int fd, const struct checkout *state, struct stat *st)
+int fstat_checkout_output(int fd, const struct checkout *state, struct stat *st)
{
/* use fstat() only when path == ce->name */
if (fstat_is_reliable() &&
@@ -131,7 +130,7 @@ static int streaming_write_entry(const struct cache_entry *ce, char *path,
return -1;
result |= stream_blob_to_fd(fd, &ce->oid, filter, 1);
- *fstat_done = fstat_output(fd, state, statbuf);
+ *fstat_done = fstat_checkout_output(fd, state, statbuf);
result |= close(fd);
if (result)
@@ -250,8 +249,21 @@ int finish_delayed_checkout(struct checkout *state, int *nr_checkouts)
return errs;
}
-static int write_entry(struct cache_entry *ce,
- char *path, const struct checkout *state, int to_tempfile)
+void update_ce_after_write(const struct checkout *state, struct cache_entry *ce,
+ struct stat *st)
+{
+ if (state->refresh_cache) {
+ assert(state->istate);
+ fill_stat_cache_info(state->istate, ce, st);
+ ce->ce_flags |= CE_UPDATE_IN_BASE;
+ mark_fsmonitor_invalid(state->istate, ce);
+ state->istate->cache_changed |= CE_ENTRY_CHANGED;
+ }
+}
+
+/* Note: ca is used (and required) iff the entry refers to a regular file. */
+static int write_entry(struct cache_entry *ce, char *path, struct conv_attrs *ca,
+ const struct checkout *state, int to_tempfile)
{
unsigned int ce_mode_s_ifmt = ce->ce_mode & S_IFMT;
struct delayed_checkout *dco = state->delayed_checkout;
@@ -268,8 +280,7 @@ static int write_entry(struct cache_entry *ce,
clone_checkout_metadata(&meta, &state->meta, &ce->oid);
if (ce_mode_s_ifmt == S_IFREG) {
- struct stream_filter *filter = get_stream_filter(state->istate, ce->name,
- &ce->oid);
+ struct stream_filter *filter = get_stream_filter_ca(ca, &ce->oid);
if (filter &&
!streaming_write_entry(ce, path, filter,
state, to_tempfile,
@@ -316,14 +327,17 @@ static int write_entry(struct cache_entry *ce,
* Convert from git internal format to working tree format
*/
if (dco && dco->state != CE_NO_DELAY) {
- ret = async_convert_to_working_tree(state->istate, ce->name, new_blob,
- size, &buf, &meta, dco);
+ ret = async_convert_to_working_tree_ca(ca, ce->name,
+ new_blob, size,
+ &buf, &meta, dco);
if (ret && string_list_has_string(&dco->paths, ce->name)) {
free(new_blob);
goto delayed;
}
- } else
- ret = convert_to_working_tree(state->istate, ce->name, new_blob, size, &buf, &meta);
+ } else {
+ ret = convert_to_working_tree_ca(ca, ce->name, new_blob,
+ size, &buf, &meta);
+ }
if (ret) {
free(new_blob);
@@ -345,7 +359,7 @@ static int write_entry(struct cache_entry *ce,
wrote = write_in_full(fd, new_blob, size);
if (!to_tempfile)
- fstat_done = fstat_output(fd, state, &st);
+ fstat_done = fstat_checkout_output(fd, state, &st);
close(fd);
free(new_blob);
if (wrote < 0)
@@ -370,15 +384,10 @@ static int write_entry(struct cache_entry *ce,
finish:
if (state->refresh_cache) {
- assert(state->istate);
- if (!fstat_done)
- if (lstat(ce->name, &st) < 0)
- return error_errno("unable to stat just-written file %s",
- ce->name);
- fill_stat_cache_info(state->istate, ce, &st);
- ce->ce_flags |= CE_UPDATE_IN_BASE;
- mark_fsmonitor_invalid(state->istate, ce);
- state->istate->cache_changed |= CE_ENTRY_CHANGED;
+ if (!fstat_done && lstat(ce->name, &st) < 0)
+ return error_errno("unable to stat just-written file %s",
+ ce->name);
+ update_ce_after_write(state, ce , &st);
}
delayed:
return 0;
@@ -412,11 +421,22 @@ static void mark_colliding_entries(const struct checkout *state,
ce->ce_flags |= CE_MATCHED;
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(state->istate);
for (i = 0; i < state->istate->cache_nr; i++) {
struct cache_entry *dup = state->istate->cache[i];
- if (dup == ce)
- break;
+ if (dup == ce) {
+ /*
+ * Parallel checkout doesn't create the files in index
+ * order. So the other side of the collision may appear
+ * after the given cache_entry in the array.
+ */
+ if (parallel_checkout_status() == PC_RUNNING)
+ continue;
+ else
+ break;
+ }
if (dup->ce_flags & (CE_MATCHED | CE_VALID | CE_SKIP_WORKTREE))
continue;
@@ -429,19 +449,13 @@ static void mark_colliding_entries(const struct checkout *state,
}
}
-/*
- * Write the contents from ce out to the working tree.
- *
- * When topath[] is not NULL, instead of writing to the working tree
- * file named by ce, a temporary file is created by this function and
- * its name is returned in topath[], which must be able to hold at
- * least TEMPORARY_FILENAME_LENGTH bytes long.
- */
-int checkout_entry(struct cache_entry *ce, const struct checkout *state,
- char *topath, int *nr_checkouts)
+int checkout_entry_ca(struct cache_entry *ce, struct conv_attrs *ca,
+ const struct checkout *state, char *topath,
+ int *nr_checkouts)
{
static struct strbuf path = STRBUF_INIT;
struct stat st;
+ struct conv_attrs ca_buf;
if (ce->ce_flags & CE_WT_REMOVE) {
if (topath)
@@ -454,8 +468,13 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,
return 0;
}
- if (topath)
- return write_entry(ce, topath, state, 1);
+ if (topath) {
+ if (S_ISREG(ce->ce_mode) && !ca) {
+ convert_attrs(state->istate, &ca_buf, ce->name);
+ ca = &ca_buf;
+ }
+ return write_entry(ce, topath, ca, state, 1);
+ }
strbuf_reset(&path);
strbuf_add(&path, state->base_dir, state->base_dir_len);
@@ -517,9 +536,19 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,
return 0;
create_directories(path.buf, path.len, state);
+
if (nr_checkouts)
(*nr_checkouts)++;
- return write_entry(ce, path.buf, state, 0);
+
+ if (S_ISREG(ce->ce_mode) && !ca) {
+ convert_attrs(state->istate, &ca_buf, ce->name);
+ ca = &ca_buf;
+ }
+
+ if (!enqueue_checkout(ce, ca))
+ return 0;
+
+ return write_entry(ce, path.buf, ca, state, 0);
}
void unlink_entry(const struct cache_entry *ce)
@@ -530,7 +559,7 @@ void unlink_entry(const struct cache_entry *ce)
submodule_move_head(ce->name, "HEAD", NULL,
SUBMODULE_MOVE_HEAD_FORCE);
}
- if (!check_leading_path(ce->name, ce_namelen(ce)))
+ if (check_leading_path(ce->name, ce_namelen(ce), 1) >= 0)
return;
if (remove_or_warn(ce->ce_mode, ce->name))
return;
diff --git a/entry.h b/entry.h
new file mode 100644
index 0000000000..b8c0e170dc
--- /dev/null
+++ b/entry.h
@@ -0,0 +1,59 @@
+#ifndef ENTRY_H
+#define ENTRY_H
+
+#include "cache.h"
+#include "convert.h"
+
+struct checkout {
+ struct index_state *istate;
+ const char *base_dir;
+ int base_dir_len;
+ struct delayed_checkout *delayed_checkout;
+ struct checkout_metadata meta;
+ unsigned force:1,
+ quiet:1,
+ not_new:1,
+ clone:1,
+ refresh_cache:1;
+};
+#define CHECKOUT_INIT { NULL, "" }
+
+#define TEMPORARY_FILENAME_LENGTH 25
+/*
+ * Write the contents from ce out to the working tree.
+ *
+ * When topath[] is not NULL, instead of writing to the working tree
+ * file named by ce, a temporary file is created by this function and
+ * its name is returned in topath[], which must be able to hold at
+ * least TEMPORARY_FILENAME_LENGTH bytes long.
+ *
+ * With checkout_entry_ca(), callers can optionally pass a preloaded
+ * conv_attrs struct (to avoid reloading it), when ce refers to a
+ * regular file. If ca is NULL, the attributes will be loaded
+ * internally when (and if) needed.
+ */
+int checkout_entry_ca(struct cache_entry *ce, struct conv_attrs *ca,
+ const struct checkout *state, char *topath,
+ int *nr_checkouts);
+static inline int checkout_entry(struct cache_entry *ce,
+ const struct checkout *state, char *topath,
+ int *nr_checkouts)
+{
+ return checkout_entry_ca(ce, NULL, state, topath, nr_checkouts);
+}
+
+void enable_delayed_checkout(struct checkout *state);
+int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);
+
+/*
+ * Unlink the last component and schedule the leading directories for
+ * removal, such that empty directories get removed.
+ */
+void unlink_entry(const struct cache_entry *ce);
+
+void *read_blob_entry(const struct cache_entry *ce, unsigned long *size);
+int fstat_checkout_output(int fd, const struct checkout *state, struct stat *st);
+void update_ce_after_write(const struct checkout *state, struct cache_entry *ce,
+ struct stat *st);
+
+#endif /* ENTRY_H */
diff --git a/ewah/bitmap.c b/ewah/bitmap.c
index 0d31cdc866..38a47c44db 100644
--- a/ewah/bitmap.c
+++ b/ewah/bitmap.c
@@ -25,7 +25,7 @@
struct bitmap *bitmap_word_alloc(size_t word_alloc)
{
struct bitmap *bitmap = xmalloc(sizeof(struct bitmap));
- bitmap->words = xcalloc(word_alloc, sizeof(eword_t));
+ CALLOC_ARRAY(bitmap->words, word_alloc);
bitmap->word_alloc = word_alloc;
return bitmap;
}
diff --git a/fetch-pack.c b/fetch-pack.c
index 0392d36c0a..c135635e34 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -23,6 +23,8 @@
#include "fetch-negotiator.h"
#include "fsck.h"
#include "shallow.h"
+#include "commit-reach.h"
+#include "commit-graph.h"
static int transfer_unpack_limit = -1;
static int fetch_unpack_limit = -1;
@@ -45,6 +47,8 @@ static struct string_list uri_protocols = STRING_LIST_INIT_DUP;
/* Remember to update object flag allocation in object.h */
#define COMPLETE (1U << 0)
#define ALTERNATE (1U << 1)
+#define COMMON (1U << 6)
+#define REACH_SCRATCH (1U << 7)
/*
* After sending this many "have"s if we do not get any new ACK , we
@@ -1113,9 +1117,11 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,
if (args->deepen)
setup_alternate_shallow(&shallow_lock, &alternate_shallow_file,
NULL);
- else if (si->nr_ours || si->nr_theirs)
+ else if (si->nr_ours || si->nr_theirs) {
+ if (args->reject_shallow_remote)
+ die(_("source repository is shallow, reject to clone."));
alternate_shallow_file = setup_temporary_shallow(si->shallow);
- else
+ } else
alternate_shallow_file = NULL;
if (get_pack(args, fd, pack_lockfiles, NULL, sought, nr_sought,
&fsck_options.gitmodules_found))
@@ -1193,11 +1199,9 @@ static void add_common(struct strbuf *req_buf, struct oidset *common)
}
static int add_haves(struct fetch_negotiator *negotiator,
- int seen_ack,
struct strbuf *req_buf,
- int *haves_to_send, int *in_vain)
+ int *haves_to_send)
{
- int ret = 0;
int haves_added = 0;
const struct object_id *oid;
@@ -1207,41 +1211,29 @@ static int add_haves(struct fetch_negotiator *negotiator,
break;
}
- *in_vain += haves_added;
- if (!haves_added || (seen_ack && *in_vain >= MAX_IN_VAIN)) {
- /* Send Done */
- packet_buf_write(req_buf, "done\n");
- ret = 1;
- }
-
/* Increase haves to send on next round */
*haves_to_send = next_flush(1, *haves_to_send);
- return ret;
+ return haves_added;
}
-static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
- struct fetch_pack_args *args,
- const struct ref *wants, struct oidset *common,
- int *haves_to_send, int *in_vain,
- int sideband_all, int seen_ack)
+static void write_fetch_command_and_capabilities(struct strbuf *req_buf,
+ const struct string_list *server_options)
{
- int ret = 0;
const char *hash_name;
- struct strbuf req_buf = STRBUF_INIT;
if (server_supports_v2("fetch", 1))
- packet_buf_write(&req_buf, "command=fetch");
+ packet_buf_write(req_buf, "command=fetch");
if (server_supports_v2("agent", 0))
- packet_buf_write(&req_buf, "agent=%s", git_user_agent_sanitized());
+ packet_buf_write(req_buf, "agent=%s", git_user_agent_sanitized());
if (advertise_sid && server_supports_v2("session-id", 0))
- packet_buf_write(&req_buf, "session-id=%s", trace2_session_id());
- if (args->server_options && args->server_options->nr &&
+ packet_buf_write(req_buf, "session-id=%s", trace2_session_id());
+ if (server_options && server_options->nr &&
server_supports_v2("server-option", 1)) {
int i;
- for (i = 0; i < args->server_options->nr; i++)
- packet_buf_write(&req_buf, "server-option=%s",
- args->server_options->items[i].string);
+ for (i = 0; i < server_options->nr; i++)
+ packet_buf_write(req_buf, "server-option=%s",
+ server_options->items[i].string);
}
if (server_feature_v2("object-format", &hash_name)) {
@@ -1249,13 +1241,26 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
if (hash_algo_by_ptr(the_hash_algo) != hash_algo)
die(_("mismatched algorithms: client %s; server %s"),
the_hash_algo->name, hash_name);
- packet_write_fmt(fd_out, "object-format=%s", the_hash_algo->name);
+ packet_buf_write(req_buf, "object-format=%s", the_hash_algo->name);
} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) {
die(_("the server does not support algorithm '%s'"),
the_hash_algo->name);
}
+ packet_buf_delim(req_buf);
+}
+
+static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
+ struct fetch_pack_args *args,
+ const struct ref *wants, struct oidset *common,
+ int *haves_to_send, int *in_vain,
+ int sideband_all, int seen_ack)
+{
+ int haves_added;
+ int done_sent = 0;
+ struct strbuf req_buf = STRBUF_INIT;
+
+ write_fetch_command_and_capabilities(&req_buf, args->server_options);
- packet_buf_delim(&req_buf);
if (args->use_thin_pack)
packet_buf_write(&req_buf, "thin-pack");
if (args->no_progress)
@@ -1310,9 +1315,13 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
/* Add all of the common commits we've found in previous rounds */
add_common(&req_buf, common);
- /* Add initial haves */
- ret = add_haves(negotiator, seen_ack, &req_buf,
- haves_to_send, in_vain);
+ haves_added = add_haves(negotiator, &req_buf, haves_to_send);
+ *in_vain += haves_added;
+ if (!haves_added || (seen_ack && *in_vain >= MAX_IN_VAIN)) {
+ /* Send Done */
+ packet_buf_write(&req_buf, "done\n");
+ done_sent = 1;
+ }
/* Send request */
packet_buf_flush(&req_buf);
@@ -1320,7 +1329,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
die_errno(_("unable to write request to remote"));
strbuf_release(&req_buf);
- return ret;
+ return done_sent;
}
/*
@@ -1349,35 +1358,11 @@ static int process_section_header(struct packet_reader *reader,
return ret;
}
-enum common_found {
- /*
- * No commit was found to be possessed by both the client and the
- * server, and "ready" was not received.
- */
- NO_COMMON_FOUND,
-
- /*
- * At least one commit was found to be possessed by both the client and
- * the server, and "ready" was not received.
- */
- COMMON_FOUND,
-
- /*
- * "ready" was received, indicating that the server is ready to send
- * the packfile without any further negotiation.
- */
- READY
-};
-
-static enum common_found process_acks(struct fetch_negotiator *negotiator,
- struct packet_reader *reader,
- struct oidset *common)
+static int process_ack(struct fetch_negotiator *negotiator,
+ struct packet_reader *reader,
+ struct object_id *common_oid,
+ int *received_ready)
{
- /* received */
- int received_ready = 0;
- int received_ack = 0;
-
- process_section_header(reader, "acknowledgments", 0);
while (packet_reader_read(reader) == PACKET_READ_NORMAL) {
const char *arg;
@@ -1385,20 +1370,17 @@ static enum common_found process_acks(struct fetch_negotiator *negotiator,
continue;
if (skip_prefix(reader->line, "ACK ", &arg)) {
- struct object_id oid;
- received_ack = 1;
- if (!get_oid_hex(arg, &oid)) {
+ if (!get_oid_hex(arg, common_oid)) {
struct commit *commit;
- oidset_insert(common, &oid);
- commit = lookup_commit(the_repository, &oid);
+ commit = lookup_commit(the_repository, common_oid);
if (negotiator)
negotiator->ack(negotiator, commit);
}
- continue;
+ return 1;
}
if (!strcmp(reader->line, "ready")) {
- received_ready = 1;
+ *received_ready = 1;
continue;
}
@@ -1416,13 +1398,12 @@ static enum common_found process_acks(struct fetch_negotiator *negotiator,
* sent. Therefore, a DELIM is expected if "ready" is sent, and a FLUSH
* otherwise.
*/
- if (received_ready && reader->status != PACKET_READ_DELIM)
+ if (*received_ready && reader->status != PACKET_READ_DELIM)
die(_("expected packfile to be sent after 'ready'"));
- if (!received_ready && reader->status != PACKET_READ_FLUSH)
+ if (!*received_ready && reader->status != PACKET_READ_FLUSH)
die(_("expected no other sections to be sent after no 'ready'"));
- return received_ready ? READY :
- (received_ack ? COMMON_FOUND : NO_COMMON_FOUND);
+ return 0;
}
static void receive_shallow_info(struct fetch_pack_args *args,
@@ -1483,10 +1464,12 @@ static void receive_shallow_info(struct fetch_pack_args *args,
* rejected (unless --update-shallow is set); do the same.
*/
prepare_shallow_info(si, shallows);
- if (si->nr_ours || si->nr_theirs)
+ if (si->nr_ours || si->nr_theirs) {
+ if (args->reject_shallow_remote)
+ die(_("source repository is shallow, reject to clone."));
alternate_shallow_file =
setup_temporary_shallow(si->shallow);
- else
+ } else
alternate_shallow_file = NULL;
} else {
alternate_shallow_file = NULL;
@@ -1544,10 +1527,10 @@ enum fetch_state {
FETCH_DONE,
};
-static void do_check_stateless_delimiter(const struct fetch_pack_args *args,
+static void do_check_stateless_delimiter(int stateless_rpc,
struct packet_reader *reader)
{
- check_stateless_delimiter(args->stateless_rpc, reader,
+ check_stateless_delimiter(stateless_rpc, reader,
_("git fetch-pack: expected response end packet"));
}
@@ -1569,6 +1552,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
struct fetch_negotiator negotiator_alloc;
struct fetch_negotiator *negotiator;
int seen_ack = 0;
+ struct object_id common_oid;
+ int received_ready = 0;
struct string_list packfile_uris = STRING_LIST_INIT_DUP;
int i;
struct strvec index_pack_args = STRVEC_INIT;
@@ -1627,22 +1612,22 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
break;
case FETCH_PROCESS_ACKS:
/* Process ACKs/NAKs */
- switch (process_acks(negotiator, &reader, &common)) {
- case READY:
+ process_section_header(&reader, "acknowledgments", 0);
+ while (process_ack(negotiator, &reader, &common_oid,
+ &received_ready)) {
+ in_vain = 0;
+ seen_ack = 1;
+ oidset_insert(&common, &common_oid);
+ }
+ if (received_ready) {
/*
* Don't check for response delimiter; get_pack() will
* read the rest of this response.
*/
state = FETCH_GET_PACK;
- break;
- case COMMON_FOUND:
- in_vain = 0;
- seen_ack = 1;
- /* fallthrough */
- case NO_COMMON_FOUND:
- do_check_stateless_delimiter(args, &reader);
+ } else {
+ do_check_stateless_delimiter(args->stateless_rpc, &reader);
state = FETCH_SEND_REQUEST;
- break;
}
break;
case FETCH_GET_PACK:
@@ -1664,7 +1649,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
packfile_uris.nr ? &index_pack_args : NULL,
sought, nr_sought, &fsck_options.gitmodules_found))
die(_("git fetch-pack: fetch failed."));
- do_check_stateless_delimiter(args, &reader);
+ do_check_stateless_delimiter(args->stateless_rpc, &reader);
state = FETCH_DONE;
break;
@@ -1901,7 +1886,7 @@ static void update_shallow(struct fetch_pack_args *args,
* remote is also shallow, check what ref is safe to update
* without updating .git/shallow
*/
- status = xcalloc(nr_sought, sizeof(*status));
+ CALLOC_ARRAY(status, nr_sought);
assign_shallow_commits_to_refs(si, NULL, status);
if (si->nr_ours || si->nr_theirs) {
for (i = 0; i < nr_sought; i++)
@@ -1981,6 +1966,105 @@ cleanup:
return ref_cpy;
}
+static int add_to_object_array(const struct object_id *oid, void *data)
+{
+ struct object_array *a = data;
+
+ add_object_array(lookup_object(the_repository, oid), "", a);
+ return 0;
+}
+
+static void clear_common_flag(struct oidset *s)
+{
+ struct oidset_iter iter;
+ const struct object_id *oid;
+ oidset_iter_init(s, &iter);
+
+ while ((oid = oidset_iter_next(&iter))) {
+ struct object *obj = lookup_object(the_repository, oid);
+ obj->flags &= ~COMMON;
+ }
+}
+
+void negotiate_using_fetch(const struct oid_array *negotiation_tips,
+ const struct string_list *server_options,
+ int stateless_rpc,
+ int fd[],
+ struct oidset *acked_commits)
+{
+ struct fetch_negotiator negotiator;
+ struct packet_reader reader;
+ struct object_array nt_object_array = OBJECT_ARRAY_INIT;
+ struct strbuf req_buf = STRBUF_INIT;
+ int haves_to_send = INITIAL_FLUSH;
+ int in_vain = 0;
+ int seen_ack = 0;
+ int last_iteration = 0;
+ timestamp_t min_generation = GENERATION_NUMBER_INFINITY;
+
+ fetch_negotiator_init(the_repository, &negotiator);
+ mark_tips(&negotiator, negotiation_tips);
+
+ packet_reader_init(&reader, fd[0], NULL, 0,
+ PACKET_READ_CHOMP_NEWLINE |
+ PACKET_READ_DIE_ON_ERR_PACKET);
+
+ oid_array_for_each((struct oid_array *) negotiation_tips,
+ add_to_object_array,
+ &nt_object_array);
+
+ while (!last_iteration) {
+ int haves_added;
+ struct object_id common_oid;
+ int received_ready = 0;
+
+ strbuf_reset(&req_buf);
+ write_fetch_command_and_capabilities(&req_buf, server_options);
+
+ packet_buf_write(&req_buf, "wait-for-done");
+
+ haves_added = add_haves(&negotiator, &req_buf, &haves_to_send);
+ in_vain += haves_added;
+ if (!haves_added || (seen_ack && in_vain >= MAX_IN_VAIN))
+ last_iteration = 1;
+
+ /* Send request */
+ packet_buf_flush(&req_buf);
+ if (write_in_full(fd[1], req_buf.buf, req_buf.len) < 0)
+ die_errno(_("unable to write request to remote"));
+
+ /* Process ACKs/NAKs */
+ process_section_header(&reader, "acknowledgments", 0);
+ while (process_ack(&negotiator, &reader, &common_oid,
+ &received_ready)) {
+ struct commit *commit = lookup_commit(the_repository,
+ &common_oid);
+ if (commit) {
+ timestamp_t generation;
+
+ parse_commit_or_die(commit);
+ commit->object.flags |= COMMON;
+ generation = commit_graph_generation(commit);
+ if (generation < min_generation)
+ min_generation = generation;
+ }
+ in_vain = 0;
+ seen_ack = 1;
+ oidset_insert(acked_commits, &common_oid);
+ }
+ if (received_ready)
+ die(_("unexpected 'ready' from remote"));
+ else
+ do_check_stateless_delimiter(stateless_rpc, &reader);
+ if (can_all_from_reach_with_flag(&nt_object_array, COMMON,
+ REACH_SCRATCH, 0,
+ min_generation))
+ last_iteration = 1;
+ }
+ clear_common_flag(acked_commits);
+ strbuf_release(&req_buf);
+}
+
int report_unmatched_refs(struct ref **sought, int nr_sought)
{
int i, ret = 0;
diff --git a/fetch-pack.h b/fetch-pack.h
index 736a3dae46..7f94a2a583 100644
--- a/fetch-pack.h
+++ b/fetch-pack.h
@@ -5,6 +5,7 @@
#include "run-command.h"
#include "protocol.h"
#include "list-objects-filter-options.h"
+#include "oidset.h"
struct oid_array;
@@ -39,6 +40,7 @@ struct fetch_pack_args {
unsigned self_contained_and_connected:1;
unsigned cloning:1;
unsigned update_shallow:1;
+ unsigned reject_shallow_remote:1;
unsigned deepen:1;
/*
@@ -81,6 +83,19 @@ struct ref *fetch_pack(struct fetch_pack_args *args,
enum protocol_version version);
/*
+ * Execute the --negotiate-only mode of "git fetch", adding all known common
+ * commits to acked_commits.
+ *
+ * In the capability advertisement that has happened prior to invoking this
+ * function, the "wait-for-done" capability must be present.
+ */
+void negotiate_using_fetch(const struct oid_array *negotiation_tips,
+ const struct string_list *server_options,
+ int stateless_rpc,
+ int fd[],
+ struct oidset *acked_commits);
+
+/*
* Print an appropriate error message for each sought ref that wasn't
* matched. Return 0 if all sought refs were matched, otherwise 1.
*/
diff --git a/fmt-merge-msg.c b/fmt-merge-msg.c
index 1e51492a05..0f66818e0f 100644
--- a/fmt-merge-msg.c
+++ b/fmt-merge-msg.c
@@ -130,7 +130,7 @@ static int handle_line(char *line, struct merge_parents *merge_parents)
if (!find_merge_parent(merge_parents, &oid, NULL))
return 0; /* subsumed by other parents */
- origin_data = xcalloc(1, sizeof(struct origin_data));
+ CALLOC_ARRAY(origin_data, 1);
oidcpy(&origin_data->oid, &oid);
if (line[len - 1] == '\n')
diff --git a/fsck.c b/fsck.c
index f5ed6a2635..3ec500d707 100644
--- a/fsck.c
+++ b/fsck.c
@@ -558,7 +558,7 @@ static int verify_ordered(unsigned mode1, const char *name1,
return c1 < c2 ? 0 : TREE_UNORDERED;
}
-static int fsck_tree(const struct object_id *oid,
+static int fsck_tree(const struct object_id *tree_oid,
const char *buffer, unsigned long size,
struct fsck_options *options)
{
@@ -579,7 +579,9 @@ static int fsck_tree(const struct object_id *oid,
struct name_stack df_dup_candidates = { NULL };
if (init_tree_desc_gently(&desc, buffer, size)) {
- retval += report(options, oid, OBJ_TREE, FSCK_MSG_BAD_TREE, "cannot be parsed as a tree");
+ retval += report(options, tree_oid, OBJ_TREE,
+ FSCK_MSG_BAD_TREE,
+ "cannot be parsed as a tree");
return retval;
}
@@ -589,11 +591,11 @@ static int fsck_tree(const struct object_id *oid,
while (desc.size) {
unsigned short mode;
const char *name, *backslash;
- const struct object_id *oid;
+ const struct object_id *entry_oid;
- oid = tree_entry_extract(&desc, &name, &mode);
+ entry_oid = tree_entry_extract(&desc, &name, &mode);
- has_null_sha1 |= is_null_oid(oid);
+ has_null_sha1 |= is_null_oid(entry_oid);
has_full_path |= !!strchr(name, '/');
has_empty_name |= !*name;
has_dot |= !strcmp(name, ".");
@@ -603,23 +605,43 @@ static int fsck_tree(const struct object_id *oid,
if (is_hfs_dotgitmodules(name) || is_ntfs_dotgitmodules(name)) {
if (!S_ISLNK(mode))
- oidset_insert(&options->gitmodules_found, oid);
+ oidset_insert(&options->gitmodules_found,
+ entry_oid);
else
retval += report(options,
- oid, OBJ_TREE,
+ tree_oid, OBJ_TREE,
FSCK_MSG_GITMODULES_SYMLINK,
".gitmodules is a symbolic link");
}
+ if (S_ISLNK(mode)) {
+ if (is_hfs_dotgitignore(name) ||
+ is_ntfs_dotgitignore(name))
+ retval += report(options, tree_oid, OBJ_TREE,
+ FSCK_MSG_GITIGNORE_SYMLINK,
+ ".gitignore is a symlink");
+ if (is_hfs_dotgitattributes(name) ||
+ is_ntfs_dotgitattributes(name))
+ retval += report(options, tree_oid, OBJ_TREE,
+ FSCK_MSG_GITATTRIBUTES_SYMLINK,
+ ".gitattributes is a symlink");
+ if (is_hfs_dotmailmap(name) ||
+ is_ntfs_dotmailmap(name))
+ retval += report(options, tree_oid, OBJ_TREE,
+ FSCK_MSG_MAILMAP_SYMLINK,
+ ".mailmap is a symlink");
+ }
+
if ((backslash = strchr(name, '\\'))) {
while (backslash) {
backslash++;
has_dotgit |= is_ntfs_dotgit(backslash);
if (is_ntfs_dotgitmodules(backslash)) {
if (!S_ISLNK(mode))
- oidset_insert(&options->gitmodules_found, oid);
+ oidset_insert(&options->gitmodules_found,
+ entry_oid);
else
- retval += report(options, oid, OBJ_TREE,
+ retval += report(options, tree_oid, OBJ_TREE,
FSCK_MSG_GITMODULES_SYMLINK,
".gitmodules is a symbolic link");
}
@@ -628,7 +650,9 @@ static int fsck_tree(const struct object_id *oid,
}
if (update_tree_entry_gently(&desc)) {
- retval += report(options, oid, OBJ_TREE, FSCK_MSG_BAD_TREE, "cannot be parsed as a tree");
+ retval += report(options, tree_oid, OBJ_TREE,
+ FSCK_MSG_BAD_TREE,
+ "cannot be parsed as a tree");
break;
}
@@ -676,25 +700,45 @@ static int fsck_tree(const struct object_id *oid,
name_stack_clear(&df_dup_candidates);
if (has_null_sha1)
- retval += report(options, oid, OBJ_TREE, FSCK_MSG_NULL_SHA1, "contains entries pointing to null sha1");
+ retval += report(options, tree_oid, OBJ_TREE,
+ FSCK_MSG_NULL_SHA1,
+ "contains entries pointing to null sha1");
if (has_full_path)
- retval += report(options, oid, OBJ_TREE, FSCK_MSG_FULL_PATHNAME, "contains full pathnames");
+ retval += report(options, tree_oid, OBJ_TREE,
+ FSCK_MSG_FULL_PATHNAME,
+ "contains full pathnames");
if (has_empty_name)
- retval += report(options, oid, OBJ_TREE, FSCK_MSG_EMPTY_NAME, "contains empty pathname");
+ retval += report(options, tree_oid, OBJ_TREE,
+ FSCK_MSG_EMPTY_NAME,
+ "contains empty pathname");
if (has_dot)
- retval += report(options, oid, OBJ_TREE, FSCK_MSG_HAS_DOT, "contains '.'");
+ retval += report(options, tree_oid, OBJ_TREE,
+ FSCK_MSG_HAS_DOT,
+ "contains '.'");
if (has_dotdot)
- retval += report(options, oid, OBJ_TREE, FSCK_MSG_HAS_DOTDOT, "contains '..'");
+ retval += report(options, tree_oid, OBJ_TREE,
+ FSCK_MSG_HAS_DOTDOT,
+ "contains '..'");
if (has_dotgit)
- retval += report(options, oid, OBJ_TREE, FSCK_MSG_HAS_DOTGIT, "contains '.git'");
+ retval += report(options, tree_oid, OBJ_TREE,
+ FSCK_MSG_HAS_DOTGIT,
+ "contains '.git'");
if (has_zero_pad)
- retval += report(options, oid, OBJ_TREE, FSCK_MSG_ZERO_PADDED_FILEMODE, "contains zero-padded file modes");
+ retval += report(options, tree_oid, OBJ_TREE,
+ FSCK_MSG_ZERO_PADDED_FILEMODE,
+ "contains zero-padded file modes");
if (has_bad_modes)
- retval += report(options, oid, OBJ_TREE, FSCK_MSG_BAD_FILEMODE, "contains bad file modes");
+ retval += report(options, tree_oid, OBJ_TREE,
+ FSCK_MSG_BAD_FILEMODE,
+ "contains bad file modes");
if (has_dup_entries)
- retval += report(options, oid, OBJ_TREE, FSCK_MSG_DUPLICATE_ENTRIES, "contains duplicate file entries");
+ retval += report(options, tree_oid, OBJ_TREE,
+ FSCK_MSG_DUPLICATE_ENTRIES,
+ "contains duplicate file entries");
if (not_properly_sorted)
- retval += report(options, oid, OBJ_TREE, FSCK_MSG_TREE_NOT_SORTED, "not properly sorted");
+ retval += report(options, tree_oid, OBJ_TREE,
+ FSCK_MSG_TREE_NOT_SORTED,
+ "not properly sorted");
return retval;
}
diff --git a/fsck.h b/fsck.h
index 7202c3c87e..d07f7a2459 100644
--- a/fsck.h
+++ b/fsck.h
@@ -67,6 +67,9 @@ enum fsck_msg_type {
FUNC(NUL_IN_COMMIT, WARN) \
/* infos (reported as warnings, but ignored by default) */ \
FUNC(GITMODULES_PARSE, INFO) \
+ FUNC(GITIGNORE_SYMLINK, INFO) \
+ FUNC(GITATTRIBUTES_SYMLINK, INFO) \
+ FUNC(MAILMAP_SYMLINK, INFO) \
FUNC(BAD_TAG_NAME, INFO) \
FUNC(MISSING_TAGGER_ENTRY, INFO) \
/* ignored (elevated when requested) */ \
diff --git a/fsmonitor.c b/fsmonitor.c
index 23f8a0c97e..ab9bfc60b3 100644
--- a/fsmonitor.c
+++ b/fsmonitor.c
@@ -185,10 +185,10 @@ static int query_fsmonitor(int version, const char *last_update, struct strbuf *
int fsmonitor_is_trivial_response(const struct strbuf *query_result)
{
static char trivial_response[3] = { '\0', '/', '\0' };
- int is_trivial = !memcmp(trivial_response,
- &query_result->buf[query_result->len - 3], 3);
- return is_trivial;
+ return query_result->len >= 3 &&
+ !memcmp(trivial_response,
+ &query_result->buf[query_result->len - 3], 3);
}
static void fsmonitor_refresh_callback(struct index_state *istate, char *name)
diff --git a/fsmonitor.h b/fsmonitor.h
index 7f1794b90b..f20d72631d 100644
--- a/fsmonitor.h
+++ b/fsmonitor.h
@@ -50,6 +50,17 @@ void refresh_fsmonitor(struct index_state *istate);
int fsmonitor_is_trivial_response(const struct strbuf *query_result);
/*
+ * Check if refresh_fsmonitor has been called at least once.
+ * refresh_fsmonitor is idempotent. Returns true if fsmonitor is
+ * not enabled (since the state will be "fresh" w/ CE_FSMONITOR_VALID unset)
+ * This version is useful for assertions
+ */
+static inline int is_fsmonitor_refreshed(const struct index_state *istate)
+{
+ return !core_fsmonitor || istate->fsmonitor_has_run_once;
+}
+
+/*
* Set the given cache entries CE_FSMONITOR_VALID bit. This should be
* called any time the cache entry has been updated to reflect the
* current state of the file on disk.
diff --git a/git-compat-util.h b/git-compat-util.h
index 3b2738c73d..a508dbe5a3 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -256,6 +256,11 @@ static inline const char *precompose_argv_prefix(int argc, const char **argv, co
{
return prefix;
}
+static inline const char *precompose_string_if_needed(const char *in)
+{
+ return in;
+}
+
#define probe_utf8_pathname_composition()
#endif
@@ -898,7 +903,7 @@ int xstrncmpz(const char *s, const char *t, size_t len);
#define FREE_AND_NULL(p) do { free(p); (p) = NULL; } while (0)
#define ALLOC_ARRAY(x, alloc) (x) = xmalloc(st_mult(sizeof(*(x)), (alloc)))
-#define CALLOC_ARRAY(x, alloc) (x) = xcalloc((alloc), sizeof(*(x)));
+#define CALLOC_ARRAY(x, alloc) (x) = xcalloc((alloc), sizeof(*(x)))
#define REALLOC_ARRAY(x, alloc) (x) = xrealloc((x), st_mult(sizeof(*(x)), (alloc)))
#define COPY_ARRAY(dst, src, n) copy_array((dst), (src), (n), sizeof(*(dst)) + \
@@ -1242,6 +1247,13 @@ int access_or_die(const char *path, int mode, unsigned flag);
/* Warn on an inaccessible file if errno indicates this is an error */
int warn_on_fopen_errors(const char *path);
+/*
+ * Open with O_NOFOLLOW, or equivalent. Note that the fallback equivalent
+ * may be racy. Do not use this as protection against an attacker who can
+ * simultaneously create paths.
+ */
+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
diff --git a/git-filter-branch.sh b/git-filter-branch.sh
index fea7964617..cb89372813 100755
--- a/git-filter-branch.sh
+++ b/git-filter-branch.sh
@@ -492,14 +492,12 @@ then
sha1=$(git rev-parse "$ref"^0)
test -f "$workdir"/../map/$sha1 && continue
ancestor=$(git rev-list --simplify-merges -1 "$ref" "$@")
- test "$ancestor" && echo $(map $ancestor) >> "$workdir"/../map/$sha1
+ test "$ancestor" && echo $(map $ancestor) >"$workdir"/../map/$sha1
done < "$tempdir"/heads
fi
# Finally update the refs
-_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"
echo
while read ref
do
@@ -519,7 +517,7 @@ do
git update-ref -m "filter-branch: delete" -d "$ref" $sha1 ||
die "Could not delete $ref"
;;
- $_x40)
+ *)
echo "Ref '$ref' was rewritten"
if ! git update-ref -m "filter-branch: rewrite" \
"$ref" $rewritten $sha1 2>/dev/null; then
@@ -533,16 +531,6 @@ do
fi
fi
;;
- *)
- # NEEDSWORK: possibly add -Werror, making this an error
- warn "WARNING: '$ref' was rewritten into multiple commits:"
- warn "$rewritten"
- warn "WARNING: Ref '$ref' points to the first one now."
- rewritten=$(echo "$rewritten" | head -n 1)
- git update-ref -m "filter-branch: rewrite to first" \
- "$ref" $rewritten $sha1 ||
- die "Could not rewrite $ref"
- ;;
esac
git update-ref -m "filter-branch: backup" "$orig_namespace$ref" $sha1 ||
exit
diff --git a/git-p4.py b/git-p4.py
index 09c9e93ac4..d34a1946b7 100755
--- a/git-p4.py
+++ b/git-p4.py
@@ -3600,19 +3600,18 @@ class P4Sync(Command, P4UserMap):
return True
def searchParent(self, parent, branch, target):
- parentFound = False
- for blob in read_pipe_lines(["git", "rev-list", "--reverse",
+ targetTree = read_pipe(["git", "rev-parse",
+ "{}^{{tree}}".format(target)]).strip()
+ for line in read_pipe_lines(["git", "rev-list", "--format=%H %T",
"--no-merges", parent]):
- blob = blob.strip()
- if len(read_pipe(["git", "diff-tree", blob, target])) == 0:
- parentFound = True
+ if line.startswith("commit "):
+ continue
+ commit, tree = line.strip().split(" ")
+ if tree == targetTree:
if self.verbose:
- print("Found parent of %s in commit %s" % (branch, blob))
- break
- if parentFound:
- return blob
- else:
- return None
+ print("Found parent of %s in commit %s" % (branch, commit))
+ return commit
+ return None
def importChanges(self, changes, origin_revision=0):
cnt = 1
diff --git a/git-send-email.perl b/git-send-email.perl
index 1f425c0809..25be2ebd2a 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -212,22 +212,41 @@ my $dump_aliases = 0;
my $multiedit;
my $editor;
+sub system_or_msg {
+ my ($args, $msg) = @_;
+ system(@$args);
+ my $signalled = $? & 127;
+ my $exit_code = $? >> 8;
+ return unless $signalled or $exit_code;
+
+ my @sprintf_args = ($args->[0], $exit_code);
+ if (defined $msg) {
+ # Quiet the 'redundant' warning category, except we
+ # need to support down to Perl 5.8, so we can't do a
+ # "no warnings 'redundant'", since that category was
+ # introduced in perl 5.22, and asking for it will die
+ # on older perls.
+ no warnings;
+ return sprintf($msg, @sprintf_args);
+ }
+ return sprintf(__("fatal: command '%s' died with exit code %d"),
+ @sprintf_args);
+}
+
+sub system_or_die {
+ my $msg = system_or_msg(@_);
+ die $msg if $msg;
+}
+
sub do_edit {
if (!defined($editor)) {
$editor = Git::command_oneline('var', 'GIT_EDITOR');
}
+ my $die_msg = __("the editor exited uncleanly, aborting everything");
if (defined($multiedit) && !$multiedit) {
- map {
- system('sh', '-c', $editor.' "$@"', $editor, $_);
- if (($? & 127) || ($? >> 8)) {
- die(__("the editor exited uncleanly, aborting everything"));
- }
- } @_;
+ system_or_die(['sh', '-c', $editor.' "$@"', $editor, $_], $die_msg) for @_;
} else {
- system('sh', '-c', $editor.' "$@"', $editor, @_);
- if (($? & 127) || ($? >> 8)) {
- die(__("the editor exited uncleanly, aborting everything"));
- }
+ system_or_die(['sh', '-c', $editor.' "$@"', $editor, @_], $die_msg);
}
}
@@ -698,9 +717,7 @@ if (@rev_list_opts) {
if ($validate) {
foreach my $f (@files) {
unless (-p $f) {
- my $error = validate_patch($f, $target_xfer_encoding);
- $error and die sprintf(__("fatal: %s: %s\nwarning: no patches were sent\n"),
- $f, $error);
+ validate_patch($f, $target_xfer_encoding);
}
}
}
@@ -1942,7 +1959,8 @@ sub validate_patch {
my ($fn, $xfer_encoding) = @_;
if ($repo) {
- my $validate_hook = catfile(catdir($repo->repo_path(), 'hooks'),
+ my $hooks_path = $repo->command_oneline('rev-parse', '--git-path', 'hooks');
+ my $validate_hook = catfile($hooks_path,
'sendemail-validate');
my $hook_error;
if (-x $validate_hook) {
@@ -1952,11 +1970,14 @@ sub validate_patch {
chdir($repo->wc_path() or $repo->repo_path())
or die("chdir: $!");
local $ENV{"GIT_DIR"} = $repo->repo_path();
- $hook_error = "rejected by sendemail-validate hook"
- if system($validate_hook, $target);
+ $hook_error = system_or_msg([$validate_hook, $target]);
chdir($cwd_save) or die("chdir: $!");
}
- return $hook_error if $hook_error;
+ if ($hook_error) {
+ die sprintf(__("fatal: %s: rejected by sendemail-validate hook\n" .
+ "%s\n" .
+ "warning: no patches were sent\n"), $fn, $hook_error);
+ }
}
# Any long lines will be automatically fixed if we use a suitable transfer
@@ -1966,7 +1987,8 @@ sub validate_patch {
or die sprintf(__("unable to open %s: %s\n"), $fn, $!);
while (my $line = <$fh>) {
if (length($line) > 998) {
- return sprintf(__("%s: patch contains a line longer than 998 characters"), $.);
+ die sprintf(__("fatal: %s:%d is longer than 998 characters\n" .
+ "warning: no patches were sent\n"), $fn, $.);
}
}
}
diff --git a/git-submodule.sh b/git-submodule.sh
index eb90f18229..4678378424 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -420,9 +420,9 @@ fetch_in_submodule () (
cd "$1" &&
if test $# -eq 3
then
- echo "$3" | git fetch --stdin ${2:+"$2"}
+ echo "$3" | git fetch ${GIT_QUIET:+--quiet} --stdin ${2:+"$2"}
else
- git fetch ${2:+"$2"}
+ git fetch ${GIT_QUIET:+--quiet} ${2:+"$2"}
fi
)
diff --git a/git.c b/git.c
index 9bc077a025..18bed9a996 100644
--- a/git.c
+++ b/git.c
@@ -255,6 +255,14 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
git_config_push_parameter((*argv)[1]);
(*argv)++;
(*argc)--;
+ } else if (!strcmp(cmd, "--config-env")) {
+ if (*argc < 2) {
+ fprintf(stderr, _("no config key given for --config-env\n" ));
+ usage(git_usage_string);
+ }
+ git_config_push_env((*argv)[1]);
+ (*argv)++;
+ (*argc)--;
} else if (skip_prefix(cmd, "--config-env=", &cmd)) {
git_config_push_env(cmd);
} else if (!strcmp(cmd, "--literal-pathspecs")) {
@@ -423,7 +431,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
int nongit_ok;
prefix = setup_git_directory_gently(&nongit_ok);
}
- prefix = precompose_argv_prefix(argc, argv, prefix);
+ precompose_argv_prefix(argc, argv, NULL);
if (use_pager == -1 && p->option & (RUN_SETUP | RUN_SETUP_GENTLY) &&
!(p->option & DELAY_PAGER_CONFIG))
use_pager = check_pager_config(p->cmd);
@@ -490,6 +498,8 @@ static struct cmd_struct commands[] = {
{ "check-mailmap", cmd_check_mailmap, RUN_SETUP },
{ "check-ref-format", cmd_check_ref_format, NO_PARSEOPT },
{ "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
+ { "checkout--worker", cmd_checkout__worker,
+ RUN_SETUP | NEED_WORK_TREE | SUPPORT_SUPER_PREFIX },
{ "checkout-index", cmd_checkout_index,
RUN_SETUP | NEED_WORK_TREE},
{ "cherry", cmd_cherry, RUN_SETUP },
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 0959a782ec..e09e024a09 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -569,6 +569,15 @@ our %feature = (
'sub' => \&feature_extra_branch_refs,
'override' => 0,
'default' => []},
+
+ # Redact e-mail addresses.
+
+ # To enable system wide have in $GITWEB_CONFIG
+ # $feature{'email-privacy'}{'default'} = [1];
+ 'email-privacy' => {
+ 'sub' => sub { feature_bool('email-privacy', @_) },
+ 'override' => 1,
+ 'default' => [0]},
);
sub gitweb_get_feature {
@@ -3449,6 +3458,13 @@ sub parse_date {
return %date;
}
+sub hide_mailaddrs_if_private {
+ my $line = shift;
+ return $line unless gitweb_check_feature('email-privacy');
+ $line =~ s/<[^@>]+@[^>]+>/<redacted>/g;
+ return $line;
+}
+
sub parse_tag {
my $tag_id = shift;
my %tag;
@@ -3465,7 +3481,7 @@ sub parse_tag {
} elsif ($line =~ m/^tag (.+)$/) {
$tag{'name'} = $1;
} elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) {
- $tag{'author'} = $1;
+ $tag{'author'} = hide_mailaddrs_if_private($1);
$tag{'author_epoch'} = $2;
$tag{'author_tz'} = $3;
if ($tag{'author'} =~ m/^([^<]+) <([^>]*)>/) {
@@ -3513,7 +3529,7 @@ sub parse_commit_text {
} elsif ((!defined $withparents) && ($line =~ m/^parent ($oid_regex)$/)) {
push @parents, $1;
} elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) {
- $co{'author'} = to_utf8($1);
+ $co{'author'} = hide_mailaddrs_if_private(to_utf8($1));
$co{'author_epoch'} = $2;
$co{'author_tz'} = $3;
if ($co{'author'} =~ m/^([^<]+) <([^>]*)>/) {
@@ -3523,7 +3539,7 @@ sub parse_commit_text {
$co{'author_name'} = $co{'author'};
}
} elsif ($line =~ m/^committer (.*) ([0-9]+) (.*)$/) {
- $co{'committer'} = to_utf8($1);
+ $co{'committer'} = hide_mailaddrs_if_private(to_utf8($1));
$co{'committer_epoch'} = $2;
$co{'committer_tz'} = $3;
if ($co{'committer'} =~ m/^([^<]+) <([^>]*)>/) {
@@ -3568,9 +3584,10 @@ sub parse_commit_text {
if (! defined $co{'title'} || $co{'title'} eq "") {
$co{'title'} = $co{'title_short'} = '(no commit message)';
}
- # remove added spaces
+ # remove added spaces, redact e-mail addresses if applicable.
foreach my $line (@commit_lines) {
$line =~ s/^ //;
+ $line = hide_mailaddrs_if_private($line);
}
$co{'comment'} = \@commit_lines;
@@ -7489,7 +7506,8 @@ sub git_log_generic {
-accesskey => "n", -title => "Alt-n"}, "next");
}
my $patch_max = gitweb_get_feature('patches');
- if ($patch_max && !defined $file_name) {
+ if ($patch_max && !defined $file_name &&
+ !gitweb_check_feature('email-privacy')) {
if ($patch_max < 0 || @commitlist <= $patch_max) {
$paging_nav .= " &sdot; " .
$cgi->a({-href => href(action=>"patches", -replay=>1)},
@@ -7550,7 +7568,8 @@ sub git_commit {
} @$parents ) .
')';
}
- if (gitweb_check_feature('patches') && @$parents <= 1) {
+ if (gitweb_check_feature('patches') && @$parents <= 1 &&
+ !gitweb_check_feature('email-privacy')) {
$formats_nav .= " | " .
$cgi->a({-href => href(action=>"patch", -replay=>1)},
"patch");
@@ -7863,7 +7882,8 @@ sub git_commitdiff {
$formats_nav =
$cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)},
"raw");
- if ($patch_max && @{$co{'parents'}} <= 1) {
+ if ($patch_max && @{$co{'parents'}} <= 1 &&
+ !gitweb_check_feature('email-privacy')) {
$formats_nav .= " | " .
$cgi->a({-href => href(action=>"patch", -replay=>1)},
"patch");
diff --git a/grep.c b/grep.c
index aabfaaa4c3..8f91af1cb0 100644
--- a/grep.c
+++ b/grep.c
@@ -40,20 +40,6 @@ static struct grep_opt grep_defaults = {
.output = std_output,
};
-#ifdef USE_LIBPCRE2
-static pcre2_general_context *pcre2_global_context;
-
-static void *pcre2_malloc(PCRE2_SIZE size, MAYBE_UNUSED void *memory_data)
-{
- return malloc(size);
-}
-
-static void pcre2_free(void *pointer, MAYBE_UNUSED void *memory_data)
-{
- free(pointer);
-}
-#endif
-
static const char *color_grep_slots[] = {
[GREP_COLOR_CONTEXT] = "context",
[GREP_COLOR_FILENAME] = "filename",
@@ -152,20 +138,9 @@ int grep_config(const char *var, const char *value, void *cb)
* Initialize one instance of grep_opt and copy the
* default values from the template we read the configuration
* information in an earlier call to git_config(grep_config).
- *
- * If using PCRE, make sure that the library is configured
- * to use the same allocator as Git (e.g. nedmalloc on Windows).
- *
- * Any allocated memory needs to be released in grep_destroy().
*/
void grep_init(struct grep_opt *opt, struct repository *repo, const char *prefix)
{
-#if defined(USE_LIBPCRE2)
- if (!pcre2_global_context)
- pcre2_global_context = pcre2_general_context_create(
- pcre2_malloc, pcre2_free, NULL);
-#endif
-
*opt = grep_defaults;
opt->repo = repo;
@@ -175,13 +150,6 @@ void grep_init(struct grep_opt *opt, struct repository *repo, const char *prefix
opt->header_tail = &opt->header_list;
}
-void grep_destroy(void)
-{
-#ifdef USE_LIBPCRE2
- pcre2_general_context_free(pcre2_global_context);
-#endif
-}
-
static void grep_set_pattern_type_option(enum grep_pattern_type pattern_type, struct grep_opt *opt)
{
/*
@@ -363,6 +331,28 @@ static int is_fixed(const char *s, size_t len)
}
#ifdef USE_LIBPCRE2
+#define GREP_PCRE2_DEBUG_MALLOC 0
+
+static void *pcre2_malloc(PCRE2_SIZE size, MAYBE_UNUSED void *memory_data)
+{
+ void *pointer = malloc(size);
+#if GREP_PCRE2_DEBUG_MALLOC
+ static int count = 1;
+ fprintf(stderr, "PCRE2:%p -> #%02d: alloc(%lu)\n", pointer, count++, size);
+#endif
+ return pointer;
+}
+
+static void pcre2_free(void *pointer, MAYBE_UNUSED void *memory_data)
+{
+#if GREP_PCRE2_DEBUG_MALLOC
+ static int count = 1;
+ if (pointer)
+ fprintf(stderr, "PCRE2:%p -> #%02d: free()\n", pointer, count++);
+#endif
+ free(pointer);
+}
+
static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt)
{
int error;
@@ -373,17 +363,20 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt
int patinforet;
size_t jitsizearg;
- assert(opt->pcre2);
-
- p->pcre2_compile_context = NULL;
+ /*
+ * Call pcre2_general_context_create() before calling any
+ * other pcre2_*(). It sets up our malloc()/free() functions
+ * with which everything else is allocated.
+ */
+ p->pcre2_general_context = pcre2_general_context_create(
+ pcre2_malloc, pcre2_free, NULL);
+ if (!p->pcre2_general_context)
+ die("Couldn't allocate PCRE2 general context");
- /* pcre2_global_context is initialized in append_grep_pattern */
if (opt->ignore_case) {
if (!opt->ignore_locale && has_non_ascii(p->pattern)) {
- if (!pcre2_global_context)
- BUG("pcre2_global_context uninitialized");
- p->pcre2_tables = pcre2_maketables(pcre2_global_context);
- p->pcre2_compile_context = pcre2_compile_context_create(NULL);
+ p->pcre2_tables = pcre2_maketables(p->pcre2_general_context);
+ p->pcre2_compile_context = pcre2_compile_context_create(p->pcre2_general_context);
pcre2_set_character_tables(p->pcre2_compile_context,
p->pcre2_tables);
}
@@ -393,28 +386,18 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt
!(!opt->ignore_case && (p->fixed || p->is_fixed)))
options |= (PCRE2_UTF | PCRE2_MATCH_INVALID_UTF);
+#ifdef GIT_PCRE2_VERSION_10_36_OR_HIGHER
/* Work around https://bugs.exim.org/show_bug.cgi?id=2642 fixed in 10.36 */
- if (PCRE2_MATCH_INVALID_UTF && options & (PCRE2_UTF | PCRE2_CASELESS)) {
- struct strbuf buf;
- int len;
- int err;
-
- if ((len = pcre2_config(PCRE2_CONFIG_VERSION, NULL)) < 0)
- BUG("pcre2_config(..., NULL) failed: %d", len);
- strbuf_init(&buf, len + 1);
- if ((err = pcre2_config(PCRE2_CONFIG_VERSION, buf.buf)) < 0)
- BUG("pcre2_config(..., buf.buf) failed: %d", err);
- if (versioncmp(buf.buf, "10.36") < 0)
- options |= PCRE2_NO_START_OPTIMIZE;
- strbuf_release(&buf);
- }
+ if (PCRE2_MATCH_INVALID_UTF && options & (PCRE2_UTF | PCRE2_CASELESS))
+ options |= PCRE2_NO_START_OPTIMIZE;
+#endif
p->pcre2_pattern = pcre2_compile((PCRE2_SPTR)p->pattern,
p->patternlen, options, &error, &erroffset,
p->pcre2_compile_context);
if (p->pcre2_pattern) {
- p->pcre2_match_data = pcre2_match_data_create_from_pattern(p->pcre2_pattern, NULL);
+ p->pcre2_match_data = pcre2_match_data_create_from_pattern(p->pcre2_pattern, p->pcre2_general_context);
if (!p->pcre2_match_data)
die("Couldn't allocate PCRE2 match data");
} else {
@@ -493,7 +476,12 @@ static void free_pcre2_pattern(struct grep_pat *p)
pcre2_compile_context_free(p->pcre2_compile_context);
pcre2_code_free(p->pcre2_pattern);
pcre2_match_data_free(p->pcre2_match_data);
+#ifdef GIT_PCRE2_VERSION_10_34_OR_HIGHER
+ pcre2_maketables_free(p->pcre2_general_context, p->pcre2_tables);
+#else
free((void *)p->pcre2_tables);
+#endif
+ pcre2_general_context_free(p->pcre2_general_context);
}
#else /* !USE_LIBPCRE2 */
static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt)
@@ -555,7 +543,6 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
#endif
if (p->fixed || p->is_fixed) {
#ifdef USE_LIBPCRE2
- opt->pcre2 = 1;
if (p->is_fixed) {
compile_pcre2_pattern(p, opt);
} else {
@@ -621,7 +608,7 @@ static struct grep_expr *compile_pattern_atom(struct grep_pat **list)
case GREP_PATTERN: /* atom */
case GREP_PATTERN_HEAD:
case GREP_PATTERN_BODY:
- x = xcalloc(1, sizeof (struct grep_expr));
+ CALLOC_ARRAY(x, 1);
x->node = GREP_NODE_ATOM;
x->u.atom = p;
*list = p->next;
@@ -651,7 +638,7 @@ static struct grep_expr *compile_pattern_not(struct grep_pat **list)
if (!p->next)
die("--not not followed by pattern expression");
*list = p->next;
- x = xcalloc(1, sizeof (struct grep_expr));
+ CALLOC_ARRAY(x, 1);
x->node = GREP_NODE_NOT;
x->u.unary = compile_pattern_not(list);
if (!x->u.unary)
@@ -676,7 +663,7 @@ static struct grep_expr *compile_pattern_and(struct grep_pat **list)
y = compile_pattern_and(list);
if (!y)
die("--and not followed by pattern expression");
- z = xcalloc(1, sizeof (struct grep_expr));
+ CALLOC_ARRAY(z, 1);
z->node = GREP_NODE_AND;
z->u.binary.left = x;
z->u.binary.right = y;
@@ -696,7 +683,7 @@ static struct grep_expr *compile_pattern_or(struct grep_pat **list)
y = compile_pattern_or(list);
if (!y)
die("not a pattern expression %s", p->pattern);
- z = xcalloc(1, sizeof (struct grep_expr));
+ CALLOC_ARRAY(z, 1);
z->node = GREP_NODE_OR;
z->u.binary.left = x;
z->u.binary.right = y;
@@ -1507,7 +1494,7 @@ static int fill_textconv_grep(struct repository *r,
fill_filespec(df, gs->identifier, 1, 0100644);
break;
case GREP_SOURCE_FILE:
- fill_filespec(df, &null_oid, 0, 0100644);
+ fill_filespec(df, null_oid(), 0, 0100644);
break;
default:
BUG("attempt to textconv something without a path?");
diff --git a/grep.h b/grep.h
index ae89d6254b..72f82b1e30 100644
--- a/grep.h
+++ b/grep.h
@@ -4,10 +4,17 @@
#ifdef USE_LIBPCRE2
#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>
+#if (PCRE2_MAJOR >= 10 && PCRE2_MINOR >= 36) || PCRE2_MAJOR >= 11
+#define GIT_PCRE2_VERSION_10_36_OR_HIGHER
+#endif
+#if (PCRE2_MAJOR >= 10 && PCRE2_MINOR >= 34) || PCRE2_MAJOR >= 11
+#define GIT_PCRE2_VERSION_10_34_OR_HIGHER
+#endif
#else
typedef int pcre2_code;
typedef int pcre2_match_data;
typedef int pcre2_compile_context;
+typedef int pcre2_general_context;
#endif
#ifndef PCRE2_MATCH_INVALID_UTF
/* PCRE2_MATCH_* dummy also with !USE_LIBPCRE2, for test-pcre2-config.c */
@@ -69,6 +76,7 @@ struct grep_pat {
pcre2_code *pcre2_pattern;
pcre2_match_data *pcre2_match_data;
pcre2_compile_context *pcre2_compile_context;
+ pcre2_general_context *pcre2_general_context;
const uint8_t *pcre2_tables;
uint32_t pcre2_jit_on;
unsigned fixed:1;
@@ -161,7 +169,6 @@ struct grep_opt {
int grep_config(const char *var, const char *value, void *);
void grep_init(struct grep_opt *, struct repository *repo, const char *prefix);
-void grep_destroy(void);
void grep_commit_pattern_type(enum grep_pattern_type, struct grep_opt *opt);
void append_grep_pat(struct grep_opt *opt, const char *pat, size_t patlen, const char *origin, int no, enum grep_pat_token t);
diff --git a/hash.h b/hash.h
index 3fb0c3d400..2986f991c6 100644
--- a/hash.h
+++ b/hash.h
@@ -95,6 +95,29 @@ static inline void git_SHA256_Clone(git_SHA256_CTX *dst, const git_SHA256_CTX *s
/* Number of algorithms supported (including unknown). */
#define GIT_HASH_NALGOS (GIT_HASH_SHA256 + 1)
+/* The length in bytes and in hex digits of an object name (SHA-1 value). */
+#define GIT_SHA1_RAWSZ 20
+#define GIT_SHA1_HEXSZ (2 * GIT_SHA1_RAWSZ)
+/* The block size of SHA-1. */
+#define GIT_SHA1_BLKSZ 64
+
+/* The length in bytes and in hex digits of an object name (SHA-256 value). */
+#define GIT_SHA256_RAWSZ 32
+#define GIT_SHA256_HEXSZ (2 * GIT_SHA256_RAWSZ)
+/* The block size of SHA-256. */
+#define GIT_SHA256_BLKSZ 64
+
+/* The length in byte and in hex digits of the largest possible hash value. */
+#define GIT_MAX_RAWSZ GIT_SHA256_RAWSZ
+#define GIT_MAX_HEXSZ GIT_SHA256_HEXSZ
+/* The largest possible block size for any supported hash. */
+#define GIT_MAX_BLKSZ GIT_SHA256_BLKSZ
+
+struct object_id {
+ unsigned char hash[GIT_MAX_RAWSZ];
+ int algo;
+};
+
/* A suitably aligned type for stack allocations of hash contexts. */
union git_hash_ctx {
git_SHA_CTX sha1;
@@ -106,6 +129,7 @@ typedef void (*git_hash_init_fn)(git_hash_ctx *ctx);
typedef void (*git_hash_clone_fn)(git_hash_ctx *dst, const git_hash_ctx *src);
typedef void (*git_hash_update_fn)(git_hash_ctx *ctx, const void *in, size_t len);
typedef void (*git_hash_final_fn)(unsigned char *hash, git_hash_ctx *ctx);
+typedef void (*git_hash_final_oid_fn)(struct object_id *oid, git_hash_ctx *ctx);
struct git_hash_algo {
/*
@@ -138,11 +162,17 @@ struct git_hash_algo {
/* The hash finalization function. */
git_hash_final_fn final_fn;
+ /* The hash finalization function for object IDs. */
+ git_hash_final_oid_fn final_oid_fn;
+
/* The OID of the empty tree. */
const struct object_id *empty_tree;
/* The OID of the empty blob. */
const struct object_id *empty_blob;
+
+ /* The all-zeros OID. */
+ const struct object_id *null_oid;
};
extern const struct git_hash_algo hash_algos[GIT_HASH_NALGOS];
@@ -161,67 +191,65 @@ static inline int hash_algo_by_ptr(const struct git_hash_algo *p)
return p - hash_algos;
}
-/* The length in bytes and in hex digits of an object name (SHA-1 value). */
-#define GIT_SHA1_RAWSZ 20
-#define GIT_SHA1_HEXSZ (2 * GIT_SHA1_RAWSZ)
-/* The block size of SHA-1. */
-#define GIT_SHA1_BLKSZ 64
-
-/* The length in bytes and in hex digits of an object name (SHA-256 value). */
-#define GIT_SHA256_RAWSZ 32
-#define GIT_SHA256_HEXSZ (2 * GIT_SHA256_RAWSZ)
-/* The block size of SHA-256. */
-#define GIT_SHA256_BLKSZ 64
-
-/* The length in byte and in hex digits of the largest possible hash value. */
-#define GIT_MAX_RAWSZ GIT_SHA256_RAWSZ
-#define GIT_MAX_HEXSZ GIT_SHA256_HEXSZ
-/* The largest possible block size for any supported hash. */
-#define GIT_MAX_BLKSZ GIT_SHA256_BLKSZ
-
-struct object_id {
- unsigned char hash[GIT_MAX_RAWSZ];
-};
-
#define the_hash_algo the_repository->hash_algo
-extern const struct object_id null_oid;
+const struct object_id *null_oid(void);
-static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2)
+static inline int hashcmp_algop(const unsigned char *sha1, const unsigned char *sha2, const struct git_hash_algo *algop)
{
/*
* Teach the compiler that there are only two possibilities of hash size
* here, so that it can optimize for this case as much as possible.
*/
- if (the_hash_algo->rawsz == GIT_MAX_RAWSZ)
+ if (algop->rawsz == GIT_MAX_RAWSZ)
return memcmp(sha1, sha2, GIT_MAX_RAWSZ);
return memcmp(sha1, sha2, GIT_SHA1_RAWSZ);
}
+static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2)
+{
+ return hashcmp_algop(sha1, sha2, the_hash_algo);
+}
+
static inline int oidcmp(const struct object_id *oid1, const struct object_id *oid2)
{
- return hashcmp(oid1->hash, oid2->hash);
+ const struct git_hash_algo *algop;
+ if (!oid1->algo)
+ algop = the_hash_algo;
+ else
+ algop = &hash_algos[oid1->algo];
+ return hashcmp_algop(oid1->hash, oid2->hash, algop);
}
-static inline int hasheq(const unsigned char *sha1, const unsigned char *sha2)
+static inline int hasheq_algop(const unsigned char *sha1, const unsigned char *sha2, const struct git_hash_algo *algop)
{
/*
* We write this here instead of deferring to hashcmp so that the
* compiler can properly inline it and avoid calling memcmp.
*/
- if (the_hash_algo->rawsz == GIT_MAX_RAWSZ)
+ if (algop->rawsz == GIT_MAX_RAWSZ)
return !memcmp(sha1, sha2, GIT_MAX_RAWSZ);
return !memcmp(sha1, sha2, GIT_SHA1_RAWSZ);
}
+static inline int hasheq(const unsigned char *sha1, const unsigned char *sha2)
+{
+ return hasheq_algop(sha1, sha2, the_hash_algo);
+}
+
static inline int oideq(const struct object_id *oid1, const struct object_id *oid2)
{
- return hasheq(oid1->hash, oid2->hash);
+ const struct git_hash_algo *algop;
+ if (!oid1->algo)
+ algop = the_hash_algo;
+ else
+ algop = &hash_algos[oid1->algo];
+ return hasheq_algop(oid1->hash, oid2->hash, algop);
}
static inline int is_null_oid(const struct object_id *oid)
{
- return oideq(oid, &null_oid);
+ return oideq(oid, null_oid());
}
static inline void hashcpy(unsigned char *sha_dst, const unsigned char *sha_src)
@@ -232,6 +260,7 @@ static inline void hashcpy(unsigned char *sha_dst, const unsigned char *sha_src)
static inline void oidcpy(struct object_id *dst, const struct object_id *src)
{
memcpy(dst->hash, src->hash, GIT_MAX_RAWSZ);
+ dst->algo = src->algo;
}
static inline struct object_id *oiddup(const struct object_id *src)
@@ -249,11 +278,13 @@ static inline void hashclr(unsigned char *hash)
static inline void oidclr(struct object_id *oid)
{
memset(oid->hash, 0, GIT_MAX_RAWSZ);
+ oid->algo = hash_algo_by_ptr(the_hash_algo);
}
static inline void oidread(struct object_id *oid, const unsigned char *hash)
{
memcpy(oid->hash, hash, the_hash_algo->rawsz);
+ oid->algo = hash_algo_by_ptr(the_hash_algo);
}
static inline int is_empty_blob_sha1(const unsigned char *sha1)
@@ -276,6 +307,11 @@ static inline int is_empty_tree_oid(const struct object_id *oid)
return oideq(oid, the_hash_algo->empty_tree);
}
+static inline void oid_set_algo(struct object_id *oid, const struct git_hash_algo *algop)
+{
+ oid->algo = hash_algo_by_ptr(algop);
+}
+
const char *empty_tree_oid_hex(void);
const char *empty_blob_oid_hex(void);
diff --git a/hashmap.c b/hashmap.c
index 5009471800..134d2eec80 100644
--- a/hashmap.c
+++ b/hashmap.c
@@ -76,7 +76,7 @@ unsigned int memihash_cont(unsigned int hash_seed, const void *buf, size_t len)
static void alloc_table(struct hashmap *map, unsigned int size)
{
map->tablesize = size;
- map->table = xcalloc(size, sizeof(struct hashmap_entry *));
+ CALLOC_ARRAY(map->table, size);
/* calculate resize thresholds for new size */
map->grow_at = (unsigned int) ((uint64_t) size * HASHMAP_LOAD_FACTOR / 100);
diff --git a/hex.c b/hex.c
index da51e64929..4f64d34696 100644
--- a/hex.c
+++ b/hex.c
@@ -69,7 +69,10 @@ int get_sha1_hex(const char *hex, unsigned char *sha1)
int get_oid_hex_algop(const char *hex, struct object_id *oid,
const struct git_hash_algo *algop)
{
- return get_hash_hex_algop(hex, oid->hash, algop);
+ int ret = get_hash_hex_algop(hex, oid->hash, algop);
+ if (!ret)
+ oid_set_algo(oid, algop);
+ return ret;
}
/*
@@ -80,7 +83,7 @@ int get_oid_hex_any(const char *hex, struct object_id *oid)
{
int i;
for (i = GIT_HASH_NALGOS - 1; i > 0; i--) {
- if (!get_hash_hex_algop(hex, oid->hash, &hash_algos[i]))
+ if (!get_oid_hex_algop(hex, oid, &hash_algos[i]))
return i;
}
return GIT_HASH_UNKNOWN;
@@ -95,7 +98,7 @@ int parse_oid_hex_algop(const char *hex, struct object_id *oid,
const char **end,
const struct git_hash_algo *algop)
{
- int ret = get_hash_hex_algop(hex, oid->hash, algop);
+ int ret = get_oid_hex_algop(hex, oid, algop);
if (!ret)
*end = hex + algop->hexsz;
return ret;
@@ -121,6 +124,13 @@ char *hash_to_hex_algop_r(char *buffer, const unsigned char *hash,
char *buf = buffer;
int i;
+ /*
+ * Our struct object_id has been memset to 0, so default to printing
+ * using the default hash.
+ */
+ if (algop == &hash_algos[0])
+ algop = the_hash_algo;
+
for (i = 0; i < algop->rawsz; i++) {
unsigned int val = *hash++;
*buf++ = hex[val >> 4];
@@ -133,7 +143,7 @@ char *hash_to_hex_algop_r(char *buffer, const unsigned char *hash,
char *oid_to_hex_r(char *buffer, const struct object_id *oid)
{
- return hash_to_hex_algop_r(buffer, oid->hash, the_hash_algo);
+ return hash_to_hex_algop_r(buffer, oid->hash, &hash_algos[oid->algo]);
}
char *hash_to_hex_algop(const unsigned char *hash, const struct git_hash_algo *algop)
@@ -151,5 +161,5 @@ char *hash_to_hex(const unsigned char *hash)
char *oid_to_hex(const struct object_id *oid)
{
- return hash_to_hex_algop(oid->hash, the_hash_algo);
+ return hash_to_hex_algop(oid->hash, &hash_algos[oid->algo]);
}
diff --git a/http-backend.c b/http-backend.c
index a03b4bae22..b329bf63f0 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -39,7 +39,7 @@ static struct string_list *get_parameters(void)
if (!query_params) {
const char *query = getenv("QUERY_STRING");
- query_params = xcalloc(1, sizeof(*query_params));
+ CALLOC_ARRAY(query_params, 1);
while (query && *query) {
char *name = url_decode_parameter_name(&query);
char *value = url_decode_parameter_value(&query);
diff --git a/http-push.c b/http-push.c
index 6a4a43e07f..d7cb1675a2 100644
--- a/http-push.c
+++ b/http-push.c
@@ -896,7 +896,7 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
- lock = xcalloc(1, sizeof(*lock));
+ CALLOC_ARRAY(lock, 1);
lock->timeout = -1;
if (start_active_slot(slot)) {
@@ -1022,6 +1022,8 @@ static void remote_ls(const char *path, int flags,
/* extract hex from sharded "xx/x{38}" filename */
static int get_oid_hex_from_objpath(const char *path, struct object_id *oid)
{
+ oid->algo = hash_algo_by_ptr(the_hash_algo);
+
if (strlen(path) != the_hash_algo->hexsz + 1)
return -1;
@@ -1436,7 +1438,7 @@ static void one_remote_ref(const char *refname)
* may be required for updating server info later.
*/
if (repo->can_update_info_refs && !has_object_file(&ref->old_oid)) {
- obj = lookup_unknown_object(&ref->old_oid);
+ obj = lookup_unknown_object(the_repository, &ref->old_oid);
fprintf(stderr, " fetch %s for %s\n",
oid_to_hex(&ref->old_oid), refname);
add_fetch_request(obj);
@@ -1713,7 +1715,7 @@ int cmd_main(int argc, const char **argv)
int new_refs;
struct ref *ref, *local_refs;
- repo = xcalloc(1, sizeof(*repo));
+ CALLOC_ARRAY(repo, 1);
argv++;
for (i = 1; i < argc; i++, argv++) {
diff --git a/http-walker.c b/http-walker.c
index 4fb1235cd4..90d8ecb57e 100644
--- a/http-walker.c
+++ b/http-walker.c
@@ -155,7 +155,7 @@ static void prefetch(struct walker *walker, unsigned char *sha1)
newreq = xmalloc(sizeof(*newreq));
newreq->walker = walker;
- hashcpy(newreq->oid.hash, sha1);
+ oidread(&newreq->oid, sha1);
newreq->repo = data->alt;
newreq->state = WAITING;
newreq->req = NULL;
diff --git a/http.c b/http.c
index f8ea28bb2e..8119247149 100644
--- a/http.c
+++ b/http.c
@@ -1635,9 +1635,18 @@ static int handle_curl_result(struct slot_results *results)
if (results->curl_result == CURLE_OK) {
credential_approve(&http_auth);
- if (proxy_auth.password)
- credential_approve(&proxy_auth);
+ credential_approve(&proxy_auth);
+ credential_approve(&cert_auth);
return HTTP_OK;
+ } else if (results->curl_result == CURLE_SSL_CERTPROBLEM) {
+ /*
+ * We can't tell from here whether it's a bad path, bad
+ * certificate, bad password, or something else wrong
+ * with the certificate. So we reject the credential to
+ * avoid caching or saving a bad password.
+ */
+ credential_reject(&cert_auth);
+ return HTTP_NOAUTH;
} else if (missing_target(results))
return HTTP_MISSING_TARGET;
else if (results->http_code == 401) {
@@ -2324,7 +2333,7 @@ struct http_pack_request *new_direct_http_pack_request(
off_t prev_posn = 0;
struct http_pack_request *preq;
- preq = xcalloc(1, sizeof(*preq));
+ CALLOC_ARRAY(preq, 1);
strbuf_init(&preq->tmpfile, 0);
preq->url = url;
@@ -2419,7 +2428,7 @@ struct http_object_request *new_http_object_request(const char *base_url,
off_t prev_posn = 0;
struct http_object_request *freq;
- freq = xcalloc(1, sizeof(*freq));
+ CALLOC_ARRAY(freq, 1);
strbuf_init(&freq->tmpfile, 0);
oidcpy(&freq->oid, oid);
freq->localfile = -1;
@@ -2566,7 +2575,7 @@ int finish_http_object_request(struct http_object_request *freq)
}
git_inflate_end(&freq->stream);
- the_hash_algo->final_fn(freq->real_oid.hash, &freq->c);
+ the_hash_algo->final_oid_fn(&freq->real_oid, &freq->c);
if (freq->zret != Z_STREAM_END) {
unlink_or_warn(freq->tmpfile.buf);
return -1;
diff --git a/imap-send.c b/imap-send.c
index d0b94f911e..bb085d66d1 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -963,9 +963,9 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
char *arg, *rsp;
int s = -1, preauth;
- ctx = xcalloc(1, sizeof(*ctx));
+ CALLOC_ARRAY(ctx, 1);
- ctx->imap = imap = xcalloc(1, sizeof(*imap));
+ ctx->imap = CALLOC_ARRAY(imap, 1);
imap->buf.sock.fd[0] = imap->buf.sock.fd[1] = -1;
imap->in_progress_append = &imap->in_progress;
diff --git a/line-log.c b/line-log.c
index 75c8b1acff..51d93310a4 100644
--- a/line-log.c
+++ b/line-log.c
@@ -296,7 +296,7 @@ static void line_log_data_insert(struct line_log_data **list,
return;
}
- p = xcalloc(1, sizeof(struct line_log_data));
+ CALLOC_ARRAY(p, 1);
p->path = path;
range_set_append(&p->ranges, begin, end);
if (ip) {
diff --git a/line-range.c b/line-range.c
index 9b50583dc0..955a8a9535 100644
--- a/line-range.c
+++ b/line-range.c
@@ -202,7 +202,7 @@ static const char *parse_range_funcname(
drv = userdiff_find_by_path(istate, path);
if (drv && drv->funcname.pattern) {
const struct userdiff_funcname *pe = &drv->funcname;
- xecfg = xcalloc(1, sizeof(*xecfg));
+ CALLOC_ARRAY(xecfg, 1);
xdiff_set_find_func(xecfg, pe->pattern, pe->cflags);
}
diff --git a/list-objects-filter-options.c b/list-objects-filter-options.c
index d2d1c81caf..96a605c8ad 100644
--- a/list-objects-filter-options.c
+++ b/list-objects-filter-options.c
@@ -29,6 +29,8 @@ const char *list_object_filter_config_name(enum list_objects_filter_choice c)
return "tree";
case LOFC_SPARSE_OID:
return "sparse:oid";
+ case LOFC_OBJECT_TYPE:
+ return "object:type";
case LOFC_COMBINE:
return "combine";
case LOFC__COUNT:
@@ -97,6 +99,19 @@ static int gently_parse_list_objects_filter(
}
return 1;
+ } else if (skip_prefix(arg, "object:type=", &v0)) {
+ int type = type_from_string_gently(v0, strlen(v0), 1);
+ if (type < 0) {
+ strbuf_addf(errbuf, _("'%s' for 'object:type=<type>' is"
+ "not a valid object type"), v0);
+ return 1;
+ }
+
+ filter_options->object_type = type;
+ filter_options->choice = LOFC_OBJECT_TYPE;
+
+ return 0;
+
} else if (skip_prefix(arg, "combine:", &v0)) {
return parse_combine_filter(filter_options, v0, errbuf);
diff --git a/list-objects-filter-options.h b/list-objects-filter-options.h
index 01767c3c96..da5b6737e2 100644
--- a/list-objects-filter-options.h
+++ b/list-objects-filter-options.h
@@ -1,6 +1,7 @@
#ifndef LIST_OBJECTS_FILTER_OPTIONS_H
#define LIST_OBJECTS_FILTER_OPTIONS_H
+#include "cache.h"
#include "parse-options.h"
#include "string-list.h"
@@ -13,6 +14,7 @@ enum list_objects_filter_choice {
LOFC_BLOB_LIMIT,
LOFC_TREE_DEPTH,
LOFC_SPARSE_OID,
+ LOFC_OBJECT_TYPE,
LOFC_COMBINE,
LOFC__COUNT /* must be last */
};
@@ -54,6 +56,7 @@ struct list_objects_filter_options {
char *sparse_oid_name;
unsigned long blob_limit_value;
unsigned long tree_exclude_depth;
+ enum object_type object_type;
/* LOFC_COMBINE values */
diff --git a/list-objects-filter.c b/list-objects-filter.c
index 4ec0041cfb..1c1ee3d1bb 100644
--- a/list-objects-filter.c
+++ b/list-objects-filter.c
@@ -82,6 +82,16 @@ static enum list_objects_filter_result filter_blobs_none(
default:
BUG("unknown filter_situation: %d", filter_situation);
+ case LOFS_TAG:
+ assert(obj->type == OBJ_TAG);
+ /* always include all tag objects */
+ return LOFR_MARK_SEEN | LOFR_DO_SHOW;
+
+ case LOFS_COMMIT:
+ assert(obj->type == OBJ_COMMIT);
+ /* always include all commit objects */
+ return LOFR_MARK_SEEN | LOFR_DO_SHOW;
+
case LOFS_BEGIN_TREE:
assert(obj->type == OBJ_TREE);
/* always include all tree objects */
@@ -173,6 +183,16 @@ static enum list_objects_filter_result filter_trees_depth(
default:
BUG("unknown filter_situation: %d", filter_situation);
+ case LOFS_TAG:
+ assert(obj->type == OBJ_TAG);
+ /* always include all tag objects */
+ return LOFR_MARK_SEEN | LOFR_DO_SHOW;
+
+ case LOFS_COMMIT:
+ assert(obj->type == OBJ_COMMIT);
+ /* always include all commit objects */
+ return LOFR_MARK_SEEN | LOFR_DO_SHOW;
+
case LOFS_END_TREE:
assert(obj->type == OBJ_TREE);
filter_data->current_depth--;
@@ -186,7 +206,7 @@ static enum list_objects_filter_result filter_trees_depth(
seen_info = oidmap_get(
&filter_data->seen_at_depth, &obj->oid);
if (!seen_info) {
- seen_info = xcalloc(1, sizeof(*seen_info));
+ CALLOC_ARRAY(seen_info, 1);
oidcpy(&seen_info->base.oid, &obj->oid);
seen_info->depth = filter_data->current_depth;
oidmap_put(&filter_data->seen_at_depth, seen_info);
@@ -267,6 +287,16 @@ static enum list_objects_filter_result filter_blobs_limit(
default:
BUG("unknown filter_situation: %d", filter_situation);
+ case LOFS_TAG:
+ assert(obj->type == OBJ_TAG);
+ /* always include all tag objects */
+ return LOFR_MARK_SEEN | LOFR_DO_SHOW;
+
+ case LOFS_COMMIT:
+ assert(obj->type == OBJ_COMMIT);
+ /* always include all commit objects */
+ return LOFR_MARK_SEEN | LOFR_DO_SHOW;
+
case LOFS_BEGIN_TREE:
assert(obj->type == OBJ_TREE);
/* always include all tree objects */
@@ -371,6 +401,16 @@ static enum list_objects_filter_result filter_sparse(
default:
BUG("unknown filter_situation: %d", filter_situation);
+ case LOFS_TAG:
+ assert(obj->type == OBJ_TAG);
+ /* always include all tag objects */
+ return LOFR_MARK_SEEN | LOFR_DO_SHOW;
+
+ case LOFS_COMMIT:
+ assert(obj->type == OBJ_COMMIT);
+ /* always include all commit objects */
+ return LOFR_MARK_SEEN | LOFR_DO_SHOW;
+
case LOFS_BEGIN_TREE:
assert(obj->type == OBJ_TREE);
dtype = DT_DIR;
@@ -505,6 +545,81 @@ static void filter_sparse_oid__init(
filter->free_fn = filter_sparse_free;
}
+/*
+ * A filter for list-objects to omit large blobs.
+ * And to OPTIONALLY collect a list of the omitted OIDs.
+ */
+struct filter_object_type_data {
+ enum object_type object_type;
+};
+
+static enum list_objects_filter_result filter_object_type(
+ struct repository *r,
+ enum list_objects_filter_situation filter_situation,
+ struct object *obj,
+ const char *pathname,
+ const char *filename,
+ struct oidset *omits,
+ void *filter_data_)
+{
+ struct filter_object_type_data *filter_data = filter_data_;
+
+ switch (filter_situation) {
+ default:
+ BUG("unknown filter_situation: %d", filter_situation);
+
+ case LOFS_TAG:
+ assert(obj->type == OBJ_TAG);
+ if (filter_data->object_type == OBJ_TAG)
+ return LOFR_MARK_SEEN | LOFR_DO_SHOW;
+ return LOFR_MARK_SEEN;
+
+ case LOFS_COMMIT:
+ assert(obj->type == OBJ_COMMIT);
+ if (filter_data->object_type == OBJ_COMMIT)
+ return LOFR_MARK_SEEN | LOFR_DO_SHOW;
+ return LOFR_MARK_SEEN;
+
+ case LOFS_BEGIN_TREE:
+ assert(obj->type == OBJ_TREE);
+
+ /*
+ * If we only want to show commits or tags, then there is no
+ * need to walk down trees.
+ */
+ if (filter_data->object_type == OBJ_COMMIT ||
+ filter_data->object_type == OBJ_TAG)
+ return LOFR_SKIP_TREE;
+
+ if (filter_data->object_type == OBJ_TREE)
+ return LOFR_MARK_SEEN | LOFR_DO_SHOW;
+
+ return LOFR_MARK_SEEN;
+
+ case LOFS_BLOB:
+ assert(obj->type == OBJ_BLOB);
+
+ if (filter_data->object_type == OBJ_BLOB)
+ return LOFR_MARK_SEEN | LOFR_DO_SHOW;
+ return LOFR_MARK_SEEN;
+
+ case LOFS_END_TREE:
+ return LOFR_ZERO;
+ }
+}
+
+static void filter_object_type__init(
+ struct list_objects_filter_options *filter_options,
+ struct filter *filter)
+{
+ struct filter_object_type_data *d = xcalloc(1, sizeof(*d));
+ d->object_type = filter_options->object_type;
+
+ filter->filter_data = d;
+ filter->filter_object_fn = filter_object_type;
+ filter->free_fn = free;
+}
+
/* A filter which only shows objects shown by all sub-filters. */
struct combine_filter_data {
struct subfilter *sub;
@@ -626,7 +741,7 @@ static void filter_combine__init(
size_t sub;
d->nr = filter_options->sub_nr;
- d->sub = xcalloc(d->nr, sizeof(*d->sub));
+ CALLOC_ARRAY(d->sub, d->nr);
for (sub = 0; sub < d->nr; sub++)
d->sub[sub].filter = list_objects_filter__init(
filter->omits ? &d->sub[sub].omits : NULL,
@@ -651,6 +766,7 @@ static filter_init_fn s_filters[] = {
filter_blobs_limit__init,
filter_trees_depth__init,
filter_sparse_oid__init,
+ filter_object_type__init,
filter_combine__init,
};
@@ -674,7 +790,7 @@ struct filter *list_objects_filter__init(
if (!init_fn)
return NULL;
- filter = xcalloc(1, sizeof(*filter));
+ CALLOC_ARRAY(filter, 1);
filter->omits = omitted;
init_fn(filter_options, filter);
return filter;
diff --git a/list-objects-filter.h b/list-objects-filter.h
index cfd784e203..9e98814111 100644
--- a/list-objects-filter.h
+++ b/list-objects-filter.h
@@ -55,6 +55,8 @@ enum list_objects_filter_result {
};
enum list_objects_filter_situation {
+ LOFS_COMMIT,
+ LOFS_TAG,
LOFS_BEGIN_TREE,
LOFS_END_TREE,
LOFS_BLOB
diff --git a/list-objects.c b/list-objects.c
index e19589baa0..7f404677d5 100644
--- a/list-objects.c
+++ b/list-objects.c
@@ -213,6 +213,21 @@ static void process_tree(struct traversal_context *ctx,
free_tree_buffer(tree);
}
+static void process_tag(struct traversal_context *ctx,
+ struct tag *tag,
+ const char *name)
+{
+ enum list_objects_filter_result r;
+
+ r = list_objects_filter__filter_object(ctx->revs->repo, LOFS_TAG,
+ &tag->object, NULL, NULL,
+ ctx->filter);
+ if (r & LOFR_MARK_SEEN)
+ tag->object.flags |= SEEN;
+ if (r & LOFR_DO_SHOW)
+ ctx->show_object(&tag->object, name, ctx->show_data);
+}
+
static void mark_edge_parents_uninteresting(struct commit *commit,
struct rev_info *revs,
show_edge_fn show_edge)
@@ -334,8 +349,7 @@ static void traverse_trees_and_blobs(struct traversal_context *ctx,
if (obj->flags & (UNINTERESTING | SEEN))
continue;
if (obj->type == OBJ_TAG) {
- obj->flags |= SEEN;
- ctx->show_object(obj, name, ctx->show_data);
+ process_tag(ctx, (struct tag *)obj, name);
continue;
}
if (!path)
@@ -361,6 +375,12 @@ static void do_traverse(struct traversal_context *ctx)
strbuf_init(&csp, PATH_MAX);
while ((commit = get_revision(ctx->revs)) != NULL) {
+ enum list_objects_filter_result r;
+
+ r = list_objects_filter__filter_object(ctx->revs->repo,
+ LOFS_COMMIT, &commit->object,
+ NULL, NULL, ctx->filter);
+
/*
* an uninteresting boundary commit may not have its tree
* parsed yet, but we are not going to show them anyway
@@ -375,7 +395,11 @@ static void do_traverse(struct traversal_context *ctx)
die(_("unable to load root tree for commit %s"),
oid_to_hex(&commit->object.oid));
}
- ctx->show_commit(commit, ctx->show_data);
+
+ if (r & LOFR_MARK_SEEN)
+ commit->object.flags |= SEEN;
+ if (r & LOFR_DO_SHOW)
+ ctx->show_commit(commit, ctx->show_data);
if (ctx->revs->tree_blobs_in_commit_order)
/*
diff --git a/ll-merge.c b/ll-merge.c
index 1ec0b959e0..9a8a2c365c 100644
--- a/ll-merge.c
+++ b/ll-merge.c
@@ -268,7 +268,7 @@ static int read_merge_config(const char *var, const char *value, void *cb)
if (!strncmp(fn->name, name, namelen) && !fn->name[namelen])
break;
if (!fn) {
- fn = xcalloc(1, sizeof(struct ll_merge_driver));
+ CALLOC_ARRAY(fn, 1);
fn->name = xmemdupz(name, namelen);
fn->fn = ll_ext_merge;
*ll_user_merge_tail = fn;
diff --git a/log-tree.c b/log-tree.c
index 4531cebfab..7b823786c2 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -369,8 +369,14 @@ void fmt_output_subject(struct strbuf *filename,
int start_len = filename->len;
int max_len = start_len + info->patch_name_max - (strlen(suffix) + 1);
- if (0 < info->reroll_count)
- strbuf_addf(filename, "v%d-", info->reroll_count);
+ if (info->reroll_count) {
+ struct strbuf temp = STRBUF_INIT;
+
+ strbuf_addf(&temp, "v%s", info->reroll_count);
+ format_sanitized_subject(filename, temp.buf, temp.len);
+ strbuf_addstr(filename, "-");
+ strbuf_release(&temp);
+ }
strbuf_addf(filename, "%04d-%s", nr, subject);
if (max_len < filename->len)
@@ -413,7 +419,7 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit,
{
const char *extra_headers = opt->extra_headers;
const char *name = oid_to_hex(opt->zero_commit ?
- &null_oid : &commit->object.oid);
+ null_oid() : &commit->object.oid);
*need_8bit_cte_p = 0; /* unknown */
diff --git a/mailinfo.c b/mailinfo.c
index 5681d9130d..ccc6beb27e 100644
--- a/mailinfo.c
+++ b/mailinfo.c
@@ -821,7 +821,7 @@ static int handle_commit_msg(struct mailinfo *mi, struct strbuf *line)
for (i = 0; header[i]; i++) {
if (mi->s_hdr_data[i])
strbuf_release(mi->s_hdr_data[i]);
- mi->s_hdr_data[i] = NULL;
+ FREE_AND_NULL(mi->s_hdr_data[i]);
}
return 0;
}
@@ -994,6 +994,16 @@ static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,
const char *rest;
if (!mi->format_flowed) {
+ if (len >= 2 &&
+ line->buf[len - 2] == '\r' &&
+ line->buf[len - 1] == '\n') {
+ mi->have_quoted_cr = 1;
+ if (mi->quoted_cr == quoted_cr_strip) {
+ strbuf_setlen(line, len - 2);
+ strbuf_addch(line, '\n');
+ len--;
+ }
+ }
handle_filter(mi, line);
return;
}
@@ -1033,6 +1043,13 @@ static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,
handle_filter(mi, line);
}
+static void summarize_quoted_cr(struct mailinfo *mi)
+{
+ if (mi->have_quoted_cr &&
+ mi->quoted_cr == quoted_cr_warn)
+ warning(_("quoted CRLF detected"));
+}
+
static void handle_body(struct mailinfo *mi, struct strbuf *line)
{
struct strbuf prev = STRBUF_INIT;
@@ -1051,6 +1068,8 @@ static void handle_body(struct mailinfo *mi, struct strbuf *line)
handle_filter(mi, &prev);
strbuf_reset(&prev);
}
+ summarize_quoted_cr(mi);
+ mi->have_quoted_cr = 0;
if (!handle_boundary(mi, line))
goto handle_body_out;
}
@@ -1100,6 +1119,7 @@ static void handle_body(struct mailinfo *mi, struct strbuf *line)
if (prev.len)
handle_filter(mi, &prev);
+ summarize_quoted_cr(mi);
flush_inbody_header_accum(mi);
@@ -1206,6 +1226,19 @@ int mailinfo(struct mailinfo *mi, const char *msg, const char *patch)
return mi->input_error;
}
+int mailinfo_parse_quoted_cr_action(const char *actionstr, int *action)
+{
+ if (!strcmp(actionstr, "nowarn"))
+ *action = quoted_cr_nowarn;
+ else if (!strcmp(actionstr, "warn"))
+ *action = quoted_cr_warn;
+ else if (!strcmp(actionstr, "strip"))
+ *action = quoted_cr_strip;
+ else
+ return -1;
+ return 0;
+}
+
static int git_mailinfo_config(const char *var, const char *value, void *mi_)
{
struct mailinfo *mi = mi_;
@@ -1216,6 +1249,11 @@ static int git_mailinfo_config(const char *var, const char *value, void *mi_)
mi->use_scissors = git_config_bool(var, value);
return 0;
}
+ if (!strcmp(var, "mailinfo.quotedcr")) {
+ if (mailinfo_parse_quoted_cr_action(value, &mi->quoted_cr) != 0)
+ return error(_("bad action '%s' for '%s'"), value, var);
+ return 0;
+ }
/* perhaps others here */
return 0;
}
@@ -1228,6 +1266,7 @@ void setup_mailinfo(struct mailinfo *mi)
strbuf_init(&mi->charset, 0);
strbuf_init(&mi->log_message, 0);
strbuf_init(&mi->inbody_header_accum, 0);
+ mi->quoted_cr = quoted_cr_warn;
mi->header_stage = 1;
mi->use_inbody_headers = 1;
mi->content_top = mi->content;
@@ -1236,22 +1275,14 @@ void setup_mailinfo(struct mailinfo *mi)
void clear_mailinfo(struct mailinfo *mi)
{
- int i;
-
strbuf_release(&mi->name);
strbuf_release(&mi->email);
strbuf_release(&mi->charset);
strbuf_release(&mi->inbody_header_accum);
free(mi->message_id);
- if (mi->p_hdr_data)
- for (i = 0; mi->p_hdr_data[i]; i++)
- strbuf_release(mi->p_hdr_data[i]);
- free(mi->p_hdr_data);
- if (mi->s_hdr_data)
- for (i = 0; mi->s_hdr_data[i]; i++)
- strbuf_release(mi->s_hdr_data[i]);
- free(mi->s_hdr_data);
+ strbuf_list_free(mi->p_hdr_data);
+ strbuf_list_free(mi->s_hdr_data);
while (mi->content < mi->content_top) {
free(*(mi->content_top));
diff --git a/mailinfo.h b/mailinfo.h
index 79b1d6774e..f2ffd0349e 100644
--- a/mailinfo.h
+++ b/mailinfo.h
@@ -5,6 +5,13 @@
#define MAX_BOUNDARIES 5
+enum quoted_cr_action {
+ quoted_cr_unset = -1,
+ quoted_cr_nowarn,
+ quoted_cr_warn,
+ quoted_cr_strip,
+};
+
struct mailinfo {
FILE *input;
FILE *output;
@@ -14,6 +21,7 @@ struct mailinfo {
struct strbuf email;
int keep_subject;
int keep_non_patch_brackets_in_subject;
+ int quoted_cr; /* enum quoted_cr_action */
int add_message_id;
int use_scissors;
int use_inbody_headers;
@@ -24,6 +32,7 @@ struct mailinfo {
struct strbuf charset;
unsigned int format_flowed:1;
unsigned int delsp:1;
+ unsigned int have_quoted_cr:1;
char *message_id;
enum {
TE_DONTCARE, TE_QP, TE_BASE64
@@ -39,6 +48,7 @@ struct mailinfo {
int input_error;
};
+int mailinfo_parse_quoted_cr_action(const char *actionstr, int *action);
void setup_mailinfo(struct mailinfo *);
int mailinfo(struct mailinfo *, const char *msg, const char *patch);
void clear_mailinfo(struct mailinfo *);
diff --git a/mailmap.c b/mailmap.c
index 9bb9cf8b30..d1f7c0d272 100644
--- a/mailmap.c
+++ b/mailmap.c
@@ -83,7 +83,7 @@ static void add_mapping(struct string_list *map,
if (item->util) {
me = (struct mailmap_entry *)item->util;
} else {
- me = xcalloc(1, sizeof(struct mailmap_entry));
+ CALLOC_ARRAY(me, 1);
me->namemap.strdup_strings = 1;
me->namemap.cmp = namemap_cmp;
item->util = me;
@@ -157,20 +157,30 @@ static void read_mailmap_line(struct string_list *map, char *buffer)
add_mapping(map, name1, email1, name2, email2);
}
-static int read_mailmap_file(struct string_list *map, const char *filename)
+/* Flags for read_mailmap_file() */
+#define MAILMAP_NOFOLLOW (1<<0)
+
+static int read_mailmap_file(struct string_list *map, const char *filename,
+ unsigned flags)
{
char buffer[1024];
FILE *f;
+ int fd;
if (!filename)
return 0;
- f = fopen(filename, "r");
- if (!f) {
+ if (flags & MAILMAP_NOFOLLOW)
+ fd = open_nofollow(filename, O_RDONLY);
+ else
+ fd = open(filename, O_RDONLY);
+
+ if (fd < 0) {
if (errno == ENOENT)
return 0;
return error_errno("unable to open mailmap at %s", filename);
}
+ f = xfdopen(fd, "r");
while (fgets(buffer, sizeof(buffer), f) != NULL)
read_mailmap_line(map, buffer);
@@ -226,10 +236,12 @@ int read_mailmap(struct string_list *map)
git_mailmap_blob = "HEAD:.mailmap";
if (!startup_info->have_repository || !is_bare_repository())
- err |= read_mailmap_file(map, ".mailmap");
+ err |= read_mailmap_file(map, ".mailmap",
+ startup_info->have_repository ?
+ MAILMAP_NOFOLLOW : 0);
if (startup_info->have_repository)
err |= read_mailmap_blob(map, git_mailmap_blob);
- err |= read_mailmap_file(map, git_mailmap_file);
+ err |= read_mailmap_file(map, git_mailmap_file, 0);
return err;
}
diff --git a/match-trees.c b/match-trees.c
index f6c194c1cc..df413989fa 100644
--- a/match-trees.c
+++ b/match-trees.c
@@ -226,7 +226,7 @@ static int splice_tree(const struct object_id *oid1, const char *prefix,
oid_to_hex(oid1));
if (*subpath) {
struct object_id tree_oid;
- hashcpy(tree_oid.hash, rewrite_here);
+ oidread(&tree_oid, rewrite_here);
status = splice_tree(&tree_oid, subpath, oid2, &subtree);
if (status)
return status;
diff --git a/mem-pool.c b/mem-pool.c
index 8401761dda..ccdcad2e3d 100644
--- a/mem-pool.c
+++ b/mem-pool.c
@@ -5,7 +5,7 @@
#include "cache.h"
#include "mem-pool.h"
-#define BLOCK_GROWTH_SIZE 1024*1024 - sizeof(struct mp_block);
+#define BLOCK_GROWTH_SIZE (1024 * 1024 - sizeof(struct mp_block))
/*
* Allocate a new mp_block and insert it after the block specified in
diff --git a/merge-ort-wrappers.c b/merge-ort-wrappers.c
index 7eec25f93a..ad04106169 100644
--- a/merge-ort-wrappers.c
+++ b/merge-ort-wrappers.c
@@ -30,7 +30,7 @@ int merge_ort_nonrecursive(struct merge_options *opt,
return -1;
if (oideq(&merge_base->object.oid, &merge->object.oid)) {
- printf(_("Already up to date!"));
+ printf_ln(_("Already up to date."));
return 1;
}
diff --git a/merge-ort.c b/merge-ort.c
index 603d30c521..4a9ce2a822 100644
--- a/merge-ort.c
+++ b/merge-ort.c
@@ -18,6 +18,7 @@
#include "merge-ort.h"
#include "alloc.h"
+#include "attr.h"
#include "blob.h"
#include "cache-tree.h"
#include "commit.h"
@@ -25,6 +26,7 @@
#include "diff.h"
#include "diffcore.h"
#include "dir.h"
+#include "entry.h"
#include "ll-merge.h"
#include "object-store.h"
#include "revision.h"
@@ -51,6 +53,12 @@ enum merge_side {
MERGE_SIDE2 = 2
};
+struct traversal_callback_data {
+ unsigned long mask;
+ unsigned long dirmask;
+ struct name_entry names[3];
+};
+
struct rename_info {
/*
* All variables that are arrays of size 3 correspond to data tracked
@@ -67,8 +75,12 @@ struct rename_info {
/*
* dirs_removed: directories removed on a given side of history.
+ *
+ * The keys of dirs_removed[side] are the directories that were removed
+ * on the given side of history. The value of the strintmap for each
+ * directory is a value from enum dir_rename_relevance.
*/
- struct strset dirs_removed[3];
+ struct strintmap dirs_removed[3];
/*
* dir_rename_count: tracking where parts of a directory were renamed to
@@ -89,6 +101,46 @@ struct rename_info {
struct strmap dir_renames[3];
/*
+ * relevant_sources: deleted paths wanted in rename detection, and why
+ *
+ * relevant_sources is a set of deleted paths on each side of
+ * history for which we need rename detection. If a path is deleted
+ * on one side of history, we need to detect if it is part of a
+ * rename if either
+ * * the file is modified/deleted on the other side of history
+ * * we need to detect renames for an ancestor directory
+ * If neither of those are true, we can skip rename detection for
+ * that path. The reason is stored as a value from enum
+ * file_rename_relevance, as the reason can inform the algorithm in
+ * diffcore_rename_extended().
+ */
+ struct strintmap relevant_sources[3];
+
+ /*
+ * dir_rename_mask:
+ * 0: optimization removing unmodified potential rename source okay
+ * 2 or 4: optimization okay, but must check for files added to dir
+ * 7: optimization forbidden; need rename source in case of dir rename
+ */
+ unsigned dir_rename_mask:3;
+
+ /*
+ * callback_data_*: supporting data structures for alternate traversal
+ *
+ * We sometimes need to be able to traverse through all the files
+ * in a given tree before all immediate subdirectories within that
+ * tree. Since traverse_trees() doesn't do that naturally, we have
+ * a traverse_trees_wrapper() that stores any immediate
+ * subdirectories while traversing files, then traverses the
+ * immediate subdirectories later. These callback_data* variables
+ * store the information for the subdirectories so that we can do
+ * that traversal order.
+ */
+ struct traversal_callback_data *callback_data;
+ int callback_data_nr, callback_data_alloc;
+ char *callback_data_traverse_path;
+
+ /*
* needed_limit: value needed for inexact rename detection to run
*
* If the current rename limit wasn't high enough for inexact
@@ -171,6 +223,16 @@ struct merge_options_internal {
struct rename_info renames;
/*
+ * attr_index: hacky minimal index used for renormalization
+ *
+ * renormalization code _requires_ an index, though it only needs to
+ * find a .gitattributes file within the index. So, when
+ * renormalization is important, we create a special index with just
+ * that one file.
+ */
+ struct index_state attr_index;
+
+ /*
* current_dir_name, toplevel_dir: temporary vars
*
* These are used in collect_merge_info_callback(), and will set the
@@ -318,8 +380,8 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,
int i;
void (*strmap_func)(struct strmap *, int) =
reinitialize ? strmap_partial_clear : strmap_clear;
- void (*strset_func)(struct strset *) =
- reinitialize ? strset_partial_clear : strset_clear;
+ void (*strintmap_func)(struct strintmap *) =
+ reinitialize ? strintmap_partial_clear : strintmap_clear;
/*
* We marked opti->paths with strdup_strings = 0, so that we
@@ -349,21 +411,20 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,
string_list_clear(&opti->paths_to_free, 0);
opti->paths_to_free.strdup_strings = 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) {
- struct hashmap_iter iter;
- struct strmap_entry *entry;
-
- strset_func(&renames->dirs_removed[i]);
+ strintmap_func(&renames->dirs_removed[i]);
- strmap_for_each_entry(&renames->dir_rename_count[i],
- &iter, entry) {
- struct strintmap *counts = entry->value;
- strintmap_clear(counts);
- }
- strmap_func(&renames->dir_rename_count[i], 1);
+ partial_clear_dir_rename_count(&renames->dir_rename_count[i]);
+ if (!reinitialize)
+ strmap_clear(&renames->dir_rename_count[i], 1);
strmap_func(&renames->dir_renames[i], 0);
+
+ strintmap_func(&renames->relevant_sources[i]);
}
if (!reinitialize) {
@@ -386,6 +447,12 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,
}
strmap_clear(&opti->output, 0);
}
+
+ renames->dir_rename_mask = 0;
+
+ /* Clean out callback_data as well. */
+ FREE_AND_NULL(renames->callback_data);
+ renames->callback_data_nr = renames->callback_data_alloc = 0;
}
static int err(struct merge_options *opt, const char *err, ...)
@@ -476,6 +543,82 @@ static char *unique_path(struct strmap *existing_paths,
/*** Function Grouping: functions related to collect_merge_info() ***/
+static int traverse_trees_wrapper_callback(int n,
+ unsigned long mask,
+ unsigned long dirmask,
+ struct name_entry *names,
+ struct traverse_info *info)
+{
+ struct merge_options *opt = info->data;
+ struct rename_info *renames = &opt->priv->renames;
+ unsigned filemask = mask & ~dirmask;
+
+ assert(n==3);
+
+ if (!renames->callback_data_traverse_path)
+ renames->callback_data_traverse_path = xstrdup(info->traverse_path);
+
+ if (filemask && filemask == renames->dir_rename_mask)
+ renames->dir_rename_mask = 0x07;
+
+ ALLOC_GROW(renames->callback_data, renames->callback_data_nr + 1,
+ renames->callback_data_alloc);
+ renames->callback_data[renames->callback_data_nr].mask = mask;
+ renames->callback_data[renames->callback_data_nr].dirmask = dirmask;
+ COPY_ARRAY(renames->callback_data[renames->callback_data_nr].names,
+ names, 3);
+ renames->callback_data_nr++;
+
+ return mask;
+}
+
+/*
+ * Much like traverse_trees(), BUT:
+ * - read all the tree entries FIRST, saving them
+ * - note that the above step provides an opportunity to compute necessary
+ * additional details before the "real" traversal
+ * - loop through the saved entries and call the original callback on them
+ */
+static int traverse_trees_wrapper(struct index_state *istate,
+ int n,
+ struct tree_desc *t,
+ struct traverse_info *info)
+{
+ int ret, i, old_offset;
+ traverse_callback_t old_fn;
+ char *old_callback_data_traverse_path;
+ struct merge_options *opt = info->data;
+ struct rename_info *renames = &opt->priv->renames;
+
+ assert(renames->dir_rename_mask == 2 || renames->dir_rename_mask == 4);
+
+ old_callback_data_traverse_path = renames->callback_data_traverse_path;
+ old_fn = info->fn;
+ old_offset = renames->callback_data_nr;
+
+ renames->callback_data_traverse_path = NULL;
+ info->fn = traverse_trees_wrapper_callback;
+ ret = traverse_trees(istate, n, t, info);
+ if (ret < 0)
+ return ret;
+
+ info->traverse_path = renames->callback_data_traverse_path;
+ info->fn = old_fn;
+ for (i = old_offset; i < renames->callback_data_nr; ++i) {
+ info->fn(n,
+ renames->callback_data[i].mask,
+ renames->callback_data[i].dirmask,
+ renames->callback_data[i].names,
+ info);
+ }
+
+ renames->callback_data_nr = old_offset;
+ free(renames->callback_data_traverse_path);
+ renames->callback_data_traverse_path = old_callback_data_traverse_path;
+ info->traverse_path = NULL;
+ return 0;
+}
+
static void setup_path_info(struct merge_options *opt,
struct string_list_item *result,
const char *current_dir_name,
@@ -539,12 +682,25 @@ static void add_pair(struct merge_options *opt,
struct name_entry *names,
const char *pathname,
unsigned side,
- unsigned is_add /* if false, is_delete */)
+ unsigned is_add /* if false, is_delete */,
+ unsigned match_mask,
+ unsigned dir_rename_mask)
{
struct diff_filespec *one, *two;
struct rename_info *renames = &opt->priv->renames;
int names_idx = is_add ? side : 0;
+ if (!is_add) {
+ unsigned content_relevant = (match_mask == 0);
+ unsigned location_relevant = (dir_rename_mask == 0x07);
+
+ if (content_relevant || location_relevant) {
+ /* content_relevant trumps location_relevant */
+ strintmap_set(&renames->relevant_sources[side], pathname,
+ content_relevant ? RELEVANT_CONTENT : RELEVANT_LOCATION);
+ }
+ }
+
one = alloc_filespec(pathname);
two = alloc_filespec(pathname);
fill_filespec(is_add ? two : one,
@@ -563,14 +719,75 @@ static void collect_rename_info(struct merge_options *opt,
struct rename_info *renames = &opt->priv->renames;
unsigned side;
+ /*
+ * Update dir_rename_mask (determines ignore-rename-source validity)
+ *
+ * dir_rename_mask helps us keep track of when directory rename
+ * detection may be relevant. Basically, whenver a directory is
+ * removed on one side of history, and a file is added to that
+ * directory on the other side of history, directory rename
+ * detection is relevant (meaning we have to detect renames for all
+ * files within that directory to deduce where the directory
+ * moved). Also, whenever a directory needs directory rename
+ * detection, due to the "majority rules" choice for where to move
+ * it (see t6423 testcase 1f), we also need to detect renames for
+ * all files within subdirectories of that directory as well.
+ *
+ * Here we haven't looked at files within the directory yet, we are
+ * just looking at the directory itself. So, if we aren't yet in
+ * a case where a parent directory needed directory rename detection
+ * (i.e. dir_rename_mask != 0x07), and if the directory was removed
+ * on one side of history, record the mask of the other side of
+ * history in dir_rename_mask.
+ */
+ if (renames->dir_rename_mask != 0x07 &&
+ (dirmask == 3 || dirmask == 5)) {
+ /* simple sanity check */
+ assert(renames->dir_rename_mask == 0 ||
+ renames->dir_rename_mask == (dirmask & ~1));
+ /* update dir_rename_mask; have it record mask of new side */
+ renames->dir_rename_mask = (dirmask & ~1);
+ }
+
/* Update dirs_removed, as needed */
if (dirmask == 1 || dirmask == 3 || dirmask == 5) {
/* absent_mask = 0x07 - dirmask; sides = absent_mask/2 */
unsigned sides = (0x07 - dirmask)/2;
+ unsigned relevance = (renames->dir_rename_mask == 0x07) ?
+ RELEVANT_FOR_ANCESTOR : NOT_RELEVANT;
+ /*
+ * Record relevance of this directory. However, note that
+ * when collect_merge_info_callback() recurses into this
+ * directory and calls collect_rename_info() on paths
+ * within that directory, if we find a path that was added
+ * to this directory on the other side of history, we will
+ * upgrade this value to RELEVANT_FOR_SELF; see below.
+ */
if (sides & 1)
- strset_add(&renames->dirs_removed[1], fullname);
+ strintmap_set(&renames->dirs_removed[1], fullname,
+ relevance);
if (sides & 2)
- strset_add(&renames->dirs_removed[2], fullname);
+ strintmap_set(&renames->dirs_removed[2], fullname,
+ relevance);
+ }
+
+ /*
+ * Here's the block that potentially upgrades to RELEVANT_FOR_SELF.
+ * When we run across a file added to a directory. In such a case,
+ * find the directory of the file and upgrade its relevance.
+ */
+ if (renames->dir_rename_mask == 0x07 &&
+ (filemask == 2 || filemask == 4)) {
+ /*
+ * Need directory rename for parent directory on other side
+ * of history from added file. Thus
+ * side = (~filemask & 0x06) >> 1
+ * or
+ * side = 3 - (filemask/2).
+ */
+ unsigned side = 3 - (filemask >> 1);
+ strintmap_set(&renames->dirs_removed[side], dirname,
+ RELEVANT_FOR_SELF);
}
if (filemask == 0 || filemask == 7)
@@ -581,11 +798,15 @@ static void collect_rename_info(struct merge_options *opt,
/* Check for deletion on side */
if ((filemask & 1) && !(filemask & side_mask))
- add_pair(opt, names, fullname, side, 0 /* delete */);
+ add_pair(opt, names, fullname, side, 0 /* delete */,
+ match_mask & filemask,
+ renames->dir_rename_mask);
/* Check for addition on side */
if (!(filemask & 1) && (filemask & side_mask))
- add_pair(opt, names, fullname, side, 1 /* add */);
+ add_pair(opt, names, fullname, side, 1 /* add */,
+ match_mask & filemask,
+ renames->dir_rename_mask);
}
}
@@ -603,12 +824,14 @@ static int collect_merge_info_callback(int n,
*/
struct merge_options *opt = info->data;
struct merge_options_internal *opti = opt->priv;
+ struct rename_info *renames = &opt->priv->renames;
struct string_list_item pi; /* Path Info */
struct conflict_info *ci; /* typed alias to pi.util (which is void*) */
struct name_entry *p;
size_t len;
char *fullpath;
const char *dirname = opti->current_dir_name;
+ unsigned prev_dir_rename_mask = renames->dir_rename_mask;
unsigned filemask = mask & ~dirmask;
unsigned match_mask = 0; /* will be updated below */
unsigned mbase_null = !(mask & 1);
@@ -749,8 +972,13 @@ static int collect_merge_info_callback(int n,
original_dir_name = opti->current_dir_name;
opti->current_dir_name = pi.string;
- ret = traverse_trees(NULL, 3, t, &newinfo);
+ if (renames->dir_rename_mask == 0 ||
+ renames->dir_rename_mask == 0x07)
+ ret = traverse_trees(NULL, 3, t, &newinfo);
+ else
+ ret = traverse_trees_wrapper(NULL, 3, t, &newinfo);
opti->current_dir_name = original_dir_name;
+ renames->dir_rename_mask = prev_dir_rename_mask;
for (i = MERGE_BASE; i <= MERGE_SIDE2; i++)
free(buf[i]);
@@ -974,6 +1202,63 @@ static int merge_submodule(struct merge_options *opt,
return 0;
}
+static void initialize_attr_index(struct merge_options *opt)
+{
+ /*
+ * The renormalize_buffer() functions require attributes, and
+ * annoyingly those can only be read from the working tree or from
+ * an index_state. merge-ort doesn't have an index_state, so we
+ * generate a fake one containing only attribute information.
+ */
+ struct merged_info *mi;
+ struct index_state *attr_index = &opt->priv->attr_index;
+ struct cache_entry *ce;
+
+ attr_index->initialized = 1;
+
+ if (!opt->renormalize)
+ return;
+
+ mi = strmap_get(&opt->priv->paths, GITATTRIBUTES_FILE);
+ if (!mi)
+ return;
+
+ if (mi->clean) {
+ int len = strlen(GITATTRIBUTES_FILE);
+ ce = make_empty_cache_entry(attr_index, len);
+ ce->ce_mode = create_ce_mode(mi->result.mode);
+ ce->ce_flags = create_ce_flags(0);
+ ce->ce_namelen = len;
+ oidcpy(&ce->oid, &mi->result.oid);
+ memcpy(ce->name, GITATTRIBUTES_FILE, len);
+ add_index_entry(attr_index, ce,
+ ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
+ get_stream_filter(attr_index, GITATTRIBUTES_FILE, &ce->oid);
+ } else {
+ int stage, len;
+ struct conflict_info *ci;
+
+ ASSIGN_AND_VERIFY_CI(ci, mi);
+ for (stage = 0; stage < 3; stage++) {
+ unsigned stage_mask = (1 << stage);
+
+ if (!(ci->filemask & stage_mask))
+ continue;
+ len = strlen(GITATTRIBUTES_FILE);
+ ce = make_empty_cache_entry(attr_index, len);
+ ce->ce_mode = create_ce_mode(ci->stages[stage].mode);
+ ce->ce_flags = create_ce_flags(stage);
+ ce->ce_namelen = len;
+ oidcpy(&ce->oid, &ci->stages[stage].oid);
+ memcpy(ce->name, GITATTRIBUTES_FILE, len);
+ add_index_entry(attr_index, ce,
+ ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
+ get_stream_filter(attr_index, GITATTRIBUTES_FILE,
+ &ce->oid);
+ }
+ }
+}
+
static int merge_3way(struct merge_options *opt,
const char *path,
const struct object_id *o,
@@ -988,6 +1273,9 @@ static int merge_3way(struct merge_options *opt,
char *base, *name1, *name2;
int merge_status;
+ if (!opt->priv->attr_index.initialized)
+ initialize_attr_index(opt);
+
ll_opts.renormalize = opt->renormalize;
ll_opts.extra_marker_size = extra_marker_size;
ll_opts.xdl_opts = opt->xdl_opts;
@@ -1026,7 +1314,7 @@ static int merge_3way(struct merge_options *opt,
merge_status = ll_merge(result_buf, path, &orig, base,
&src1, name1, &src2, name2,
- opt->repo->index, &ll_opts);
+ &opt->priv->attr_index, &ll_opts);
free(base);
free(name1);
@@ -1118,7 +1406,7 @@ static int handle_content_merge(struct merge_options *opt,
two_way = ((S_IFMT & o->mode) != (S_IFMT & a->mode));
merge_status = merge_3way(opt, path,
- two_way ? &null_oid : &o->oid,
+ two_way ? null_oid() : &o->oid,
&a->oid, &b->oid,
pathnames, extra_marker_size,
&result_buf);
@@ -1140,7 +1428,7 @@ static int handle_content_merge(struct merge_options *opt,
} else if (S_ISGITLINK(a->mode)) {
int two_way = ((S_IFMT & o->mode) != (S_IFMT & a->mode));
clean = merge_submodule(opt, pathnames[0],
- two_way ? &null_oid : &o->oid,
+ two_way ? null_oid() : &o->oid,
&a->oid, &b->oid, &result->oid);
if (opt->priv->call_depth && two_way && !clean) {
result->mode = o->mode;
@@ -1302,131 +1590,6 @@ static char *handle_path_level_conflicts(struct merge_options *opt,
return new_path;
}
-static void dirname_munge(char *filename)
-{
- char *slash = strrchr(filename, '/');
- if (!slash)
- slash = filename;
- *slash = '\0';
-}
-
-static void increment_count(struct strmap *dir_rename_count,
- char *old_dir,
- char *new_dir)
-{
- struct strintmap *counts;
- struct strmap_entry *e;
-
- /* Get the {new_dirs -> counts} mapping using old_dir */
- e = strmap_get_entry(dir_rename_count, old_dir);
- if (e) {
- counts = e->value;
- } else {
- counts = xmalloc(sizeof(*counts));
- strintmap_init_with_options(counts, 0, NULL, 1);
- strmap_put(dir_rename_count, old_dir, counts);
- }
-
- /* Increment the count for new_dir */
- strintmap_incr(counts, new_dir, 1);
-}
-
-static void update_dir_rename_counts(struct strmap *dir_rename_count,
- struct strset *dirs_removed,
- const char *oldname,
- const char *newname)
-{
- char *old_dir = xstrdup(oldname);
- char *new_dir = xstrdup(newname);
- char new_dir_first_char = new_dir[0];
- int first_time_in_loop = 1;
-
- while (1) {
- dirname_munge(old_dir);
- dirname_munge(new_dir);
-
- /*
- * When renaming
- * "a/b/c/d/e/foo.c" -> "a/b/some/thing/else/e/foo.c"
- * then this suggests that both
- * a/b/c/d/e/ => a/b/some/thing/else/e/
- * a/b/c/d/ => a/b/some/thing/else/
- * so we want to increment counters for both. We do NOT,
- * however, also want to suggest that there was the following
- * rename:
- * a/b/c/ => a/b/some/thing/
- * so we need to quit at that point.
- *
- * Note the when first_time_in_loop, we only strip off the
- * basename, and we don't care if that's different.
- */
- if (!first_time_in_loop) {
- char *old_sub_dir = strchr(old_dir, '\0')+1;
- char *new_sub_dir = strchr(new_dir, '\0')+1;
- if (!*new_dir) {
- /*
- * Special case when renaming to root directory,
- * i.e. when new_dir == "". In this case, we had
- * something like
- * a/b/subdir => subdir
- * and so dirname_munge() sets things up so that
- * old_dir = "a/b\0subdir\0"
- * new_dir = "\0ubdir\0"
- * We didn't have a '/' to overwrite a '\0' onto
- * in new_dir, so we have to compare differently.
- */
- if (new_dir_first_char != old_sub_dir[0] ||
- strcmp(old_sub_dir+1, new_sub_dir))
- break;
- } else {
- if (strcmp(old_sub_dir, new_sub_dir))
- break;
- }
- }
-
- if (strset_contains(dirs_removed, old_dir))
- increment_count(dir_rename_count, old_dir, new_dir);
- else
- break;
-
- /* If we hit toplevel directory ("") for old or new dir, quit */
- if (!*old_dir || !*new_dir)
- break;
-
- first_time_in_loop = 0;
- }
-
- /* Free resources we don't need anymore */
- free(old_dir);
- free(new_dir);
-}
-
-static void compute_rename_counts(struct diff_queue_struct *pairs,
- struct strmap *dir_rename_count,
- struct strset *dirs_removed)
-{
- int i;
-
- for (i = 0; i < pairs->nr; ++i) {
- struct diff_filepair *pair = pairs->queue[i];
-
- /* File not part of directory rename if it wasn't renamed */
- if (pair->status != 'R')
- continue;
-
- /*
- * Make dir_rename_count contain a map of a map:
- * old_directory -> {new_directory -> count}
- * In other words, for every pair look at the directories for
- * the old filename and the new filename and count how many
- * times that pairing occurs.
- */
- update_dir_rename_counts(dir_rename_count, dirs_removed,
- pair->one->path,
- pair->two->path);
- }
-}
-
static void get_provisional_directory_renames(struct merge_options *opt,
unsigned side,
int *clean)
@@ -1435,9 +1598,6 @@ static void get_provisional_directory_renames(struct merge_options *opt,
struct strmap_entry *entry;
struct rename_info *renames = &opt->priv->renames;
- compute_rename_counts(&renames->pairs[side],
- &renames->dir_rename_count[side],
- &renames->dirs_removed[side]);
/*
* Collapse
* dir_rename_count: old_directory -> {new_directory -> count}
@@ -1466,6 +1626,9 @@ static void get_provisional_directory_renames(struct merge_options *opt,
}
}
+ if (max == 0)
+ continue;
+
if (bad_max == max) {
path_msg(opt, source_dir, 0,
_("CONFLICT (directory rename split): "
@@ -1474,18 +1637,7 @@ static void get_provisional_directory_renames(struct merge_options *opt,
"no destination getting a majority of the "
"files."),
source_dir);
- /*
- * We should mark this as unclean IF something attempts
- * to use this rename. We do not yet have the logic
- * in place to detect if this directory rename is being
- * used, and optimizations that reduce the number of
- * renames cause this to falsely trigger. For now,
- * just disable it, causing t6423 testcase 2a to break.
- * We'll later fix the detection, and when we do we
- * will re-enable setting *clean to 0 (and thereby fix
- * t6423 testcase 2a).
- */
- /* *clean = 0; */
+ *clean = 0;
} else {
strmap_put(&renames->dir_renames[side],
source_dir, (void*)best);
@@ -1576,8 +1728,7 @@ static void compute_collisions(struct strmap *collisions,
if (collision_info) {
free(new_path);
} else {
- collision_info = xcalloc(1,
- sizeof(struct collision_info));
+ CALLOC_ARRAY(collision_info, 1);
string_list_init(&collision_info->source_files, 0);
strmap_put(collisions, new_path, collision_info);
}
@@ -1718,7 +1869,7 @@ static void apply_directory_rename_modifications(struct merge_options *opt,
struct conflict_info *dir_ci;
char *cur_dir = dirs_to_insert.items[i].string;
- dir_ci = xcalloc(1, sizeof(*dir_ci));
+ CALLOC_ARRAY(dir_ci, 1);
dir_ci->merged.directory_name = parent_name;
len = strlen(parent_name);
@@ -2079,7 +2230,7 @@ static int process_renames(struct merge_options *opt,
if (type_changed) {
/* rename vs. typechange */
/* Mark the original as resolved by removal */
- memcpy(&oldinfo->stages[0].oid, &null_oid,
+ memcpy(&oldinfo->stages[0].oid, null_oid(),
sizeof(oldinfo->stages[0].oid));
oldinfo->stages[0].mode = 0;
oldinfo->filemask &= 0x06;
@@ -2112,6 +2263,19 @@ static int process_renames(struct merge_options *opt,
return clean_merge;
}
+static inline int possible_side_renames(struct rename_info *renames,
+ unsigned side_index)
+{
+ return renames->pairs[side_index].nr > 0 &&
+ !strintmap_empty(&renames->relevant_sources[side_index]);
+}
+
+static inline int possible_renames(struct rename_info *renames)
+{
+ return possible_side_renames(renames, 1) ||
+ possible_side_renames(renames, 2);
+}
+
static void resolve_diffpair_statuses(struct diff_queue_struct *q)
{
/*
@@ -2148,6 +2312,16 @@ static void detect_regular_renames(struct merge_options *opt,
struct diff_options diff_opts;
struct rename_info *renames = &opt->priv->renames;
+ if (!possible_side_renames(renames, side_index)) {
+ /*
+ * No rename detection needed for this side, but we still need
+ * to make sure 'adds' are marked correctly in case the other
+ * side had directory renames.
+ */
+ resolve_diffpair_statuses(&renames->pairs[side_index]);
+ return;
+ }
+
repo_diff_setup(opt->repo, &diff_opts);
diff_opts.flags.recursive = 1;
diff_opts.flags.rename_empty = 0;
@@ -2162,7 +2336,10 @@ static void detect_regular_renames(struct merge_options *opt,
diff_queued_diff = renames->pairs[side_index];
trace2_region_enter("diff", "diffcore_rename", opt->repo);
- diffcore_rename(&diff_opts);
+ diffcore_rename_extended(&diff_opts,
+ &renames->relevant_sources[side_index],
+ &renames->dirs_removed[side_index],
+ &renames->dir_rename_count[side_index]);
trace2_region_leave("diff", "diffcore_rename", opt->repo);
resolve_diffpair_statuses(&diff_queued_diff);
@@ -2262,6 +2439,8 @@ static int detect_and_process_renames(struct merge_options *opt,
int need_dir_renames, s, clean = 1;
memset(&combined, 0, sizeof(combined));
+ if (!possible_renames(renames))
+ goto cleanup;
trace2_region_enter("merge", "regular renames", opt->repo);
detect_regular_renames(opt, MERGE_SIDE1);
@@ -2289,13 +2468,32 @@ static int detect_and_process_renames(struct merge_options *opt,
clean &= collect_renames(opt, &combined, MERGE_SIDE2,
&renames->dir_renames[1],
&renames->dir_renames[2]);
- QSORT(combined.queue, combined.nr, compare_pairs);
+ STABLE_QSORT(combined.queue, combined.nr, compare_pairs);
trace2_region_leave("merge", "directory renames", opt->repo);
trace2_region_enter("merge", "process renames", opt->repo);
clean &= process_renames(opt, &combined);
trace2_region_leave("merge", "process renames", opt->repo);
+ goto simple_cleanup; /* collect_renames() handles some of cleanup */
+
+cleanup:
+ /*
+ * Free now unneeded filepairs, which would have been handled
+ * in collect_renames() normally but we skipped that code.
+ */
+ for (s = MERGE_SIDE1; s <= MERGE_SIDE2; s++) {
+ struct diff_queue_struct *side_pairs;
+ int i;
+
+ 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);
+ }
+ }
+
+simple_cleanup:
/* Free memory for renames->pairs[] and combined */
for (s = MERGE_SIDE1; s <= MERGE_SIDE2; s++) {
free(renames->pairs[s].queue);
@@ -2340,6 +2538,61 @@ static int string_list_df_name_compare(const char *one, const char *two)
return onelen - twolen;
}
+static int read_oid_strbuf(struct merge_options *opt,
+ const struct object_id *oid,
+ struct strbuf *dst)
+{
+ void *buf;
+ enum object_type type;
+ unsigned long size;
+ buf = read_object_file(oid, &type, &size);
+ if (!buf)
+ return err(opt, _("cannot read object %s"), oid_to_hex(oid));
+ if (type != OBJ_BLOB) {
+ free(buf);
+ return err(opt, _("object %s is not a blob"), oid_to_hex(oid));
+ }
+ strbuf_attach(dst, buf, size, size + 1);
+ return 0;
+}
+
+static int blob_unchanged(struct merge_options *opt,
+ const struct version_info *base,
+ const struct version_info *side,
+ const char *path)
+{
+ struct strbuf basebuf = STRBUF_INIT;
+ struct strbuf sidebuf = STRBUF_INIT;
+ int ret = 0; /* assume changed for safety */
+ struct index_state *idx = &opt->priv->attr_index;
+
+ if (!idx->initialized)
+ initialize_attr_index(opt);
+
+ if (base->mode != side->mode)
+ return 0;
+ if (oideq(&base->oid, &side->oid))
+ return 1;
+
+ if (read_oid_strbuf(opt, &base->oid, &basebuf) ||
+ read_oid_strbuf(opt, &side->oid, &sidebuf))
+ goto error_return;
+ /*
+ * Note: binary | is used so that both renormalizations are
+ * performed. Comparison can be skipped if both files are
+ * unchanged since their sha1s have already been compared.
+ */
+ if (renormalize_buffer(idx, path, basebuf.buf, basebuf.len, &basebuf) |
+ renormalize_buffer(idx, path, sidebuf.buf, sidebuf.len, &sidebuf))
+ ret = (basebuf.len == sidebuf.len &&
+ !memcmp(basebuf.buf, sidebuf.buf, basebuf.len));
+
+error_return:
+ strbuf_release(&basebuf);
+ strbuf_release(&sidebuf);
+ return ret;
+}
+
struct directory_versions {
/*
* versions: list of (basename -> version_info)
@@ -2400,22 +2653,15 @@ static void write_tree(struct object_id *result_oid,
size_t hash_size)
{
size_t maxlen = 0, extra;
- unsigned int nr = versions->nr - offset;
+ unsigned int nr;
struct strbuf buf = STRBUF_INIT;
- struct string_list relevant_entries = STRING_LIST_INIT_NODUP;
int i;
- /*
- * We want to sort the last (versions->nr-offset) entries in versions.
- * Do so by abusing the string_list API a bit: make another string_list
- * that contains just those entries and then sort them.
- *
- * We won't use relevant_entries again and will let it just pop off the
- * stack, so there won't be allocation worries or anything.
- */
- relevant_entries.items = versions->items + offset;
- relevant_entries.nr = versions->nr - offset;
- QSORT(relevant_entries.items, relevant_entries.nr, tree_entry_order);
+ assert(offset <= versions->nr);
+ nr = versions->nr - offset;
+ if (versions->nr)
+ /* No need for STABLE_QSORT -- filenames must be unique */
+ QSORT(versions->items + offset, nr, tree_entry_order);
/* Pre-allocate some space in buf */
extra = hash_size + 8; /* 8: 6 for mode, 1 for space, 1 for NUL char */
@@ -2671,7 +2917,7 @@ static void process_entry(struct merge_options *opt,
if (ci->filemask & (1 << i))
continue;
ci->stages[i].mode = 0;
- oidcpy(&ci->stages[i].oid, &null_oid);
+ oidcpy(&ci->stages[i].oid, null_oid());
}
} else if (ci->df_conflict && ci->merged.result.mode != 0) {
/*
@@ -2703,7 +2949,7 @@ static void process_entry(struct merge_options *opt,
* the directory to remain here, so we need to move this
* path to some new location.
*/
- new_ci = xcalloc(1, sizeof(*new_ci));
+ CALLOC_ARRAY(new_ci, 1);
/* 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
@@ -2717,7 +2963,7 @@ static void process_entry(struct merge_options *opt,
continue;
/* zero out any entries related to directories */
new_ci->stages[i].mode = 0;
- oidcpy(&new_ci->stages[i].oid, &null_oid);
+ oidcpy(&new_ci->stages[i].oid, null_oid());
}
/*
@@ -2804,12 +3050,21 @@ static void process_entry(struct merge_options *opt,
rename_b = 1;
}
- path_msg(opt, path, 0,
- _("CONFLICT (distinct types): %s had different "
- "types on each side; renamed %s of them so "
- "each can be recorded somewhere."),
- path,
- (rename_a && rename_b) ? _("both") : _("one"));
+ if (rename_a && rename_b) {
+ path_msg(opt, path, 0,
+ _("CONFLICT (distinct types): %s had "
+ "different types on each side; "
+ "renamed both of them so each can "
+ "be recorded somewhere."),
+ path);
+ } else {
+ path_msg(opt, path, 0,
+ _("CONFLICT (distinct types): %s had "
+ "different types on each side; "
+ "renamed one of them so each can be "
+ "recorded somewhere."),
+ path);
+ }
ci->merged.clean = 0;
memcpy(new_ci, ci, sizeof(*new_ci));
@@ -2818,11 +3073,11 @@ static void process_entry(struct merge_options *opt,
new_ci->merged.result.mode = ci->stages[2].mode;
oidcpy(&new_ci->merged.result.oid, &ci->stages[2].oid);
new_ci->stages[1].mode = 0;
- oidcpy(&new_ci->stages[1].oid, &null_oid);
+ oidcpy(&new_ci->stages[1].oid, null_oid());
new_ci->filemask = 5;
if ((S_IFMT & b_mode) != (S_IFMT & o_mode)) {
new_ci->stages[0].mode = 0;
- oidcpy(&new_ci->stages[0].oid, &null_oid);
+ oidcpy(&new_ci->stages[0].oid, null_oid());
new_ci->filemask = 4;
}
@@ -2830,11 +3085,11 @@ static void process_entry(struct merge_options *opt,
ci->merged.result.mode = ci->stages[1].mode;
oidcpy(&ci->merged.result.oid, &ci->stages[1].oid);
ci->stages[2].mode = 0;
- oidcpy(&ci->stages[2].oid, &null_oid);
+ oidcpy(&ci->stages[2].oid, null_oid());
ci->filemask = 3;
if ((S_IFMT & a_mode) != (S_IFMT & o_mode)) {
ci->stages[0].mode = 0;
- oidcpy(&ci->stages[0].oid, &null_oid);
+ oidcpy(&ci->stages[0].oid, null_oid());
ci->filemask = 2;
}
@@ -2926,8 +3181,13 @@ static void process_entry(struct merge_options *opt,
modify_branch = (side == 1) ? opt->branch1 : opt->branch2;
delete_branch = (side == 1) ? opt->branch2 : opt->branch1;
- if (ci->path_conflict &&
- oideq(&ci->stages[0].oid, &ci->stages[side].oid)) {
+ if (opt->renormalize &&
+ blob_unchanged(opt, &ci->stages[0], &ci->stages[side],
+ path)) {
+ ci->merged.is_null = 1;
+ ci->merged.clean = 1;
+ } else if (ci->path_conflict &&
+ oideq(&ci->stages[0].oid, &ci->stages[side].oid)) {
/*
* This came from a rename/delete; no action to take,
* but avoid printing "modify/delete" conflict notice
@@ -2951,7 +3211,7 @@ static void process_entry(struct merge_options *opt,
/* Deleted on both sides */
ci->merged.is_null = 1;
ci->merged.result.mode = 0;
- oidcpy(&ci->merged.result.oid, &null_oid);
+ oidcpy(&ci->merged.result.oid, null_oid());
ci->merged.clean = !ci->path_conflict;
}
@@ -3083,7 +3343,7 @@ static int checkout(struct merge_options *opt,
unpack_opts.verbose_update = (opt->verbosity > 2);
unpack_opts.fn = twoway_merge;
if (1/* FIXME: opts->overwrite_ignore*/) {
- unpack_opts.dir = xcalloc(1, sizeof(*unpack_opts.dir));
+ CALLOC_ARRAY(unpack_opts.dir, 1);
unpack_opts.dir->flags |= DIR_SHOW_IGNORED;
setup_standard_excludes(unpack_opts.dir);
}
@@ -3099,23 +3359,27 @@ static int checkout(struct merge_options *opt,
return ret;
}
-static int record_conflicted_index_entries(struct merge_options *opt,
- struct index_state *index,
- struct strmap *paths,
- struct strmap *conflicted)
+static int record_conflicted_index_entries(struct merge_options *opt)
{
struct hashmap_iter iter;
struct strmap_entry *e;
+ struct index_state *index = opt->repo->index;
+ struct checkout state = CHECKOUT_INIT;
int errs = 0;
int original_cache_nr;
- if (strmap_empty(conflicted))
+ if (strmap_empty(&opt->priv->conflicted))
return 0;
+ /* If any entries have skip_worktree set, we'll have to check 'em out */
+ state.force = 1;
+ state.quiet = 1;
+ state.refresh_cache = 1;
+ state.istate = index;
original_cache_nr = index->cache_nr;
/* Put every entry from paths into plist, then sort */
- strmap_for_each_entry(conflicted, &iter, e) {
+ strmap_for_each_entry(&opt->priv->conflicted, &iter, e) {
const char *path = e->key;
struct conflict_info *ci = e->value;
int pos;
@@ -3156,9 +3420,23 @@ static int record_conflicted_index_entries(struct merge_options *opt,
* the higher order stages. Thus, we need override
* the CE_SKIP_WORKTREE bit and manually write those
* files to the working disk here.
- *
- * TODO: Implement this CE_SKIP_WORKTREE fixup.
*/
+ if (ce_skip_worktree(ce)) {
+ struct stat st;
+
+ if (!lstat(path, &st)) {
+ char *new_name = unique_path(&opt->priv->paths,
+ path,
+ "cruft");
+
+ path_msg(opt, path, 1,
+ _("Note: %s not up to date and in way of checking out conflicted version; old copy renamed to %s"),
+ path, new_name);
+ errs |= rename(path, new_name);
+ free(new_name);
+ }
+ errs |= checkout_entry(ce, &state, NULL, NULL);
+ }
/*
* Mark this cache entry for removal and instead add
@@ -3190,6 +3468,11 @@ static int record_conflicted_index_entries(struct merge_options *opt,
* entries we added to the end into their right locations.
*/
remove_marked_cache_entries(index, 1);
+ /*
+ * No need for STABLE_QSORT -- cmp_cache_name_compare sorts primarily
+ * on filename and secondarily on stage, and (name, stage #) are a
+ * unique tuple.
+ */
QSORT(index->cache, index->cache_nr, cmp_cache_name_compare);
return errs;
@@ -3203,7 +3486,8 @@ void merge_switch_to_result(struct merge_options *opt,
{
assert(opt->priv == NULL);
if (result->clean >= 0 && update_worktree_and_index) {
- struct merge_options_internal *opti = result->priv;
+ const char *filename;
+ FILE *fp;
trace2_region_enter("merge", "checkout", opt->repo);
if (checkout(opt, head, result->tree)) {
@@ -3214,14 +3498,22 @@ void merge_switch_to_result(struct merge_options *opt,
trace2_region_leave("merge", "checkout", opt->repo);
trace2_region_enter("merge", "record_conflicted", opt->repo);
- if (record_conflicted_index_entries(opt, opt->repo->index,
- &opti->paths,
- &opti->conflicted)) {
+ opt->priv = result->priv;
+ if (record_conflicted_index_entries(opt)) {
/* failure to function */
+ opt->priv = NULL;
result->clean = -1;
return;
}
+ opt->priv = NULL;
trace2_region_leave("merge", "record_conflicted", opt->repo);
+
+ trace2_region_enter("merge", "write_auto_merge", opt->repo);
+ filename = git_path_auto_merge(opt->repo);
+ fp = xfopen(filename, "w");
+ fprintf(fp, "%s\n", oid_to_hex(&result->tree->object.oid));
+ fclose(fp);
+ trace2_region_leave("merge", "write_auto_merge", opt->repo);
}
if (display_update_msgs) {
@@ -3266,6 +3558,8 @@ void merge_finalize(struct merge_options *opt,
{
struct merge_options_internal *opti = result->priv;
+ if (opt->renormalize)
+ git_attr_set_direction(GIT_ATTR_CHECKIN);
assert(opt->priv == NULL);
clear_or_reinit_internal_opts(opti, 0);
@@ -3274,6 +3568,23 @@ void merge_finalize(struct merge_options *opt,
/*** Function Grouping: helper functions for merge_incore_*() ***/
+static struct tree *shift_tree_object(struct repository *repo,
+ struct tree *one, struct tree *two,
+ const char *subtree_shift)
+{
+ struct object_id shifted;
+
+ if (!*subtree_shift) {
+ shift_tree(repo, &one->object.oid, &two->object.oid, &shifted, 0);
+ } else {
+ shift_tree_by(repo, &one->object.oid, &two->object.oid, &shifted,
+ subtree_shift);
+ }
+ if (oideq(&two->object.oid, &shifted))
+ return two;
+ return lookup_tree(repo, &shifted);
+}
+
static inline void set_commit_tree(struct commit *c, struct tree *t)
{
c->maybe_tree = t;
@@ -3341,6 +3652,10 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)
/* Default to histogram diff. Actually, just hardcode it...for now. */
opt->xdl_opts = DIFF_WITH_ALG(opt, HISTOGRAM_DIFF);
+ /* Handle attr direction stuff for renormalization */
+ if (opt->renormalize)
+ git_attr_set_direction(GIT_ATTR_CHECKOUT);
+
/* Initialization of opt->priv, our internal merge data */
trace2_region_enter("merge", "allocate/init", opt->repo);
if (opt->priv) {
@@ -3353,12 +3668,14 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)
/* Initialization of various renames fields */
renames = &opt->priv->renames;
for (i = MERGE_SIDE1; i <= MERGE_SIDE2; i++) {
- strset_init_with_options(&renames->dirs_removed[i],
- NULL, 0);
+ strintmap_init_with_options(&renames->dirs_removed[i],
+ NOT_RELEVANT, NULL, 0);
strmap_init_with_options(&renames->dir_rename_count[i],
NULL, 1);
strmap_init_with_options(&renames->dir_renames[i],
NULL, 0);
+ strintmap_init_with_options(&renames->relevant_sources[i],
+ 0, NULL, 0);
}
/*
@@ -3397,6 +3714,13 @@ static void merge_ort_nonrecursive_internal(struct merge_options *opt,
{
struct object_id working_tree_oid;
+ if (opt->subtree_shift) {
+ side2 = shift_tree_object(opt->repo, side1, side2,
+ opt->subtree_shift);
+ merge_base = shift_tree_object(opt->repo, side1, merge_base,
+ opt->subtree_shift);
+ }
+
trace2_region_enter("merge", "collect_merge_info", opt->repo);
if (collect_merge_info(opt, merge_base, side1, side2) != 0) {
/*
diff --git a/merge-recursive.c b/merge-recursive.c
index b052974f19..d146bb116f 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -303,7 +303,7 @@ static inline void setup_rename_conflict_info(enum rename_type rename_type,
return;
}
- ci = xcalloc(1, sizeof(struct rename_conflict_info));
+ CALLOC_ARRAY(ci, 1);
ci->rename_type = rename_type;
ci->ren1 = ren1;
ci->ren2 = ren2;
@@ -453,7 +453,7 @@ static void unpack_trees_finish(struct merge_options *opt)
static int save_files_dirs(const struct object_id *oid,
struct strbuf *base, const char *path,
- unsigned int mode, int stage, void *context)
+ unsigned int mode, void *context)
{
struct path_hashmap_entry *entry;
int baselen = base->len;
@@ -473,8 +473,8 @@ static void get_files_dirs(struct merge_options *opt, struct tree *tree)
{
struct pathspec match_all;
memset(&match_all, 0, sizeof(match_all));
- read_tree_recursive(opt->repo, tree, "", 0, 0,
- &match_all, save_files_dirs, opt);
+ read_tree(opt->repo, tree,
+ &match_all, save_files_dirs, opt);
}
static int get_tree_entry_if_blob(struct repository *r,
@@ -486,7 +486,7 @@ static int get_tree_entry_if_blob(struct repository *r,
ret = get_tree_entry(r, tree, path, &dfs->oid, &dfs->mode);
if (S_ISDIR(dfs->mode)) {
- oidcpy(&dfs->oid, &null_oid);
+ oidcpy(&dfs->oid, null_oid());
dfs->mode = 0;
}
return ret;
@@ -522,6 +522,8 @@ static struct string_list *get_unmerged(struct index_state *istate)
unmerged->strdup_strings = 1;
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(istate);
for (i = 0; i < istate->cache_nr; i++) {
struct string_list_item *item;
struct stage_data *e;
@@ -1075,6 +1077,11 @@ static int merge_3way(struct merge_options *opt,
read_mmblob(&src1, &a->oid);
read_mmblob(&src2, &b->oid);
+ /*
+ * FIXME: Using a->path for normalization rules in ll_merge could be
+ * wrong if we renamed from a->path to b->path. We should use the
+ * target path for where the file will be written.
+ */
merge_status = ll_merge(result_buf, a->path, &orig, base,
&src1, name1, &src2, name2,
opt->repo->index, &ll_opts);
@@ -1154,6 +1161,8 @@ static void print_commit(struct commit *commit)
struct strbuf sb = STRBUF_INIT;
struct pretty_print_context ctx = {0};
ctx.date_mode.type = DATE_NORMAL;
+ /* FIXME: Merge this with output_commit_title() */
+ assert(!merge_remote_util(commit));
format_commit_message(commit, " %h: %m %s", &sb, &ctx);
fprintf(stderr, "%s\n", sb.buf);
strbuf_release(&sb);
@@ -1177,6 +1186,11 @@ static int merge_submodule(struct merge_options *opt,
int search = !opt->priv->call_depth;
/* store a in result in case we fail */
+ /* FIXME: This is the WRONG resolution for the recursive case when
+ * we need to be careful to avoid accidentally matching either side.
+ * Should probably use o instead there, much like we do for merging
+ * binaries.
+ */
oidcpy(result, a);
/* we can not handle deletion conflicts */
@@ -1301,6 +1315,13 @@ static int merge_mode_and_contents(struct merge_options *opt,
if ((S_IFMT & a->mode) != (S_IFMT & b->mode)) {
result->clean = 0;
+ /*
+ * FIXME: This is a bad resolution for recursive case; for
+ * the recursive case we want something that is unlikely to
+ * accidentally match either side. Also, while it makes
+ * sense to prefer regular files over symlinks, it doesn't
+ * make sense to prefer regular files over submodules.
+ */
if (S_ISREG(a->mode)) {
result->blob.mode = a->mode;
oidcpy(&result->blob.oid, &a->oid);
@@ -1349,6 +1370,7 @@ static int merge_mode_and_contents(struct merge_options *opt,
free(result_buf.ptr);
if (ret)
return ret;
+ /* FIXME: bug, what if modes didn't match? */
result->clean = (merge_status == 0);
} else if (S_ISGITLINK(a->mode)) {
result->clean = merge_submodule(opt, &result->blob.oid,
@@ -1604,7 +1626,7 @@ static int handle_file_collision(struct merge_options *opt,
/* Store things in diff_filespecs for functions that need it */
null.path = (char *)collide_path;
- oidcpy(&null.oid, &null_oid);
+ oidcpy(&null.oid, null_oid());
null.mode = 0;
if (merge_mode_and_contents(opt, &null, a, b, collide_path,
@@ -2389,8 +2411,7 @@ static void compute_collisions(struct hashmap *collisions,
continue;
collision_ent = collision_find_entry(collisions, new_path);
if (!collision_ent) {
- collision_ent = xcalloc(1,
- sizeof(struct collision_entry));
+ CALLOC_ARRAY(collision_ent, 1);
hashmap_entry_init(&collision_ent->ent,
strhash(new_path));
hashmap_put(collisions, &collision_ent->ent);
@@ -2594,7 +2615,7 @@ static struct string_list *get_renames(struct merge_options *opt,
struct string_list *renames;
compute_collisions(&collisions, dir_renames, pairs);
- renames = xcalloc(1, sizeof(struct string_list));
+ CALLOC_ARRAY(renames, 1);
for (i = 0; i < pairs->nr; ++i) {
struct string_list_item *item;
@@ -2664,6 +2685,14 @@ static int process_renames(struct merge_options *opt,
struct string_list b_by_dst = STRING_LIST_INIT_NODUP;
const struct rename *sre;
+ /*
+ * FIXME: As string-list.h notes, it's O(n^2) to build a sorted
+ * string_list one-by-one, but O(n log n) to build it unsorted and
+ * then sort it. Note that as we build the list, we do not need to
+ * check if the existing destination path is already in the list,
+ * because the structure of diffcore_rename guarantees we won't
+ * have duplicates.
+ */
for (i = 0; i < a_renames->nr; i++) {
sre = a_renames->items[i].util;
string_list_insert(&a_by_dst, sre->pair->two->path)->util
@@ -2790,11 +2819,11 @@ static int process_renames(struct merge_options *opt,
dst_other.mode = ren1->dst_entry->stages[other_stage].mode;
try_merge = 0;
- if (oideq(&src_other.oid, &null_oid) &&
+ if (oideq(&src_other.oid, null_oid()) &&
ren1->dir_rename_original_type == 'A') {
setup_rename_conflict_info(RENAME_VIA_DIR,
opt, ren1, NULL);
- } else if (oideq(&src_other.oid, &null_oid)) {
+ } else if (oideq(&src_other.oid, null_oid())) {
setup_rename_conflict_info(RENAME_DELETE,
opt, ren1, NULL);
} else if ((dst_other.mode == ren1->pair->two->mode) &&
@@ -2813,7 +2842,7 @@ static int process_renames(struct merge_options *opt,
1, /* update_cache */
0 /* update_wd */))
clean_merge = -1;
- } else if (!oideq(&dst_other.oid, &null_oid)) {
+ } else if (!oideq(&dst_other.oid, null_oid())) {
/*
* Probably not a clean merge, but it's
* premature to set clean_merge to 0 here,
@@ -2987,7 +3016,7 @@ static int blob_unchanged(struct merge_options *opt,
struct strbuf obuf = STRBUF_INIT;
struct strbuf abuf = STRBUF_INIT;
int ret = 0; /* assume changed for safety */
- const struct index_state *idx = opt->repo->index;
+ struct index_state *idx = opt->repo->index;
if (a->mode != o->mode)
return 0;
@@ -3433,7 +3462,7 @@ static int merge_trees_internal(struct merge_options *opt,
}
if (oideq(&merge_base->object.oid, &merge->object.oid)) {
- output(opt, 0, _("Already up to date!"));
+ output(opt, 0, _("Already up to date."));
*result = head;
return 1;
}
@@ -3602,6 +3631,15 @@ static int merge_recursive_internal(struct merge_options *opt,
return err(opt, _("merge returned no commit"));
}
+ /*
+ * FIXME: Since merge_recursive_internal() is only ever called by
+ * places that ensure the index is loaded first
+ * (e.g. builtin/merge.c, rebase/sequencer, etc.), in the common
+ * case where the merge base was unique that means when we get here
+ * we immediately discard the index and re-read it, which is a
+ * complete waste of time. We should only be discarding and
+ * re-reading if we were forced to recurse.
+ */
discard_index(opt->repo->index);
if (!opt->priv->call_depth)
repo_read_index(opt->repo);
@@ -3664,7 +3702,7 @@ static int merge_start(struct merge_options *opt, struct tree *head)
return -1;
}
- opt->priv = xcalloc(1, sizeof(*opt->priv));
+ CALLOC_ARRAY(opt->priv, 1);
string_list_init(&opt->priv->df_conflict_file_set, 1);
return 0;
}
diff --git a/midx.c b/midx.c
index 971faa8cfc..21d6a05e88 100644
--- a/midx.c
+++ b/midx.c
@@ -12,6 +12,7 @@
#include "run-command.h"
#include "repository.h"
#include "chunk-format.h"
+#include "pack.h"
#define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */
#define MIDX_VERSION 1
@@ -47,11 +48,22 @@ static uint8_t oid_version(void)
}
}
+static const unsigned char *get_midx_checksum(struct multi_pack_index *m)
+{
+ return m->data + m->data_len - the_hash_algo->rawsz;
+}
+
static char *get_midx_filename(const char *object_dir)
{
return xstrfmt("%s/pack/multi-pack-index", object_dir);
}
+char *get_midx_rev_filename(struct multi_pack_index *m)
+{
+ return xstrfmt("%s/pack/multi-pack-index-%s.rev",
+ m->object_dir, hash_to_hex(get_midx_checksum(m)));
+}
+
static int midx_read_oid_fanout(const unsigned char *chunk_start,
size_t chunk_size, void *data)
{
@@ -145,8 +157,8 @@ struct multi_pack_index *load_multi_pack_index(const char *object_dir, int local
m->num_objects = ntohl(m->chunk_oid_fanout[255]);
- m->pack_names = xcalloc(m->num_packs, sizeof(*m->pack_names));
- m->packs = xcalloc(m->num_packs, sizeof(*m->packs));
+ CALLOC_ARRAY(m->pack_names, m->num_packs);
+ CALLOC_ARRAY(m->packs, m->num_packs);
cur_pack_name = (const char *)m->chunk_pack_names;
for (i = 0; i < m->num_packs; i++) {
@@ -235,11 +247,11 @@ struct object_id *nth_midxed_object_oid(struct object_id *oid,
if (n >= m->num_objects)
return NULL;
- hashcpy(oid->hash, m->chunk_oid_lookup + m->hash_len * n);
+ oidread(oid, m->chunk_oid_lookup + m->hash_len * n);
return oid;
}
-static off_t nth_midxed_offset(struct multi_pack_index *m, uint32_t pos)
+off_t nth_midxed_offset(struct multi_pack_index *m, uint32_t pos)
{
const unsigned char *offset_data;
uint32_t offset32;
@@ -258,7 +270,7 @@ static off_t nth_midxed_offset(struct multi_pack_index *m, uint32_t pos)
return offset32;
}
-static uint32_t nth_midxed_pack_int_id(struct multi_pack_index *m, uint32_t pos)
+uint32_t nth_midxed_pack_int_id(struct multi_pack_index *m, uint32_t pos)
{
return get_be32(m->chunk_object_offsets +
(off_t)pos * MIDX_CHUNK_OFFSET_WIDTH);
@@ -431,6 +443,14 @@ static int pack_info_compare(const void *_a, const void *_b)
return strcmp(a->pack_name, b->pack_name);
}
+static int idx_or_pack_name_cmp(const void *_va, const void *_vb)
+{
+ const char *pack_name = _va;
+ const struct pack_info *compar = _vb;
+
+ return cmp_idx_or_pack_name(pack_name, compar->pack_name);
+}
+
struct write_midx_context {
struct pack_info *info;
uint32_t nr;
@@ -443,8 +463,11 @@ struct write_midx_context {
uint32_t entries_nr;
uint32_t *pack_perm;
+ uint32_t *pack_order;
unsigned large_offsets_needed:1;
uint32_t num_large_offsets;
+
+ int preferred_pack_idx;
};
static void add_pack_to_midx(const char *full_path, size_t full_path_len,
@@ -489,6 +512,7 @@ struct pack_midx_entry {
uint32_t pack_int_id;
time_t pack_mtime;
uint64_t offset;
+ unsigned preferred : 1;
};
static int midx_oid_compare(const void *_a, const void *_b)
@@ -500,6 +524,12 @@ static int midx_oid_compare(const void *_a, const void *_b)
if (cmp)
return cmp;
+ /* Sort objects in a preferred pack first when multiple copies exist. */
+ if (a->preferred > b->preferred)
+ return -1;
+ if (a->preferred < b->preferred)
+ return 1;
+
if (a->pack_mtime > b->pack_mtime)
return -1;
else if (a->pack_mtime < b->pack_mtime)
@@ -527,7 +557,8 @@ static int nth_midxed_pack_midx_entry(struct multi_pack_index *m,
static void fill_pack_entry(uint32_t pack_int_id,
struct packed_git *p,
uint32_t cur_object,
- struct pack_midx_entry *entry)
+ struct pack_midx_entry *entry,
+ int preferred)
{
if (nth_packed_object_id(&entry->oid, p, cur_object) < 0)
die(_("failed to locate object %d in packfile"), cur_object);
@@ -536,6 +567,7 @@ static void fill_pack_entry(uint32_t pack_int_id,
entry->pack_mtime = p->mtime;
entry->offset = nth_packed_object_offset(p, cur_object);
+ entry->preferred = !!preferred;
}
/*
@@ -552,7 +584,8 @@ static void fill_pack_entry(uint32_t pack_int_id,
static struct pack_midx_entry *get_sorted_entries(struct multi_pack_index *m,
struct pack_info *info,
uint32_t nr_packs,
- uint32_t *nr_objects)
+ uint32_t *nr_objects,
+ int preferred_pack)
{
uint32_t cur_fanout, cur_pack, cur_object;
uint32_t alloc_fanout, alloc_objects, total_objects = 0;
@@ -589,12 +622,17 @@ static struct pack_midx_entry *get_sorted_entries(struct multi_pack_index *m,
nth_midxed_pack_midx_entry(m,
&entries_by_fanout[nr_fanout],
cur_object);
+ if (nth_midxed_pack_int_id(m, cur_object) == preferred_pack)
+ entries_by_fanout[nr_fanout].preferred = 1;
+ else
+ entries_by_fanout[nr_fanout].preferred = 0;
nr_fanout++;
}
}
for (cur_pack = start_pack; cur_pack < nr_packs; cur_pack++) {
uint32_t start = 0, end;
+ int preferred = cur_pack == preferred_pack;
if (cur_fanout)
start = get_pack_fanout(info[cur_pack].p, cur_fanout - 1);
@@ -602,7 +640,11 @@ static struct pack_midx_entry *get_sorted_entries(struct multi_pack_index *m,
for (cur_object = start; cur_object < end; cur_object++) {
ALLOC_GROW(entries_by_fanout, nr_fanout + 1, alloc_fanout);
- fill_pack_entry(cur_pack, info[cur_pack].p, cur_object, &entries_by_fanout[nr_fanout]);
+ fill_pack_entry(cur_pack,
+ info[cur_pack].p,
+ cur_object,
+ &entries_by_fanout[nr_fanout],
+ preferred);
nr_fanout++;
}
}
@@ -776,10 +818,80 @@ static int write_midx_large_offsets(struct hashfile *f,
return 0;
}
+struct midx_pack_order_data {
+ uint32_t nr;
+ uint32_t pack;
+ off_t offset;
+};
+
+static int midx_pack_order_cmp(const void *va, const void *vb)
+{
+ const struct midx_pack_order_data *a = va, *b = vb;
+ if (a->pack < b->pack)
+ return -1;
+ else if (a->pack > b->pack)
+ return 1;
+ else if (a->offset < b->offset)
+ return -1;
+ else if (a->offset > b->offset)
+ return 1;
+ else
+ return 0;
+}
+
+static uint32_t *midx_pack_order(struct write_midx_context *ctx)
+{
+ struct midx_pack_order_data *data;
+ uint32_t *pack_order;
+ uint32_t i;
+
+ ALLOC_ARRAY(data, ctx->entries_nr);
+ for (i = 0; i < ctx->entries_nr; i++) {
+ struct pack_midx_entry *e = &ctx->entries[i];
+ data[i].nr = i;
+ data[i].pack = ctx->pack_perm[e->pack_int_id];
+ if (!e->preferred)
+ data[i].pack |= (1U << 31);
+ data[i].offset = e->offset;
+ }
+
+ QSORT(data, ctx->entries_nr, midx_pack_order_cmp);
+
+ ALLOC_ARRAY(pack_order, ctx->entries_nr);
+ for (i = 0; i < ctx->entries_nr; i++)
+ pack_order[i] = data[i].nr;
+ free(data);
+
+ return pack_order;
+}
+
+static void write_midx_reverse_index(char *midx_name, unsigned char *midx_hash,
+ struct write_midx_context *ctx)
+{
+ struct strbuf buf = STRBUF_INIT;
+ const char *tmp_file;
+
+ strbuf_addf(&buf, "%s-%s.rev", midx_name, hash_to_hex(midx_hash));
+
+ tmp_file = write_rev_file_order(NULL, ctx->pack_order, ctx->entries_nr,
+ midx_hash, WRITE_REV);
+
+ if (finalize_object_file(tmp_file, buf.buf))
+ die(_("cannot store reverse index file"));
+
+ strbuf_release(&buf);
+}
+
+static void clear_midx_files_ext(struct repository *r, const char *ext,
+ unsigned char *keep_hash);
+
static int write_midx_internal(const char *object_dir, struct multi_pack_index *m,
- struct string_list *packs_to_drop, unsigned flags)
+ struct string_list *packs_to_drop,
+ const char *preferred_pack_name,
+ unsigned flags)
{
char *midx_name;
+ unsigned char midx_hash[GIT_MAX_RAWSZ];
uint32_t i;
struct hashfile *f = NULL;
struct lock_file lk;
@@ -828,7 +940,19 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index *
if (ctx.m && ctx.nr == ctx.m->num_packs && !packs_to_drop)
goto cleanup;
- ctx.entries = get_sorted_entries(ctx.m, ctx.info, ctx.nr, &ctx.entries_nr);
+ ctx.preferred_pack_idx = -1;
+ if (preferred_pack_name) {
+ for (i = 0; i < ctx.nr; i++) {
+ if (!cmp_idx_or_pack_name(preferred_pack_name,
+ ctx.info[i].pack_name)) {
+ ctx.preferred_pack_idx = i;
+ break;
+ }
+ }
+ }
+
+ ctx.entries = get_sorted_entries(ctx.m, ctx.info, ctx.nr, &ctx.entries_nr,
+ ctx.preferred_pack_idx);
ctx.large_offsets_needed = 0;
for (i = 0; i < ctx.entries_nr; i++) {
@@ -889,13 +1013,30 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index *
pack_name_concat_len += strlen(ctx.info[i].pack_name) + 1;
}
+ /* Check that the preferred pack wasn't expired (if given). */
+ if (preferred_pack_name) {
+ struct pack_info *preferred = bsearch(preferred_pack_name,
+ ctx.info, ctx.nr,
+ sizeof(*ctx.info),
+ idx_or_pack_name_cmp);
+
+ if (!preferred)
+ warning(_("unknown preferred pack: '%s'"),
+ preferred_pack_name);
+ else {
+ uint32_t perm = ctx.pack_perm[preferred->orig_pack_int_id];
+ if (perm == PACK_EXPIRED)
+ warning(_("preferred pack '%s' is expired"),
+ preferred_pack_name);
+ }
+ }
+
if (pack_name_concat_len % MIDX_CHUNK_ALIGNMENT)
pack_name_concat_len += MIDX_CHUNK_ALIGNMENT -
(pack_name_concat_len % MIDX_CHUNK_ALIGNMENT);
hold_lock_file_for_update(&lk, midx_name, LOCK_DIE_ON_ERROR);
f = hashfd(get_lock_file_fd(&lk), get_lock_file_path(&lk));
- FREE_AND_NULL(midx_name);
if (ctx.m)
close_midx(ctx.m);
@@ -927,8 +1068,16 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index *
write_midx_header(f, get_num_chunks(cf), ctx.nr - dropped_packs);
write_chunkfile(cf, &ctx);
- finalize_hashfile(f, NULL, CSUM_FSYNC | CSUM_HASH_IN_STREAM);
+ finalize_hashfile(f, midx_hash, CSUM_FSYNC | CSUM_HASH_IN_STREAM);
free_chunkfile(cf);
+
+ if (flags & MIDX_WRITE_REV_INDEX)
+ ctx.pack_order = midx_pack_order(&ctx);
+
+ if (flags & MIDX_WRITE_REV_INDEX)
+ write_midx_reverse_index(midx_name, midx_hash, &ctx);
+ clear_midx_files_ext(the_repository, ".rev", midx_hash);
+
commit_lock_file(&lk);
cleanup:
@@ -943,13 +1092,55 @@ cleanup:
free(ctx.info);
free(ctx.entries);
free(ctx.pack_perm);
+ free(ctx.pack_order);
free(midx_name);
return result;
}
-int write_midx_file(const char *object_dir, unsigned flags)
+int write_midx_file(const char *object_dir,
+ const char *preferred_pack_name,
+ unsigned flags)
{
- return write_midx_internal(object_dir, NULL, NULL, flags);
+ return write_midx_internal(object_dir, NULL, NULL, preferred_pack_name,
+ flags);
+}
+
+struct clear_midx_data {
+ char *keep;
+ const char *ext;
+};
+
+static void clear_midx_file_ext(const char *full_path, size_t full_path_len,
+ const char *file_name, void *_data)
+{
+ struct clear_midx_data *data = _data;
+
+ if (!(starts_with(file_name, "multi-pack-index-") &&
+ ends_with(file_name, data->ext)))
+ return;
+ if (data->keep && !strcmp(data->keep, file_name))
+ return;
+
+ if (unlink(full_path))
+ die_errno(_("failed to remove %s"), full_path);
+}
+
+static void clear_midx_files_ext(struct repository *r, const char *ext,
+ unsigned char *keep_hash)
+{
+ struct clear_midx_data data;
+ memset(&data, 0, sizeof(struct clear_midx_data));
+
+ if (keep_hash)
+ data.keep = xstrfmt("multi-pack-index-%s%s",
+ hash_to_hex(keep_hash), ext);
+ data.ext = ext;
+
+ for_each_file_in_pack_dir(r->objects->odb->path,
+ clear_midx_file_ext,
+ &data);
+
+ free(data.keep);
}
void clear_midx_file(struct repository *r)
@@ -964,6 +1155,8 @@ void clear_midx_file(struct repository *r)
if (remove_path(midx))
die(_("failed to clear multi-pack-index at %s"), midx);
+ clear_midx_files_ext(r, ".rev", NULL);
+
free(midx);
}
@@ -1144,7 +1337,7 @@ int expire_midx_packs(struct repository *r, const char *object_dir, unsigned fla
if (!m)
return 0;
- count = xcalloc(m->num_packs, sizeof(uint32_t));
+ CALLOC_ARRAY(count, m->num_packs);
if (flags & MIDX_PROGRESS)
progress = start_delayed_progress(_("Counting referenced objects"),
@@ -1184,7 +1377,7 @@ int expire_midx_packs(struct repository *r, const char *object_dir, unsigned fla
free(count);
if (packs_to_drop.nr)
- result = write_midx_internal(object_dir, m, &packs_to_drop, flags);
+ result = write_midx_internal(object_dir, m, &packs_to_drop, NULL, flags);
string_list_clear(&packs_to_drop, 0);
return result;
@@ -1315,7 +1508,7 @@ int midx_repack(struct repository *r, const char *object_dir, size_t batch_size,
if (!m)
return 0;
- include_pack = xcalloc(m->num_packs, sizeof(unsigned char));
+ CALLOC_ARRAY(include_pack, m->num_packs);
if (batch_size) {
if (fill_included_packs_batch(r, m, include_pack, batch_size))
@@ -1373,7 +1566,7 @@ int midx_repack(struct repository *r, const char *object_dir, size_t batch_size,
goto cleanup;
}
- result = write_midx_internal(object_dir, m, NULL, flags);
+ result = write_midx_internal(object_dir, m, NULL, NULL, flags);
m = NULL;
cleanup:
diff --git a/midx.h b/midx.h
index b18cf53bc4..8684cf0fef 100644
--- a/midx.h
+++ b/midx.h
@@ -15,6 +15,10 @@ struct multi_pack_index {
const unsigned char *data;
size_t data_len;
+ const uint32_t *revindex_data;
+ const uint32_t *revindex_map;
+ size_t revindex_len;
+
uint32_t signature;
unsigned char version;
unsigned char hash_len;
@@ -36,10 +40,15 @@ struct multi_pack_index {
};
#define MIDX_PROGRESS (1 << 0)
+#define MIDX_WRITE_REV_INDEX (1 << 1)
+
+char *get_midx_rev_filename(struct multi_pack_index *m);
struct multi_pack_index *load_multi_pack_index(const char *object_dir, int local);
int prepare_midx_pack(struct repository *r, struct multi_pack_index *m, uint32_t pack_int_id);
int bsearch_midx(const struct object_id *oid, struct multi_pack_index *m, uint32_t *result);
+off_t nth_midxed_offset(struct multi_pack_index *m, uint32_t pos);
+uint32_t nth_midxed_pack_int_id(struct multi_pack_index *m, uint32_t pos);
struct object_id *nth_midxed_object_oid(struct object_id *oid,
struct multi_pack_index *m,
uint32_t n);
@@ -47,7 +56,7 @@ int fill_midx_entry(struct repository *r, const struct object_id *oid, struct pa
int midx_contains_pack(struct multi_pack_index *m, const char *idx_or_pack_name);
int prepare_multi_pack_index_one(struct repository *r, const char *object_dir, int local);
-int write_midx_file(const char *object_dir, unsigned flags);
+int write_midx_file(const char *object_dir, const char *preferred_pack_name, unsigned flags);
void clear_midx_file(struct repository *r);
int verify_midx_file(struct repository *r, const char *object_dir, unsigned flags);
int expire_midx_packs(struct repository *r, const char *object_dir, unsigned flags);
diff --git a/name-hash.c b/name-hash.c
index 4e03fac9bb..7487d33124 100644
--- a/name-hash.c
+++ b/name-hash.c
@@ -8,6 +8,7 @@
#include "cache.h"
#include "thread-utils.h"
#include "trace2.h"
+#include "sparse-index.h"
struct dir_entry {
struct hashmap_entry ent;
@@ -109,8 +110,11 @@ static void hash_index_entry(struct index_state *istate, struct cache_entry *ce)
if (ce->ce_flags & CE_HASHED)
return;
ce->ce_flags |= CE_HASHED;
- hashmap_entry_init(&ce->ent, memihash(ce->name, ce_namelen(ce)));
- hashmap_add(&istate->name_hash, &ce->ent);
+
+ if (!S_ISSPARSEDIR(ce->ce_mode)) {
+ hashmap_entry_init(&ce->ent, memihash(ce->name, ce_namelen(ce)));
+ hashmap_add(&istate->name_hash, &ce->ent);
+ }
if (ignore_case)
add_dir_entry(istate, ce);
@@ -225,7 +229,7 @@ static void init_dir_mutex(void)
{
int j;
- lazy_dir_mutex_array = xcalloc(LAZY_MAX_MUTEX, sizeof(pthread_mutex_t));
+ CALLOC_ARRAY(lazy_dir_mutex_array, LAZY_MAX_MUTEX);
for (j = 0; j < LAZY_MAX_MUTEX; j++)
init_recursive_mutex(&lazy_dir_mutex_array[j]);
@@ -514,9 +518,9 @@ static void threaded_lazy_init_name_hash(
k_start = 0;
nr_each = DIV_ROUND_UP(istate->cache_nr, lazy_nr_dir_threads);
- lazy_entries = xcalloc(istate->cache_nr, sizeof(struct lazy_entry));
- td_dir = xcalloc(lazy_nr_dir_threads, sizeof(struct lazy_dir_thread_data));
- td_name = xcalloc(1, sizeof(struct lazy_name_thread_data));
+ CALLOC_ARRAY(lazy_entries, istate->cache_nr);
+ CALLOC_ARRAY(td_dir, lazy_nr_dir_threads);
+ CALLOC_ARRAY(td_name, 1);
init_dir_mutex();
@@ -680,6 +684,7 @@ int index_dir_exists(struct index_state *istate, const char *name, int namelen)
struct dir_entry *dir;
lazy_init_name_hash(istate);
+ expand_to_path(istate, name, namelen, 0);
dir = find_dir_entry(istate, name, namelen);
return dir && dir->nr;
}
@@ -690,6 +695,7 @@ void adjust_dirname_case(struct index_state *istate, char *name)
const char *ptr = startPtr;
lazy_init_name_hash(istate);
+ expand_to_path(istate, name, strlen(name), 0);
while (*ptr) {
while (*ptr && *ptr != '/')
ptr++;
@@ -713,6 +719,7 @@ struct cache_entry *index_file_exists(struct index_state *istate, const char *na
unsigned int hash = memihash(name, namelen);
lazy_init_name_hash(istate);
+ expand_to_path(istate, name, namelen, icase);
ce = hashmap_get_entry_from_hash(&istate->name_hash, hash, NULL,
struct cache_entry, ent);
diff --git a/negotiator/default.c b/negotiator/default.c
index 4b78f6bf36..434189ae5d 100644
--- a/negotiator/default.c
+++ b/negotiator/default.c
@@ -167,7 +167,7 @@ void default_negotiator_init(struct fetch_negotiator *negotiator)
negotiator->next = next;
negotiator->ack = ack;
negotiator->release = release;
- negotiator->data = ns = xcalloc(1, sizeof(*ns));
+ negotiator->data = CALLOC_ARRAY(ns, 1);
ns->rev_list.compare = compare_commits_by_commit_date;
if (marked)
diff --git a/negotiator/skipping.c b/negotiator/skipping.c
index dffbc76c49..1236e79224 100644
--- a/negotiator/skipping.c
+++ b/negotiator/skipping.c
@@ -62,7 +62,7 @@ static struct entry *rev_list_push(struct data *data, struct commit *commit, int
struct entry *entry;
commit->object.flags |= mark | SEEN;
- entry = xcalloc(1, sizeof(*entry));
+ CALLOC_ARRAY(entry, 1);
entry->commit = commit;
prio_queue_put(&data->rev_list, entry);
@@ -241,7 +241,7 @@ void skipping_negotiator_init(struct fetch_negotiator *negotiator)
negotiator->next = next;
negotiator->ack = ack;
negotiator->release = release;
- negotiator->data = data = xcalloc(1, sizeof(*data));
+ negotiator->data = CALLOC_ARRAY(data, 1);
data->rev_list.compare = compare;
if (marked)
diff --git a/notes-merge.c b/notes-merge.c
index 2fe724f1cf..46c1f7c7f1 100644
--- a/notes-merge.c
+++ b/notes-merge.c
@@ -136,7 +136,7 @@ static struct notes_merge_pair *diff_tree_remote(struct notes_merge_options *o,
diff_tree_oid(base, remote, "", &opt);
diffcore_std(&opt);
- changes = xcalloc(diff_queued_diff.nr, sizeof(struct notes_merge_pair));
+ CALLOC_ARRAY(changes, diff_queued_diff.nr);
for (i = 0; i < diff_queued_diff.nr; i++) {
struct diff_filepair *p = diff_queued_diff.queue[i];
@@ -600,7 +600,7 @@ int notes_merge(struct notes_merge_options *o,
/* Find merge bases */
bases = get_merge_bases(local, remote);
if (!bases) {
- base_oid = &null_oid;
+ base_oid = null_oid();
base_tree_oid = the_hash_algo->empty_tree;
if (o->verbosity >= 4)
printf("No merge base found; doing history-less merge\n");
@@ -628,7 +628,7 @@ int notes_merge(struct notes_merge_options *o,
if (oideq(&remote->object.oid, base_oid)) {
/* Already merged; result == local commit */
if (o->verbosity >= 2)
- printf("Already up to date!\n");
+ printf_ln("Already up to date.");
oidcpy(result_oid, &local->object.oid);
goto found_result;
}
@@ -695,13 +695,10 @@ int notes_merge_commit(struct notes_merge_options *o,
strbuf_addch(&path, '/');
baselen = path.len;
- while ((e = readdir(dir)) != NULL) {
+ while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL) {
struct stat st;
struct object_id obj_oid, blob_oid;
- if (is_dot_or_dotdot(e->d_name))
- continue;
-
if (get_oid_hex(e->d_name, &obj_oid)) {
if (o->verbosity >= 3)
printf("Skipping non-SHA1 entry '%s%s'\n",
diff --git a/notes-utils.c b/notes-utils.c
index 4bf4888d8c..d7d18e30f5 100644
--- a/notes-utils.c
+++ b/notes-utils.c
@@ -129,7 +129,7 @@ struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd)
c->cmd = cmd;
c->enabled = 1;
c->combine = combine_notes_concatenate;
- c->refs = xcalloc(1, sizeof(struct string_list));
+ CALLOC_ARRAY(c->refs, 1);
c->refs->strdup_strings = 1;
c->refs_from_env = 0;
c->mode_from_env = 0;
diff --git a/notes.c b/notes.c
index d5ac081e76..f87dac4068 100644
--- a/notes.c
+++ b/notes.c
@@ -352,7 +352,7 @@ static void add_non_note(struct notes_tree *t, char *path,
n->next = NULL;
n->path = path;
n->mode = mode;
- hashcpy(n->oid.hash, sha1);
+ oidread(&n->oid, sha1);
t->prev_non_note = n;
if (!t->first_non_note) {
@@ -452,9 +452,11 @@ static void load_subtree(struct notes_tree *t, struct leaf_node *subtree,
goto handle_non_note;
}
- l = xcalloc(1, sizeof(*l));
+ CALLOC_ARRAY(l, 1);
oidcpy(&l->key_oid, &object_oid);
oidcpy(&l->val_oid, &entry.oid);
+ oid_set_algo(&l->key_oid, the_hash_algo);
+ oid_set_algo(&l->val_oid, the_hash_algo);
if (note_tree_insert(t, node, n, l, type,
combine_notes_concatenate))
die("Failed to load %s %s into notes tree "
@@ -484,6 +486,7 @@ handle_non_note:
strbuf_addch(&non_note_path, '/');
}
strbuf_addstr(&non_note_path, entry.path);
+ oid_set_algo(&entry.oid, the_hash_algo);
add_non_note(t, strbuf_detach(&non_note_path, NULL),
entry.mode, entry.oid.hash);
}
@@ -1134,7 +1137,7 @@ int remove_note(struct notes_tree *t, const unsigned char *object_sha1)
if (!t)
t = &default_notes_tree;
assert(t->initialized);
- hashcpy(l.key_oid.hash, object_sha1);
+ oidread(&l.key_oid, object_sha1);
oidclr(&l.val_oid);
note_tree_remove(t, t->root, 0, &l);
if (is_null_oid(&l.val_oid)) /* no note was removed */
@@ -1324,7 +1327,7 @@ int copy_note(struct notes_tree *t,
if (note)
return add_note(t, to_obj, note, combine_notes);
else if (existing_note)
- return add_note(t, to_obj, &null_oid, combine_notes);
+ return add_note(t, to_obj, null_oid(), combine_notes);
return 0;
}
diff --git a/object-file.c b/object-file.c
index 5bcfde8471..f233b440b2 100644
--- a/object-file.c
+++ b/object-file.c
@@ -55,18 +55,29 @@
"\x6f\xe1\x41\xf7\x74\x91\x20\xa3\x03\x72" \
"\x18\x13"
-const struct object_id null_oid;
static const struct object_id empty_tree_oid = {
- EMPTY_TREE_SHA1_BIN_LITERAL
+ .hash = EMPTY_TREE_SHA1_BIN_LITERAL,
+ .algo = GIT_HASH_SHA1,
};
static const struct object_id empty_blob_oid = {
- EMPTY_BLOB_SHA1_BIN_LITERAL
+ .hash = EMPTY_BLOB_SHA1_BIN_LITERAL,
+ .algo = GIT_HASH_SHA1,
+};
+static const struct object_id null_oid_sha1 = {
+ .hash = {0},
+ .algo = GIT_HASH_SHA1,
};
static const struct object_id empty_tree_oid_sha256 = {
- EMPTY_TREE_SHA256_BIN_LITERAL
+ .hash = EMPTY_TREE_SHA256_BIN_LITERAL,
+ .algo = GIT_HASH_SHA256,
};
static const struct object_id empty_blob_oid_sha256 = {
- EMPTY_BLOB_SHA256_BIN_LITERAL
+ .hash = EMPTY_BLOB_SHA256_BIN_LITERAL,
+ .algo = GIT_HASH_SHA256,
+};
+static const struct object_id null_oid_sha256 = {
+ .hash = {0},
+ .algo = GIT_HASH_SHA256,
};
static void git_hash_sha1_init(git_hash_ctx *ctx)
@@ -89,6 +100,13 @@ static void git_hash_sha1_final(unsigned char *hash, git_hash_ctx *ctx)
git_SHA1_Final(hash, &ctx->sha1);
}
+static void git_hash_sha1_final_oid(struct object_id *oid, git_hash_ctx *ctx)
+{
+ git_SHA1_Final(oid->hash, &ctx->sha1);
+ memset(oid->hash + GIT_SHA1_RAWSZ, 0, GIT_MAX_RAWSZ - GIT_SHA1_RAWSZ);
+ oid->algo = GIT_HASH_SHA1;
+}
+
static void git_hash_sha256_init(git_hash_ctx *ctx)
{
@@ -110,6 +128,17 @@ static void git_hash_sha256_final(unsigned char *hash, git_hash_ctx *ctx)
git_SHA256_Final(hash, &ctx->sha256);
}
+static void git_hash_sha256_final_oid(struct object_id *oid, git_hash_ctx *ctx)
+{
+ git_SHA256_Final(oid->hash, &ctx->sha256);
+ /*
+ * This currently does nothing, so the compiler should optimize it out,
+ * but keep it in case we extend the hash size again.
+ */
+ memset(oid->hash + GIT_SHA256_RAWSZ, 0, GIT_MAX_RAWSZ - GIT_SHA256_RAWSZ);
+ oid->algo = GIT_HASH_SHA256;
+}
+
static void git_hash_unknown_init(git_hash_ctx *ctx)
{
BUG("trying to init unknown hash");
@@ -130,6 +159,12 @@ static void git_hash_unknown_final(unsigned char *hash, git_hash_ctx *ctx)
BUG("trying to finalize unknown hash");
}
+static void git_hash_unknown_final_oid(struct object_id *oid, git_hash_ctx *ctx)
+{
+ BUG("trying to finalize unknown hash");
+}
+
+
const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
{
NULL,
@@ -141,6 +176,8 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
git_hash_unknown_clone,
git_hash_unknown_update,
git_hash_unknown_final,
+ git_hash_unknown_final_oid,
+ NULL,
NULL,
NULL,
},
@@ -155,8 +192,10 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
git_hash_sha1_clone,
git_hash_sha1_update,
git_hash_sha1_final,
+ git_hash_sha1_final_oid,
&empty_tree_oid,
&empty_blob_oid,
+ &null_oid_sha1,
},
{
"sha256",
@@ -169,11 +208,18 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
git_hash_sha256_clone,
git_hash_sha256_update,
git_hash_sha256_final,
+ git_hash_sha256_final_oid,
&empty_tree_oid_sha256,
&empty_blob_oid_sha256,
+ &null_oid_sha256,
}
};
+const struct object_id *null_oid(void)
+{
+ return the_hash_algo->null_oid;
+}
+
const char *empty_tree_oid_hex(void)
{
static char buf[GIT_MAX_HEXSZ + 1];
@@ -546,7 +592,7 @@ static int link_alt_odb_entry(struct repository *r, const char *entry,
return -1;
}
- ent = xcalloc(1, sizeof(*ent));
+ CALLOC_ARRAY(ent, 1);
ent->path = xstrdup(pathbuf.buf);
/* add the alternate entry */
@@ -1029,7 +1075,7 @@ int check_object_signature(struct repository *r, const struct object_id *oid,
break;
r->hash_algo->update_fn(&c, buf, readlen);
}
- r->hash_algo->final_fn(real_oid.hash, &c);
+ r->hash_algo->final_oid_fn(&real_oid, &c);
close_istream(st);
return !oideq(oid, &real_oid) ? -1 : 0;
}
@@ -1730,7 +1776,7 @@ static void write_object_file_prepare(const struct git_hash_algo *algo,
algo->init_fn(&c);
algo->update_fn(&c, hdr, *hdrlen);
algo->update_fn(&c, buf, len);
- algo->final_fn(oid->hash, &c);
+ algo->final_oid_fn(oid, &c);
}
/*
@@ -1902,7 +1948,7 @@ static int write_loose_object(const struct object_id *oid, char *hdr,
if (ret != Z_OK)
die(_("deflateEnd on object %s failed (%d)"), oid_to_hex(oid),
ret);
- the_hash_algo->final_fn(parano_oid.hash, &c);
+ the_hash_algo->final_oid_fn(&parano_oid, &c);
if (!oideq(oid, &parano_oid))
die(_("confused by unstable object source data for %s"),
oid_to_hex(oid));
@@ -2304,10 +2350,8 @@ int for_each_file_in_obj_subdir(unsigned int subdir_nr,
strbuf_addch(path, '/');
baselen = path->len;
- while ((de = readdir(dir))) {
+ while ((de = readdir_skip_dot_and_dotdot(dir))) {
size_t namelen;
- if (is_dot_or_dotdot(de->d_name))
- continue;
namelen = strlen(de->d_name);
strbuf_setlen(path, baselen);
@@ -2315,6 +2359,7 @@ int for_each_file_in_obj_subdir(unsigned int subdir_nr,
if (namelen == the_hash_algo->hexsz - 2 &&
!hex_to_bytes(oid.hash + 1, de->d_name,
the_hash_algo->rawsz - 1)) {
+ oid_set_algo(&oid, the_hash_algo);
if (obj_cb) {
r = obj_cb(&oid, path->buf, data);
if (r)
@@ -2483,7 +2528,7 @@ static int check_stream_oid(git_zstream *stream,
return -1;
}
- the_hash_algo->final_fn(real_oid.hash, &c);
+ the_hash_algo->final_oid_fn(&real_oid, &c);
if (!oideq(expected_oid, &real_oid)) {
error(_("hash mismatch for %s (expected %s)"), path,
oid_to_hex(expected_oid));
diff --git a/object-store.h b/object-store.h
index 541dab0858..ec32c23dcb 100644
--- a/object-store.h
+++ b/object-store.h
@@ -153,6 +153,11 @@ struct raw_object_store {
/* A most-recently-used ordered version of the packed_git list. */
struct list_head packed_git_mru;
+ struct {
+ struct packed_git **packs;
+ unsigned flags;
+ } kept_pack_cache;
+
/*
* A map of packfiles to packed_git structs for tracking which
* packs have been loaded already.
diff --git a/object.c b/object.c
index 98017bed8e..14188453c5 100644
--- a/object.c
+++ b/object.c
@@ -127,7 +127,7 @@ static void grow_object_hash(struct repository *r)
int new_hash_size = r->parsed_objects->obj_hash_size < 32 ? 32 : 2 * r->parsed_objects->obj_hash_size;
struct object **new_hash;
- new_hash = xcalloc(new_hash_size, sizeof(struct object *));
+ CALLOC_ARRAY(new_hash, new_hash_size);
for (i = 0; i < r->parsed_objects->obj_hash_size; i++) {
struct object *obj = r->parsed_objects->obj_hash[i];
@@ -177,12 +177,11 @@ void *object_as_type(struct object *obj, enum object_type type, int quiet)
}
}
-struct object *lookup_unknown_object(const struct object_id *oid)
+struct object *lookup_unknown_object(struct repository *r, const struct object_id *oid)
{
- struct object *obj = lookup_object(the_repository, oid);
+ struct object *obj = lookup_object(r, oid);
if (!obj)
- obj = create_object(the_repository, oid,
- alloc_object_node(the_repository));
+ obj = create_object(r, oid, alloc_object_node(r));
return obj;
}
@@ -478,7 +477,7 @@ struct parsed_object_pool *parsed_object_pool_new(void)
o->object_state = allocate_alloc_state();
o->is_shallow = -1;
- o->shallow_stat = xcalloc(1, sizeof(*o->shallow_stat));
+ CALLOC_ARRAY(o->shallow_stat, 1);
o->buffer_slab = allocate_commit_buffer_slab();
diff --git a/object.h b/object.h
index 59daadce21..8bca310713 100644
--- a/object.h
+++ b/object.h
@@ -60,7 +60,7 @@ struct object_array {
/*
* object flag allocation:
* revision.h: 0---------10 15 23------26
- * fetch-pack.c: 01
+ * fetch-pack.c: 01 67
* negotiator/default.c: 2--5
* walker.c: 0-2
* upload-pack.c: 4 11-----14 16-----19
@@ -145,7 +145,7 @@ struct object *parse_object_or_die(const struct object_id *oid, const char *name
struct object *parse_object_buffer(struct repository *r, const struct object_id *oid, enum object_type type, unsigned long size, void *buffer, int *eaten_p);
/** Returns the object, with potentially excess memory allocated. **/
-struct object *lookup_unknown_object(const struct object_id *oid);
+struct object *lookup_unknown_object(struct repository *r, const struct object_id *oid);
struct object_list *object_list_insert(struct object *item,
struct object_list **list_p);
diff --git a/pack-bitmap.c b/pack-bitmap.c
index 1f69b5fa85..d90e1d9d8c 100644
--- a/pack-bitmap.c
+++ b/pack-bitmap.c
@@ -13,6 +13,7 @@
#include "repository.h"
#include "object-store.h"
#include "list-objects-filter-options.h"
+#include "config.h"
/*
* An entry on the bitmap index, representing the bitmap for a given
@@ -630,6 +631,9 @@ static struct bitmap *find_objects(struct bitmap_index *bitmap_git,
traverse_commit_list_filtered(filter, revs,
show_commit, show_object,
&show_data, NULL);
+
+ revs->include_check = NULL;
+ revs->include_check_data = NULL;
}
return base;
@@ -779,9 +783,6 @@ static void filter_bitmap_exclude_type(struct bitmap_index *bitmap_git,
eword_t mask;
uint32_t i;
- if (type != OBJ_BLOB && type != OBJ_TREE)
- BUG("filter_bitmap_exclude_type: unsupported type '%d'", type);
-
/*
* The non-bitmap version of this filter never removes
* objects which the other side specifically asked for,
@@ -911,6 +912,24 @@ static void filter_bitmap_tree_depth(struct bitmap_index *bitmap_git,
OBJ_BLOB);
}
+static void filter_bitmap_object_type(struct bitmap_index *bitmap_git,
+ struct object_list *tip_objects,
+ struct bitmap *to_filter,
+ enum object_type object_type)
+{
+ if (object_type < OBJ_COMMIT || object_type > OBJ_TAG)
+ BUG("filter_bitmap_object_type given invalid object");
+
+ if (object_type != OBJ_TAG)
+ filter_bitmap_exclude_type(bitmap_git, tip_objects, to_filter, OBJ_TAG);
+ if (object_type != OBJ_COMMIT)
+ filter_bitmap_exclude_type(bitmap_git, tip_objects, to_filter, OBJ_COMMIT);
+ if (object_type != OBJ_TREE)
+ filter_bitmap_exclude_type(bitmap_git, tip_objects, to_filter, OBJ_TREE);
+ if (object_type != OBJ_BLOB)
+ filter_bitmap_exclude_type(bitmap_git, tip_objects, to_filter, OBJ_BLOB);
+}
+
static int filter_bitmap(struct bitmap_index *bitmap_git,
struct object_list *tip_objects,
struct bitmap *to_filter,
@@ -943,6 +962,24 @@ static int filter_bitmap(struct bitmap_index *bitmap_git,
return 0;
}
+ if (filter->choice == LOFC_OBJECT_TYPE) {
+ if (bitmap_git)
+ filter_bitmap_object_type(bitmap_git, tip_objects,
+ to_filter,
+ filter->object_type);
+ return 0;
+ }
+
+ if (filter->choice == LOFC_COMBINE) {
+ int i;
+ for (i = 0; i < filter->sub_nr; i++) {
+ if (filter_bitmap(bitmap_git, tip_objects, to_filter,
+ &filter->sub[i]) < 0)
+ return -1;
+ }
+ return 0;
+ }
+
/* filter choice not handled */
return -1;
}
@@ -953,7 +990,8 @@ static int can_filter_bitmap(struct list_objects_filter_options *filter)
}
struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs,
- struct list_objects_filter_options *filter)
+ struct list_objects_filter_options *filter,
+ int filter_provided_objects)
{
unsigned int i;
@@ -978,7 +1016,7 @@ struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs,
/* try to open a bitmapped pack, but don't parse it yet
* because we may not need to use it */
- bitmap_git = xcalloc(1, sizeof(*bitmap_git));
+ CALLOC_ARRAY(bitmap_git, 1);
if (open_pack_bitmap(revs->repo, bitmap_git) < 0)
goto cleanup;
@@ -997,6 +1035,7 @@ struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs,
object_list_insert(object, &wants);
object = parse_object_or_die(get_tagged_oid(tag), NULL);
+ object->flags |= (tag->object.flags & UNINTERESTING);
}
if (object->flags & UNINTERESTING)
@@ -1047,7 +1086,8 @@ struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs,
if (haves_bitmap)
bitmap_and_not(wants_bitmap, haves_bitmap);
- filter_bitmap(bitmap_git, wants, wants_bitmap, filter);
+ filter_bitmap(bitmap_git, (filter && filter_provided_objects) ? NULL : wants,
+ wants_bitmap, filter);
bitmap_git->result = wants_bitmap;
bitmap_git->haves = haves_bitmap;
@@ -1350,6 +1390,24 @@ void test_bitmap_walk(struct rev_info *revs)
free_bitmap_index(bitmap_git);
}
+int test_bitmap_commits(struct repository *r)
+{
+ struct bitmap_index *bitmap_git = prepare_bitmap_git(r);
+ struct object_id oid;
+ MAYBE_UNUSED void *value;
+
+ if (!bitmap_git)
+ die("failed to load bitmap indexes");
+
+ kh_foreach(bitmap_git->bitmaps, oid, value, {
+ printf("%s\n", oid_to_hex(&oid));
+ });
+
+ free_bitmap_index(bitmap_git);
+
+ return 0;
+}
+
int rebuild_bitmap(const uint32_t *reposition,
struct ewah_bitmap *source,
struct bitmap *dest)
@@ -1388,7 +1446,7 @@ uint32_t *create_bitmap_mapping(struct bitmap_index *bitmap_git,
uint32_t *reposition;
num_objects = bitmap_git->pack->num_objects;
- reposition = xcalloc(num_objects, sizeof(uint32_t));
+ CALLOC_ARRAY(reposition, num_objects);
for (i = 0; i < num_objects; ++i) {
struct object_id oid;
@@ -1511,3 +1569,8 @@ off_t get_disk_usage_from_bitmap(struct bitmap_index *bitmap_git,
return total;
}
+
+const struct string_list *bitmap_preferred_tips(struct repository *r)
+{
+ return repo_config_get_value_multi(r, "pack.preferbitmaptips");
+}
diff --git a/pack-bitmap.h b/pack-bitmap.h
index 36d99930d8..99d733eb26 100644
--- a/pack-bitmap.h
+++ b/pack-bitmap.h
@@ -5,6 +5,7 @@
#include "khash.h"
#include "pack.h"
#include "pack-objects.h"
+#include "string-list.h"
struct commit;
struct repository;
@@ -49,8 +50,10 @@ void traverse_bitmap_commit_list(struct bitmap_index *,
struct rev_info *revs,
show_reachable_fn show_reachable);
void test_bitmap_walk(struct rev_info *revs);
+int test_bitmap_commits(struct repository *r);
struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs,
- struct list_objects_filter_options *filter);
+ struct list_objects_filter_options *filter,
+ int filter_provided_objects);
int reuse_partial_packfile_from_bitmap(struct bitmap_index *,
struct packed_git **packfile,
uint32_t *entries,
@@ -90,4 +93,6 @@ void bitmap_writer_finish(struct pack_idx_entry **index,
const char *filename,
uint16_t options);
+const struct string_list *bitmap_preferred_tips(struct repository *r);
+
#endif
diff --git a/pack-objects.c b/pack-objects.c
index f2a433885a..fe2a4eace9 100644
--- a/pack-objects.c
+++ b/pack-objects.c
@@ -49,7 +49,7 @@ static void rehash_objects(struct packing_data *pdata)
pdata->index_size = 1024;
free(pdata->index);
- pdata->index = xcalloc(pdata->index_size, sizeof(*pdata->index));
+ CALLOC_ARRAY(pdata->index, pdata->index_size);
entry = pdata->objects;
diff --git a/pack-objects.h b/pack-objects.h
index 9d88e3e518..dca2351ef9 100644
--- a/pack-objects.h
+++ b/pack-objects.h
@@ -268,152 +268,10 @@ static inline void oe_set_in_pack(struct packing_data *pack,
pack->in_pack[e - pack->objects] = p;
}
-static inline struct object_entry *oe_delta(
- const struct packing_data *pack,
- const struct object_entry *e)
-{
- if (!e->delta_idx)
- return NULL;
- if (e->ext_base)
- return &pack->ext_bases[e->delta_idx - 1];
- else
- return &pack->objects[e->delta_idx - 1];
-}
-
-static inline void oe_set_delta(struct packing_data *pack,
- struct object_entry *e,
- struct object_entry *delta)
-{
- if (delta)
- e->delta_idx = (delta - pack->objects) + 1;
- else
- e->delta_idx = 0;
-}
-
void oe_set_delta_ext(struct packing_data *pack,
struct object_entry *e,
const struct object_id *oid);
-static inline struct object_entry *oe_delta_child(
- const struct packing_data *pack,
- const struct object_entry *e)
-{
- if (e->delta_child_idx)
- return &pack->objects[e->delta_child_idx - 1];
- return NULL;
-}
-
-static inline void oe_set_delta_child(struct packing_data *pack,
- struct object_entry *e,
- struct object_entry *delta)
-{
- if (delta)
- e->delta_child_idx = (delta - pack->objects) + 1;
- else
- e->delta_child_idx = 0;
-}
-
-static inline struct object_entry *oe_delta_sibling(
- const struct packing_data *pack,
- const struct object_entry *e)
-{
- if (e->delta_sibling_idx)
- return &pack->objects[e->delta_sibling_idx - 1];
- return NULL;
-}
-
-static inline void oe_set_delta_sibling(struct packing_data *pack,
- struct object_entry *e,
- struct object_entry *delta)
-{
- if (delta)
- e->delta_sibling_idx = (delta - pack->objects) + 1;
- else
- e->delta_sibling_idx = 0;
-}
-
-unsigned long oe_get_size_slow(struct packing_data *pack,
- const struct object_entry *e);
-static inline unsigned long oe_size(struct packing_data *pack,
- const struct object_entry *e)
-{
- if (e->size_valid)
- return e->size_;
-
- return oe_get_size_slow(pack, e);
-}
-
-static inline int oe_size_less_than(struct packing_data *pack,
- const struct object_entry *lhs,
- unsigned long rhs)
-{
- if (lhs->size_valid)
- return lhs->size_ < rhs;
- if (rhs < pack->oe_size_limit) /* rhs < 2^x <= lhs ? */
- return 0;
- return oe_get_size_slow(pack, lhs) < rhs;
-}
-
-static inline int oe_size_greater_than(struct packing_data *pack,
- const struct object_entry *lhs,
- unsigned long rhs)
-{
- if (lhs->size_valid)
- return lhs->size_ > rhs;
- if (rhs < pack->oe_size_limit) /* rhs < 2^x <= lhs ? */
- return 1;
- return oe_get_size_slow(pack, lhs) > rhs;
-}
-
-static inline void oe_set_size(struct packing_data *pack,
- struct object_entry *e,
- unsigned long size)
-{
- if (size < pack->oe_size_limit) {
- e->size_ = size;
- e->size_valid = 1;
- } else {
- e->size_valid = 0;
- if (oe_get_size_slow(pack, e) != size)
- BUG("'size' is supposed to be the object size!");
- }
-}
-
-static inline unsigned long oe_delta_size(struct packing_data *pack,
- const struct object_entry *e)
-{
- if (e->delta_size_valid)
- return e->delta_size_;
-
- /*
- * pack->delta_size[] can't be NULL because oe_set_delta_size()
- * must have been called when a new delta is saved with
- * oe_set_delta().
- * If oe_delta() returns NULL (i.e. default state, which means
- * delta_size_valid is also false), then the caller must never
- * call oe_delta_size().
- */
- return pack->delta_size[e - pack->objects];
-}
-
-static inline void oe_set_delta_size(struct packing_data *pack,
- struct object_entry *e,
- unsigned long size)
-{
- if (size < pack->oe_delta_size_limit) {
- e->delta_size_ = size;
- e->delta_size_valid = 1;
- } else {
- packing_data_lock(pack);
- if (!pack->delta_size)
- ALLOC_ARRAY(pack->delta_size, pack->nr_alloc);
- packing_data_unlock(pack);
-
- pack->delta_size[e - pack->objects] = size;
- e->delta_size_valid = 0;
- }
-}
-
static inline unsigned int oe_tree_depth(struct packing_data *pack,
struct object_entry *e)
{
@@ -422,23 +280,6 @@ static inline unsigned int oe_tree_depth(struct packing_data *pack,
return pack->tree_depth[e - pack->objects];
}
-static inline void oe_set_tree_depth(struct packing_data *pack,
- struct object_entry *e,
- unsigned int tree_depth)
-{
- if (!pack->tree_depth)
- CALLOC_ARRAY(pack->tree_depth, pack->nr_alloc);
- pack->tree_depth[e - pack->objects] = tree_depth;
-}
-
-static inline unsigned char oe_layer(struct packing_data *pack,
- struct object_entry *e)
-{
- if (!pack->layer)
- return 0;
- return pack->layer[e - pack->objects];
-}
-
static inline void oe_set_layer(struct packing_data *pack,
struct object_entry *e,
unsigned char layer)
diff --git a/pack-revindex.c b/pack-revindex.c
index 4262530449..0e4a31d9db 100644
--- a/pack-revindex.c
+++ b/pack-revindex.c
@@ -3,6 +3,7 @@
#include "object-store.h"
#include "packfile.h"
#include "config.h"
+#include "midx.h"
struct revindex_entry {
off_t offset;
@@ -293,6 +294,43 @@ int load_pack_revindex(struct packed_git *p)
return -1;
}
+int load_midx_revindex(struct multi_pack_index *m)
+{
+ char *revindex_name;
+ int ret;
+ if (m->revindex_data)
+ return 0;
+
+ revindex_name = get_midx_rev_filename(m);
+
+ ret = load_revindex_from_disk(revindex_name,
+ m->num_objects,
+ &m->revindex_map,
+ &m->revindex_len);
+ if (ret)
+ goto cleanup;
+
+ m->revindex_data = (const uint32_t *)((const char *)m->revindex_map + RIDX_HEADER_SIZE);
+
+cleanup:
+ free(revindex_name);
+ return ret;
+}
+
+int close_midx_revindex(struct multi_pack_index *m)
+{
+ if (!m || !m->revindex_map)
+ return 0;
+
+ munmap((void*)m->revindex_map, m->revindex_len);
+
+ m->revindex_map = NULL;
+ m->revindex_data = NULL;
+ m->revindex_len = 0;
+
+ return 0;
+}
+
int offset_to_pack_pos(struct packed_git *p, off_t ofs, uint32_t *pos)
{
unsigned lo, hi;
@@ -347,3 +385,91 @@ off_t pack_pos_to_offset(struct packed_git *p, uint32_t pos)
else
return nth_packed_object_offset(p, pack_pos_to_index(p, pos));
}
+
+uint32_t pack_pos_to_midx(struct multi_pack_index *m, uint32_t pos)
+{
+ if (!m->revindex_data)
+ BUG("pack_pos_to_midx: reverse index not yet loaded");
+ if (m->num_objects <= pos)
+ BUG("pack_pos_to_midx: out-of-bounds object at %"PRIu32, pos);
+ return get_be32(m->revindex_data + pos);
+}
+
+struct midx_pack_key {
+ uint32_t pack;
+ off_t offset;
+
+ uint32_t preferred_pack;
+ struct multi_pack_index *midx;
+};
+
+static int midx_pack_order_cmp(const void *va, const void *vb)
+{
+ const struct midx_pack_key *key = va;
+ struct multi_pack_index *midx = key->midx;
+
+ uint32_t versus = pack_pos_to_midx(midx, (uint32_t*)vb - (const uint32_t *)midx->revindex_data);
+ uint32_t versus_pack = nth_midxed_pack_int_id(midx, versus);
+ off_t versus_offset;
+
+ uint32_t key_preferred = key->pack == key->preferred_pack;
+ uint32_t versus_preferred = versus_pack == key->preferred_pack;
+
+ /*
+ * First, compare the preferred-ness, noting that the preferred pack
+ * comes first.
+ */
+ if (key_preferred && !versus_preferred)
+ return -1;
+ else if (!key_preferred && versus_preferred)
+ return 1;
+
+ /* Then, break ties first by comparing the pack IDs. */
+ if (key->pack < versus_pack)
+ return -1;
+ else if (key->pack > versus_pack)
+ return 1;
+
+ /* Finally, break ties by comparing offsets within a pack. */
+ versus_offset = nth_midxed_offset(midx, versus);
+ if (key->offset < versus_offset)
+ return -1;
+ else if (key->offset > versus_offset)
+ return 1;
+
+ return 0;
+}
+
+int midx_to_pack_pos(struct multi_pack_index *m, uint32_t at, uint32_t *pos)
+{
+ struct midx_pack_key key;
+ uint32_t *found;
+
+ if (!m->revindex_data)
+ BUG("midx_to_pack_pos: reverse index not yet loaded");
+ if (m->num_objects <= at)
+ BUG("midx_to_pack_pos: out-of-bounds object at %"PRIu32, at);
+
+ key.pack = nth_midxed_pack_int_id(m, at);
+ key.offset = nth_midxed_offset(m, at);
+ key.midx = m;
+ /*
+ * The preferred pack sorts first, so determine its identifier by
+ * looking at the first object in pseudo-pack order.
+ *
+ * Note that if no --preferred-pack is explicitly given when writing a
+ * multi-pack index, then whichever pack has the lowest identifier
+ * implicitly is preferred (and includes all its objects, since ties are
+ * broken first by pack identifier).
+ */
+ key.preferred_pack = nth_midxed_pack_int_id(m, pack_pos_to_midx(m, 0));
+
+ found = bsearch(&key, m->revindex_data, m->num_objects,
+ sizeof(*m->revindex_data), midx_pack_order_cmp);
+
+ if (!found)
+ return error("bad offset for revindex");
+
+ *pos = found - m->revindex_data;
+ return 0;
+}
diff --git a/pack-revindex.h b/pack-revindex.h
index ba7c82c125..479b8f2f9c 100644
--- a/pack-revindex.h
+++ b/pack-revindex.h
@@ -14,6 +14,20 @@
*
* - offset: the byte offset within the .pack file at which the object contents
* can be found
+ *
+ * The revindex can also be used with a multi-pack index (MIDX). In this
+ * setting:
+ *
+ * - index position refers to an object's numeric position within the MIDX
+ *
+ * - pack position refers to an object's position within a non-existent pack
+ * described by the MIDX. The pack structure is described in
+ * Documentation/technical/pack-format.txt.
+ *
+ * It is effectively a concatanation of all packs in the MIDX (ordered by
+ * their numeric ID within the MIDX) in their original order within each
+ * pack), removing duplicates, and placing the preferred pack (if any)
+ * first.
*/
@@ -24,6 +38,7 @@
#define GIT_TEST_REV_INDEX_DIE_IN_MEMORY "GIT_TEST_REV_INDEX_DIE_IN_MEMORY"
struct packed_git;
+struct multi_pack_index;
/*
* load_pack_revindex populates the revindex's internal data-structures for the
@@ -35,6 +50,22 @@ struct packed_git;
int load_pack_revindex(struct packed_git *p);
/*
+ * load_midx_revindex loads the '.rev' file corresponding to the given
+ * multi-pack index by mmap-ing it and assigning pointers in the
+ * multi_pack_index to point at it.
+ *
+ * A negative number is returned on error.
+ */
+int load_midx_revindex(struct multi_pack_index *m);
+
+/*
+ * Frees resources associated with a multi-pack reverse index.
+ *
+ * A negative number is returned on error.
+ */
+int close_midx_revindex(struct multi_pack_index *m);
+
+/*
* offset_to_pack_pos converts an object offset to a pack position. This
* function returns zero on success, and a negative number otherwise. The
* parameter 'pos' is usable only on success.
@@ -71,4 +102,26 @@ uint32_t pack_pos_to_index(struct packed_git *p, uint32_t pos);
*/
off_t pack_pos_to_offset(struct packed_git *p, uint32_t pos);
+/*
+ * pack_pos_to_midx converts the object at position "pos" within the MIDX
+ * pseudo-pack into a MIDX position.
+ *
+ * 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.
+ */
+uint32_t pack_pos_to_midx(struct multi_pack_index *m, uint32_t pos);
+
+/*
+ * midx_to_pack_pos converts from the MIDX-relative position at "at" to the
+ * corresponding pack position.
+ *
+ * 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.
+ */
+int midx_to_pack_pos(struct multi_pack_index *midx, uint32_t at, uint32_t *pos);
+
#endif
diff --git a/pack-write.c b/pack-write.c
index 2ca85a9d16..f1fc3ecafa 100644
--- a/pack-write.c
+++ b/pack-write.c
@@ -201,21 +201,12 @@ static void write_rev_header(struct hashfile *f)
}
static void write_rev_index_positions(struct hashfile *f,
- struct pack_idx_entry **objects,
+ uint32_t *pack_order,
uint32_t nr_objects)
{
- uint32_t *pack_order;
uint32_t i;
-
- ALLOC_ARRAY(pack_order, nr_objects);
- for (i = 0; i < nr_objects; i++)
- pack_order[i] = i;
- QSORT_S(pack_order, nr_objects, pack_order_cmp, objects);
-
for (i = 0; i < nr_objects; i++)
hashwrite_be32(f, pack_order[i]);
-
- free(pack_order);
}
static void write_rev_trailer(struct hashfile *f, const unsigned char *hash)
@@ -229,6 +220,29 @@ const char *write_rev_file(const char *rev_name,
const unsigned char *hash,
unsigned flags)
{
+ uint32_t *pack_order;
+ uint32_t i;
+ const char *ret;
+
+ ALLOC_ARRAY(pack_order, nr_objects);
+ for (i = 0; i < nr_objects; i++)
+ pack_order[i] = i;
+ QSORT_S(pack_order, nr_objects, pack_order_cmp, objects);
+
+ ret = write_rev_file_order(rev_name, pack_order, nr_objects, hash,
+ flags);
+
+ free(pack_order);
+
+ return ret;
+}
+
+const char *write_rev_file_order(const char *rev_name,
+ uint32_t *pack_order,
+ uint32_t nr_objects,
+ const unsigned char *hash,
+ unsigned flags)
+{
struct hashfile *f;
int fd;
@@ -262,7 +276,7 @@ const char *write_rev_file(const char *rev_name,
write_rev_header(f);
- write_rev_index_positions(f, objects, nr_objects);
+ write_rev_index_positions(f, pack_order, nr_objects);
write_rev_trailer(f, hash);
if (rev_name && adjust_shared_perm(rev_name) < 0)
diff --git a/pack.h b/pack.h
index 857cbd5bd4..fa13954526 100644
--- a/pack.h
+++ b/pack.h
@@ -94,6 +94,7 @@ struct ref;
void write_promisor_file(const char *promisor_name, struct ref **sought, int nr_sought);
const char *write_rev_file(const char *rev_name, struct pack_idx_entry **objects, uint32_t nr_objects, const unsigned char *hash, unsigned flags);
+const char *write_rev_file_order(const char *rev_name, uint32_t *pack_order, uint32_t nr_objects, const unsigned char *hash, unsigned flags);
/*
* The "hdr" output buffer should be at least this big, which will handle sizes
diff --git a/packfile.c b/packfile.c
index 1fec12ac5f..755aa7aec5 100644
--- a/packfile.c
+++ b/packfile.c
@@ -638,7 +638,7 @@ unsigned char *use_pack(struct packed_git *p,
if (p->pack_fd == -1 && open_packed_git(p))
die("packfile %s cannot be accessed", p->pack_name);
- win = xcalloc(1, sizeof(*win));
+ CALLOC_ARRAY(win, 1);
win->offset = (offset / window_align) * window_align;
len = p->pack_size - win->offset;
if (len > packed_git_window_size)
@@ -813,10 +813,7 @@ void for_each_file_in_pack_dir(const char *objdir,
}
strbuf_addch(&path, '/');
dirnamelen = path.len;
- while ((de = readdir(dir)) != NULL) {
- if (is_dot_or_dotdot(de->d_name))
- continue;
-
+ while ((de = readdir_skip_dot_and_dotdot(dir)) != NULL) {
strbuf_setlen(&path, dirnamelen);
strbuf_addstr(&path, de->d_name);
@@ -862,6 +859,9 @@ static void prepare_pack(const char *full_name, size_t full_name_len,
if (!strcmp(file_name, "multi-pack-index"))
return;
+ if (starts_with(file_name, "multi-pack-index") &&
+ ends_with(file_name, ".rev"))
+ return;
if (ends_with(file_name, ".idx") ||
ends_with(file_name, ".rev") ||
ends_with(file_name, ".pack") ||
@@ -2066,12 +2066,79 @@ int find_pack_entry(struct repository *r, const struct object_id *oid, struct pa
return 0;
}
+static void maybe_invalidate_kept_pack_cache(struct repository *r,
+ unsigned flags)
+{
+ if (!r->objects->kept_pack_cache.packs)
+ return;
+ if (r->objects->kept_pack_cache.flags == flags)
+ return;
+ FREE_AND_NULL(r->objects->kept_pack_cache.packs);
+ r->objects->kept_pack_cache.flags = 0;
+}
+
+static struct packed_git **kept_pack_cache(struct repository *r, unsigned flags)
+{
+ maybe_invalidate_kept_pack_cache(r, flags);
+
+ if (!r->objects->kept_pack_cache.packs) {
+ struct packed_git **packs = NULL;
+ size_t nr = 0, alloc = 0;
+ struct packed_git *p;
+
+ /*
+ * We want "all" packs here, because we need to cover ones that
+ * are used by a midx, as well. We need to look in every one of
+ * them (instead of the midx itself) to cover duplicates. It's
+ * possible that an object is found in two packs that the midx
+ * covers, one kept and one not kept, but the midx returns only
+ * the non-kept version.
+ */
+ for (p = get_all_packs(r); p; p = p->next) {
+ if ((p->pack_keep && (flags & ON_DISK_KEEP_PACKS)) ||
+ (p->pack_keep_in_core && (flags & IN_CORE_KEEP_PACKS))) {
+ ALLOC_GROW(packs, nr + 1, alloc);
+ packs[nr++] = p;
+ }
+ }
+ ALLOC_GROW(packs, nr + 1, alloc);
+ packs[nr] = NULL;
+
+ r->objects->kept_pack_cache.packs = packs;
+ r->objects->kept_pack_cache.flags = flags;
+ }
+
+ return r->objects->kept_pack_cache.packs;
+}
+
+int find_kept_pack_entry(struct repository *r,
+ const struct object_id *oid,
+ unsigned flags,
+ struct pack_entry *e)
+{
+ struct packed_git **cache;
+
+ for (cache = kept_pack_cache(r, flags); *cache; cache++) {
+ struct packed_git *p = *cache;
+ if (fill_pack_entry(oid, e, p))
+ return 1;
+ }
+
+ return 0;
+}
+
int has_object_pack(const struct object_id *oid)
{
struct pack_entry e;
return find_pack_entry(the_repository, oid, &e);
}
+int has_object_kept_pack(const struct object_id *oid, unsigned flags)
+{
+ struct pack_entry e;
+ return find_kept_pack_entry(the_repository, oid, flags, &e);
+}
+
int has_pack_index(const unsigned char *sha1)
{
struct stat st;
@@ -2177,6 +2244,7 @@ static int add_promisor_object(const struct object_id *oid,
return 0;
while (tree_entry_gently(&desc, &entry))
oidset_insert(set, &entry.oid);
+ free_tree_buffer(tree);
} else if (obj->type == OBJ_COMMIT) {
struct commit *commit = (struct commit *) obj;
struct commit_list *parents = commit->parents;
diff --git a/packfile.h b/packfile.h
index 4cfec9e8d3..3ae117a8ae 100644
--- a/packfile.h
+++ b/packfile.h
@@ -162,13 +162,18 @@ int packed_object_info(struct repository *r,
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);
+#define ON_DISK_KEEP_PACKS 1
+#define IN_CORE_KEEP_PACKS 2
+
/*
* Iff a pack file in the given repository contains the object named by sha1,
* return true and store its location to e.
*/
int find_pack_entry(struct repository *r, const struct object_id *oid, struct pack_entry *e);
+int find_kept_pack_entry(struct repository *r, const struct object_id *oid, unsigned flags, struct pack_entry *e);
int has_object_pack(const struct object_id *oid);
+int has_object_kept_pack(const struct object_id *oid, unsigned flags);
int has_pack_index(const unsigned char *sha1);
diff --git a/parallel-checkout.c b/parallel-checkout.c
new file mode 100644
index 0000000000..6b1af32bb3
--- /dev/null
+++ b/parallel-checkout.c
@@ -0,0 +1,673 @@
+#include "cache.h"
+#include "config.h"
+#include "entry.h"
+#include "parallel-checkout.h"
+#include "pkt-line.h"
+#include "progress.h"
+#include "run-command.h"
+#include "sigchain.h"
+#include "streaming.h"
+#include "thread-utils.h"
+#include "trace2.h"
+
+struct pc_worker {
+ struct child_process cp;
+ size_t next_item_to_complete, nr_items_to_complete;
+};
+
+struct parallel_checkout {
+ enum pc_status status;
+ struct parallel_checkout_item *items; /* The parallel checkout queue. */
+ size_t nr, alloc;
+ struct progress *progress;
+ unsigned int *progress_cnt;
+};
+
+static struct parallel_checkout parallel_checkout;
+
+enum pc_status parallel_checkout_status(void)
+{
+ return parallel_checkout.status;
+}
+
+static const int DEFAULT_THRESHOLD_FOR_PARALLELISM = 100;
+static const int DEFAULT_NUM_WORKERS = 1;
+
+void get_parallel_checkout_configs(int *num_workers, int *threshold)
+{
+ char *env_workers = getenv("GIT_TEST_CHECKOUT_WORKERS");
+
+ if (env_workers && *env_workers) {
+ if (strtol_i(env_workers, 10, num_workers)) {
+ die("invalid value for GIT_TEST_CHECKOUT_WORKERS: '%s'",
+ env_workers);
+ }
+ if (*num_workers < 1)
+ *num_workers = online_cpus();
+
+ *threshold = 0;
+ return;
+ }
+
+ if (git_config_get_int("checkout.workers", num_workers))
+ *num_workers = DEFAULT_NUM_WORKERS;
+ else if (*num_workers < 1)
+ *num_workers = online_cpus();
+
+ if (git_config_get_int("checkout.thresholdForParallelism", threshold))
+ *threshold = DEFAULT_THRESHOLD_FOR_PARALLELISM;
+}
+
+void init_parallel_checkout(void)
+{
+ if (parallel_checkout.status != PC_UNINITIALIZED)
+ BUG("parallel checkout already initialized");
+
+ parallel_checkout.status = PC_ACCEPTING_ENTRIES;
+}
+
+static void finish_parallel_checkout(void)
+{
+ if (parallel_checkout.status == PC_UNINITIALIZED)
+ BUG("cannot finish parallel checkout: not initialized yet");
+
+ free(parallel_checkout.items);
+ memset(&parallel_checkout, 0, sizeof(parallel_checkout));
+}
+
+static int is_eligible_for_parallel_checkout(const struct cache_entry *ce,
+ const struct conv_attrs *ca)
+{
+ enum conv_attrs_classification c;
+ size_t packed_item_size;
+
+ /*
+ * Symlinks cannot be checked out in parallel as, in case of path
+ * collision, they could racily replace leading directories of other
+ * entries being checked out. Submodules are checked out in child
+ * processes, which have their own parallel checkout queues.
+ */
+ if (!S_ISREG(ce->ce_mode))
+ return 0;
+
+ packed_item_size = sizeof(struct pc_item_fixed_portion) + ce->ce_namelen +
+ (ca->working_tree_encoding ? strlen(ca->working_tree_encoding) : 0);
+
+ /*
+ * The amount of data we send to the workers per checkout item is
+ * typically small (75~300B). So unless we find an insanely huge path
+ * of 64KB, we should never reach the 65KB limit of one pkt-line. If
+ * that does happen, we let the sequential code handle the item.
+ */
+ if (packed_item_size > LARGE_PACKET_DATA_MAX)
+ return 0;
+
+ c = classify_conv_attrs(ca);
+ switch (c) {
+ case CA_CLASS_INCORE:
+ return 1;
+
+ case CA_CLASS_INCORE_FILTER:
+ /*
+ * It would be safe to allow concurrent instances of
+ * single-file smudge filters, like rot13, but we should not
+ * assume that all filters are parallel-process safe. So we
+ * don't allow this.
+ */
+ return 0;
+
+ case CA_CLASS_INCORE_PROCESS:
+ /*
+ * The parallel queue and the delayed queue are not compatible,
+ * so they must be kept completely separated. And we can't tell
+ * if a long-running process will delay its response without
+ * actually asking it to perform the filtering. Therefore, this
+ * type of filter is not allowed in parallel checkout.
+ *
+ * Furthermore, there should only be one instance of the
+ * long-running process filter as we don't know how it is
+ * managing its own concurrency. So, spreading the entries that
+ * requisite such a filter among the parallel workers would
+ * require a lot more inter-process communication. We would
+ * probably have to designate a single process to interact with
+ * the filter and send all the necessary data to it, for each
+ * entry.
+ */
+ return 0;
+
+ case CA_CLASS_STREAMABLE:
+ return 1;
+
+ default:
+ BUG("unsupported conv_attrs classification '%d'", c);
+ }
+}
+
+int enqueue_checkout(struct cache_entry *ce, struct conv_attrs *ca)
+{
+ struct parallel_checkout_item *pc_item;
+
+ if (parallel_checkout.status != PC_ACCEPTING_ENTRIES ||
+ !is_eligible_for_parallel_checkout(ce, ca))
+ return -1;
+
+ ALLOC_GROW(parallel_checkout.items, parallel_checkout.nr + 1,
+ parallel_checkout.alloc);
+
+ pc_item = &parallel_checkout.items[parallel_checkout.nr];
+ pc_item->ce = ce;
+ memcpy(&pc_item->ca, ca, sizeof(pc_item->ca));
+ pc_item->status = PC_ITEM_PENDING;
+ pc_item->id = parallel_checkout.nr;
+ parallel_checkout.nr++;
+
+ return 0;
+}
+
+size_t pc_queue_size(void)
+{
+ return parallel_checkout.nr;
+}
+
+static void advance_progress_meter(void)
+{
+ if (parallel_checkout.progress) {
+ (*parallel_checkout.progress_cnt)++;
+ display_progress(parallel_checkout.progress,
+ *parallel_checkout.progress_cnt);
+ }
+}
+
+static int handle_results(struct checkout *state)
+{
+ int ret = 0;
+ size_t i;
+ int have_pending = 0;
+
+ /*
+ * We first update the successfully written entries with the collected
+ * stat() data, so that they can be found by mark_colliding_entries(),
+ * in the next loop, when necessary.
+ */
+ for (i = 0; i < parallel_checkout.nr; i++) {
+ struct parallel_checkout_item *pc_item = &parallel_checkout.items[i];
+ if (pc_item->status == PC_ITEM_WRITTEN)
+ update_ce_after_write(state, pc_item->ce, &pc_item->st);
+ }
+
+ for (i = 0; i < parallel_checkout.nr; i++) {
+ struct parallel_checkout_item *pc_item = &parallel_checkout.items[i];
+
+ switch(pc_item->status) {
+ case PC_ITEM_WRITTEN:
+ /* Already handled */
+ break;
+ case PC_ITEM_COLLIDED:
+ /*
+ * The entry could not be checked out due to a path
+ * collision with another entry. Since there can only
+ * be one entry of each colliding group on the disk, we
+ * could skip trying to check out this one and move on.
+ * However, this would leave the unwritten entries with
+ * null stat() fields on the index, which could
+ * potentially slow down subsequent operations that
+ * require refreshing it: git would not be able to
+ * trust st_size and would have to go to the filesystem
+ * to see if the contents match (see ie_modified()).
+ *
+ * Instead, let's pay the overhead only once, now, and
+ * call checkout_entry_ca() again for this file, to
+ * have its stat() data stored in the index. This also
+ * has the benefit of adding this entry and its
+ * colliding pair to the collision report message.
+ * Additionally, this overwriting behavior is consistent
+ * with what the sequential checkout does, so it doesn't
+ * add any extra overhead.
+ */
+ ret |= checkout_entry_ca(pc_item->ce, &pc_item->ca,
+ state, NULL, NULL);
+ advance_progress_meter();
+ break;
+ case PC_ITEM_PENDING:
+ have_pending = 1;
+ /* fall through */
+ case PC_ITEM_FAILED:
+ ret = -1;
+ break;
+ default:
+ BUG("unknown checkout item status in parallel checkout");
+ }
+ }
+
+ if (have_pending)
+ error("parallel checkout finished with pending entries");
+
+ return ret;
+}
+
+static int reset_fd(int fd, const char *path)
+{
+ if (lseek(fd, 0, SEEK_SET) != 0)
+ return error_errno("failed to rewind descriptor of '%s'", path);
+ if (ftruncate(fd, 0))
+ return error_errno("failed to truncate file '%s'", path);
+ return 0;
+}
+
+static int write_pc_item_to_fd(struct parallel_checkout_item *pc_item, int fd,
+ const char *path)
+{
+ int ret;
+ struct stream_filter *filter;
+ struct strbuf buf = STRBUF_INIT;
+ char *blob;
+ unsigned long size;
+ ssize_t wrote;
+
+ /* Sanity check */
+ assert(is_eligible_for_parallel_checkout(pc_item->ce, &pc_item->ca));
+
+ filter = get_stream_filter_ca(&pc_item->ca, &pc_item->ce->oid);
+ if (filter) {
+ if (stream_blob_to_fd(fd, &pc_item->ce->oid, filter, 1)) {
+ /* On error, reset fd to try writing without streaming */
+ if (reset_fd(fd, path))
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+
+ blob = read_blob_entry(pc_item->ce, &size);
+ if (!blob)
+ return error("cannot read object %s '%s'",
+ oid_to_hex(&pc_item->ce->oid), pc_item->ce->name);
+
+ /*
+ * checkout metadata is used to give context for external process
+ * filters. Files requiring such filters are not eligible for parallel
+ * checkout, so pass NULL. Note: if that changes, the metadata must also
+ * be passed from the main process to the workers.
+ */
+ ret = convert_to_working_tree_ca(&pc_item->ca, pc_item->ce->name,
+ blob, size, &buf, NULL);
+
+ if (ret) {
+ size_t newsize;
+ free(blob);
+ blob = strbuf_detach(&buf, &newsize);
+ size = newsize;
+ }
+
+ wrote = write_in_full(fd, blob, size);
+ free(blob);
+ if (wrote < 0)
+ return error("unable to write file '%s'", path);
+
+ return 0;
+}
+
+static int close_and_clear(int *fd)
+{
+ int ret = 0;
+
+ if (*fd >= 0) {
+ ret = close(*fd);
+ *fd = -1;
+ }
+
+ return ret;
+}
+
+void write_pc_item(struct parallel_checkout_item *pc_item,
+ struct checkout *state)
+{
+ unsigned int mode = (pc_item->ce->ce_mode & 0100) ? 0777 : 0666;
+ int fd = -1, fstat_done = 0;
+ struct strbuf path = STRBUF_INIT;
+ const char *dir_sep;
+
+ strbuf_add(&path, state->base_dir, state->base_dir_len);
+ strbuf_add(&path, pc_item->ce->name, pc_item->ce->ce_namelen);
+
+ dir_sep = find_last_dir_sep(path.buf);
+
+ /*
+ * The leading dirs should have been already created by now. But, in
+ * case of path collisions, one of the dirs could have been replaced by
+ * a symlink (checked out after we enqueued this entry for parallel
+ * checkout). Thus, we must check the leading dirs again.
+ */
+ if (dir_sep && !has_dirs_only_path(path.buf, dir_sep - path.buf,
+ state->base_dir_len)) {
+ pc_item->status = PC_ITEM_COLLIDED;
+ trace2_data_string("pcheckout", NULL, "collision/dirname", path.buf);
+ goto out;
+ }
+
+ fd = open(path.buf, O_WRONLY | O_CREAT | O_EXCL, mode);
+
+ if (fd < 0) {
+ if (errno == EEXIST || errno == EISDIR) {
+ /*
+ * Errors which probably represent a path collision.
+ * Suppress the error message and mark the item to be
+ * retried later, sequentially. ENOTDIR and ENOENT are
+ * also interesting, but the above has_dirs_only_path()
+ * call should have already caught these cases.
+ */
+ pc_item->status = PC_ITEM_COLLIDED;
+ trace2_data_string("pcheckout", NULL,
+ "collision/basename", path.buf);
+ } else {
+ error_errno("failed to open file '%s'", path.buf);
+ pc_item->status = PC_ITEM_FAILED;
+ }
+ goto out;
+ }
+
+ if (write_pc_item_to_fd(pc_item, fd, path.buf)) {
+ /* Error was already reported. */
+ pc_item->status = PC_ITEM_FAILED;
+ close_and_clear(&fd);
+ unlink(path.buf);
+ goto out;
+ }
+
+ fstat_done = fstat_checkout_output(fd, state, &pc_item->st);
+
+ if (close_and_clear(&fd)) {
+ error_errno("unable to close file '%s'", path.buf);
+ pc_item->status = PC_ITEM_FAILED;
+ goto out;
+ }
+
+ if (state->refresh_cache && !fstat_done && lstat(path.buf, &pc_item->st) < 0) {
+ error_errno("unable to stat just-written file '%s'", path.buf);
+ pc_item->status = PC_ITEM_FAILED;
+ goto out;
+ }
+
+ pc_item->status = PC_ITEM_WRITTEN;
+
+out:
+ strbuf_release(&path);
+}
+
+static void send_one_item(int fd, struct parallel_checkout_item *pc_item)
+{
+ size_t len_data;
+ char *data, *variant;
+ struct pc_item_fixed_portion *fixed_portion;
+ const char *working_tree_encoding = pc_item->ca.working_tree_encoding;
+ size_t name_len = pc_item->ce->ce_namelen;
+ size_t working_tree_encoding_len = working_tree_encoding ?
+ strlen(working_tree_encoding) : 0;
+
+ /*
+ * Any changes in the calculation of the message size must also be made
+ * in is_eligible_for_parallel_checkout().
+ */
+ len_data = sizeof(struct pc_item_fixed_portion) + name_len +
+ working_tree_encoding_len;
+
+ data = xcalloc(1, len_data);
+
+ fixed_portion = (struct pc_item_fixed_portion *)data;
+ fixed_portion->id = pc_item->id;
+ fixed_portion->ce_mode = pc_item->ce->ce_mode;
+ fixed_portion->crlf_action = pc_item->ca.crlf_action;
+ fixed_portion->ident = pc_item->ca.ident;
+ fixed_portion->name_len = name_len;
+ fixed_portion->working_tree_encoding_len = working_tree_encoding_len;
+ /*
+ * We use hashcpy() instead of oidcpy() because the hash[] positions
+ * after `the_hash_algo->rawsz` might not be initialized. And Valgrind
+ * would complain about passing uninitialized bytes to a syscall
+ * (write(2)). There is no real harm in this case, but the warning could
+ * hinder the detection of actual errors.
+ */
+ hashcpy(fixed_portion->oid.hash, pc_item->ce->oid.hash);
+
+ variant = data + sizeof(*fixed_portion);
+ if (working_tree_encoding_len) {
+ memcpy(variant, working_tree_encoding, working_tree_encoding_len);
+ variant += working_tree_encoding_len;
+ }
+ memcpy(variant, pc_item->ce->name, name_len);
+
+ packet_write(fd, data, len_data);
+
+ free(data);
+}
+
+static void send_batch(int fd, size_t start, size_t nr)
+{
+ size_t i;
+ sigchain_push(SIGPIPE, SIG_IGN);
+ for (i = 0; i < nr; i++)
+ send_one_item(fd, &parallel_checkout.items[start + i]);
+ packet_flush(fd);
+ sigchain_pop(SIGPIPE);
+}
+
+static struct pc_worker *setup_workers(struct checkout *state, int num_workers)
+{
+ struct pc_worker *workers;
+ int i, workers_with_one_extra_item;
+ size_t base_batch_size, batch_beginning = 0;
+
+ ALLOC_ARRAY(workers, num_workers);
+
+ for (i = 0; i < num_workers; i++) {
+ struct child_process *cp = &workers[i].cp;
+
+ child_process_init(cp);
+ cp->git_cmd = 1;
+ cp->in = -1;
+ cp->out = -1;
+ cp->clean_on_exit = 1;
+ strvec_push(&cp->args, "checkout--worker");
+ if (state->base_dir_len)
+ strvec_pushf(&cp->args, "--prefix=%s", state->base_dir);
+ if (start_command(cp))
+ die("failed to spawn checkout worker");
+ }
+
+ base_batch_size = parallel_checkout.nr / num_workers;
+ workers_with_one_extra_item = parallel_checkout.nr % num_workers;
+
+ for (i = 0; i < num_workers; i++) {
+ struct pc_worker *worker = &workers[i];
+ size_t batch_size = base_batch_size;
+
+ /* distribute the extra work evenly */
+ if (i < workers_with_one_extra_item)
+ batch_size++;
+
+ send_batch(worker->cp.in, batch_beginning, batch_size);
+ worker->next_item_to_complete = batch_beginning;
+ worker->nr_items_to_complete = batch_size;
+
+ batch_beginning += batch_size;
+ }
+
+ return workers;
+}
+
+static void finish_workers(struct pc_worker *workers, int num_workers)
+{
+ int i;
+
+ /*
+ * Close pipes before calling finish_command() to let the workers
+ * exit asynchronously and avoid spending extra time on wait().
+ */
+ for (i = 0; i < num_workers; i++) {
+ struct child_process *cp = &workers[i].cp;
+ if (cp->in >= 0)
+ close(cp->in);
+ if (cp->out >= 0)
+ close(cp->out);
+ }
+
+ for (i = 0; i < num_workers; i++) {
+ int rc = finish_command(&workers[i].cp);
+ if (rc > 128) {
+ /*
+ * For a normal non-zero exit, the worker should have
+ * already printed something useful to stderr. But a
+ * death by signal should be mentioned to the user.
+ */
+ error("checkout worker %d died of signal %d", i, rc - 128);
+ }
+ }
+
+ free(workers);
+}
+
+static inline void assert_pc_item_result_size(int got, int exp)
+{
+ if (got != exp)
+ BUG("wrong result size from checkout worker (got %dB, exp %dB)",
+ got, exp);
+}
+
+static void parse_and_save_result(const char *buffer, int len,
+ struct pc_worker *worker)
+{
+ struct pc_item_result *res;
+ struct parallel_checkout_item *pc_item;
+ struct stat *st = NULL;
+
+ if (len < PC_ITEM_RESULT_BASE_SIZE)
+ BUG("too short result from checkout worker (got %dB, exp >=%dB)",
+ len, (int)PC_ITEM_RESULT_BASE_SIZE);
+
+ res = (struct pc_item_result *)buffer;
+
+ /*
+ * Worker should send either the full result struct on success, or
+ * just the base (i.e. no stat data), otherwise.
+ */
+ if (res->status == PC_ITEM_WRITTEN) {
+ assert_pc_item_result_size(len, (int)sizeof(struct pc_item_result));
+ st = &res->st;
+ } else {
+ assert_pc_item_result_size(len, (int)PC_ITEM_RESULT_BASE_SIZE);
+ }
+
+ if (!worker->nr_items_to_complete)
+ BUG("received result from supposedly finished checkout worker");
+ if (res->id != worker->next_item_to_complete)
+ BUG("unexpected item id from checkout worker (got %"PRIuMAX", exp %"PRIuMAX")",
+ (uintmax_t)res->id, (uintmax_t)worker->next_item_to_complete);
+
+ worker->next_item_to_complete++;
+ worker->nr_items_to_complete--;
+
+ pc_item = &parallel_checkout.items[res->id];
+ pc_item->status = res->status;
+ if (st)
+ pc_item->st = *st;
+
+ if (res->status != PC_ITEM_COLLIDED)
+ advance_progress_meter();
+}
+
+static void gather_results_from_workers(struct pc_worker *workers,
+ int num_workers)
+{
+ int i, active_workers = num_workers;
+ struct pollfd *pfds;
+
+ CALLOC_ARRAY(pfds, num_workers);
+ for (i = 0; i < num_workers; i++) {
+ pfds[i].fd = workers[i].cp.out;
+ pfds[i].events = POLLIN;
+ }
+
+ while (active_workers) {
+ int nr = poll(pfds, num_workers, -1);
+
+ if (nr < 0) {
+ if (errno == EINTR)
+ continue;
+ die_errno("failed to poll checkout workers");
+ }
+
+ for (i = 0; i < num_workers && nr > 0; i++) {
+ struct pc_worker *worker = &workers[i];
+ struct pollfd *pfd = &pfds[i];
+
+ if (!pfd->revents)
+ continue;
+
+ if (pfd->revents & POLLIN) {
+ int len = packet_read(pfd->fd, NULL, NULL,
+ packet_buffer,
+ sizeof(packet_buffer), 0);
+
+ if (len < 0) {
+ BUG("packet_read() returned negative value");
+ } else if (!len) {
+ pfd->fd = -1;
+ active_workers--;
+ } else {
+ parse_and_save_result(packet_buffer,
+ len, worker);
+ }
+ } else if (pfd->revents & POLLHUP) {
+ pfd->fd = -1;
+ active_workers--;
+ } else if (pfd->revents & (POLLNVAL | POLLERR)) {
+ die("error polling from checkout worker");
+ }
+
+ nr--;
+ }
+ }
+
+ free(pfds);
+}
+
+static void write_items_sequentially(struct checkout *state)
+{
+ size_t i;
+
+ for (i = 0; i < parallel_checkout.nr; i++) {
+ struct parallel_checkout_item *pc_item = &parallel_checkout.items[i];
+ write_pc_item(pc_item, state);
+ if (pc_item->status != PC_ITEM_COLLIDED)
+ advance_progress_meter();
+ }
+}
+
+int run_parallel_checkout(struct checkout *state, int num_workers, int threshold,
+ struct progress *progress, unsigned int *progress_cnt)
+{
+ int ret;
+
+ if (parallel_checkout.status != PC_ACCEPTING_ENTRIES)
+ BUG("cannot run parallel checkout: uninitialized or already running");
+
+ parallel_checkout.status = PC_RUNNING;
+ parallel_checkout.progress = progress;
+ parallel_checkout.progress_cnt = progress_cnt;
+
+ if (parallel_checkout.nr < num_workers)
+ num_workers = parallel_checkout.nr;
+
+ if (num_workers <= 1 || parallel_checkout.nr < threshold) {
+ write_items_sequentially(state);
+ } else {
+ struct pc_worker *workers = setup_workers(state, num_workers);
+ gather_results_from_workers(workers, num_workers);
+ finish_workers(workers, num_workers);
+ }
+
+ ret = handle_results(state);
+
+ finish_parallel_checkout();
+ return ret;
+}
diff --git a/parallel-checkout.h b/parallel-checkout.h
new file mode 100644
index 0000000000..80f539bcb7
--- /dev/null
+++ b/parallel-checkout.h
@@ -0,0 +1,111 @@
+#ifndef PARALLEL_CHECKOUT_H
+#define PARALLEL_CHECKOUT_H
+
+#include "convert.h"
+
+struct cache_entry;
+struct checkout;
+struct progress;
+
+/****************************************************************
+ * Users of parallel checkout
+ ****************************************************************/
+
+enum pc_status {
+ PC_UNINITIALIZED = 0,
+ PC_ACCEPTING_ENTRIES,
+ PC_RUNNING,
+};
+
+enum pc_status parallel_checkout_status(void);
+void get_parallel_checkout_configs(int *num_workers, int *threshold);
+
+/*
+ * Put parallel checkout into the PC_ACCEPTING_ENTRIES state. Should be used
+ * only when in the PC_UNINITIALIZED state.
+ */
+void init_parallel_checkout(void);
+
+/*
+ * Return -1 if parallel checkout is currently not accepting entries or if the
+ * entry is not eligible for parallel checkout. Otherwise, enqueue the entry
+ * for later write and return 0.
+ */
+int enqueue_checkout(struct cache_entry *ce, struct conv_attrs *ca);
+size_t pc_queue_size(void);
+
+/*
+ * Write all the queued entries, returning 0 on success. If the number of
+ * entries is smaller than the specified threshold, the operation is performed
+ * sequentially.
+ */
+int run_parallel_checkout(struct checkout *state, int num_workers, int threshold,
+ struct progress *progress, unsigned int *progress_cnt);
+
+/****************************************************************
+ * Interface with checkout--worker
+ ****************************************************************/
+
+enum pc_item_status {
+ PC_ITEM_PENDING = 0,
+ PC_ITEM_WRITTEN,
+ /*
+ * The entry could not be written because there was another file
+ * already present in its path or leading directories. Since
+ * checkout_entry_ca() removes such files from the working tree before
+ * enqueueing the entry for parallel checkout, it means that there was
+ * a path collision among the entries being written.
+ */
+ PC_ITEM_COLLIDED,
+ PC_ITEM_FAILED,
+};
+
+struct parallel_checkout_item {
+ /*
+ * In main process ce points to a istate->cache[] entry. Thus, it's not
+ * owned by us. In workers they own the memory, which *must be* released.
+ */
+ struct cache_entry *ce;
+ struct conv_attrs ca;
+ size_t id; /* position in parallel_checkout.items[] of main process */
+
+ /* Output fields, sent from workers. */
+ enum pc_item_status status;
+ struct stat st;
+};
+
+/*
+ * The fixed-size portion of `struct parallel_checkout_item` that is sent to the
+ * workers. Following this will be 2 strings: ca.working_tree_encoding and
+ * ce.name; These are NOT null terminated, since we have the size in the fixed
+ * portion.
+ *
+ * Note that not all fields of conv_attrs and cache_entry are passed, only the
+ * ones that will be required by the workers to smudge and write the entry.
+ */
+struct pc_item_fixed_portion {
+ size_t id;
+ struct object_id oid;
+ unsigned int ce_mode;
+ enum convert_crlf_action crlf_action;
+ int ident;
+ size_t working_tree_encoding_len;
+ size_t name_len;
+};
+
+/*
+ * The fields of `struct parallel_checkout_item` that are returned by the
+ * workers. Note: `st` must be the last one, as it is omitted on error.
+ */
+struct pc_item_result {
+ size_t id;
+ enum pc_item_status status;
+ struct stat st;
+};
+
+#define PC_ITEM_RESULT_BASE_SIZE offsetof(struct pc_item_result, st)
+
+void write_pc_item(struct parallel_checkout_item *pc_item,
+ struct checkout *state);
+
+#endif /* PARALLEL_CHECKOUT_H */
diff --git a/parse-options-cb.c b/parse-options-cb.c
index 4542d4d3f9..3c811e1e4a 100644
--- a/parse-options-cb.c
+++ b/parse-options-cb.c
@@ -140,7 +140,7 @@ int parse_opt_object_id(const struct option *opt, const char *arg, int unset)
struct object_id *target = opt->value;
if (unset) {
- *target = null_oid;
+ oidcpy(target, null_oid());
return 0;
}
if (!arg)
diff --git a/parse-options.c b/parse-options.c
index fbea16eaf5..e6f56768ca 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -625,6 +625,8 @@ static int show_gitcomp(const struct option *opts, int show_all)
*
* Right now this is only used to preprocess and substitute
* OPTION_ALIAS.
+ *
+ * The returned options should be freed using free_preprocessed_options.
*/
static struct option *preprocess_options(struct parse_opt_ctx_t *ctx,
const struct option *options)
@@ -678,6 +680,7 @@ static struct option *preprocess_options(struct parse_opt_ctx_t *ctx,
newopt[i].short_name = short_name;
newopt[i].long_name = long_name;
newopt[i].help = strbuf_detach(&help, NULL);
+ newopt[i].flags |= PARSE_OPT_FROM_ALIAS;
break;
}
@@ -693,6 +696,20 @@ static struct option *preprocess_options(struct parse_opt_ctx_t *ctx,
return newopt;
}
+static void free_preprocessed_options(struct option *options)
+{
+ int i;
+
+ if (!options)
+ return;
+
+ for (i = 0; options[i].type != OPTION_END; i++) {
+ if (options[i].flags & PARSE_OPT_FROM_ALIAS)
+ free((void *)options[i].help);
+ }
+ free(options);
+}
+
static int usage_with_options_internal(struct parse_opt_ctx_t *,
const char * const *,
const struct option *, int, int);
@@ -870,7 +887,7 @@ int parse_options(int argc, const char **argv, const char *prefix,
}
precompose_argv_prefix(argc, argv, NULL);
- free(real_options);
+ free_preprocessed_options(real_options);
free(ctx.alias_groups);
return parse_options_end(&ctx);
}
diff --git a/parse-options.h b/parse-options.h
index ff6506a504..a845a9d952 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -28,26 +28,27 @@ enum parse_opt_type {
};
enum parse_opt_flags {
- PARSE_OPT_KEEP_DASHDASH = 1,
- PARSE_OPT_STOP_AT_NON_OPTION = 2,
- PARSE_OPT_KEEP_ARGV0 = 4,
- PARSE_OPT_KEEP_UNKNOWN = 8,
- PARSE_OPT_NO_INTERNAL_HELP = 16,
- PARSE_OPT_ONE_SHOT = 32
+ PARSE_OPT_KEEP_DASHDASH = 1 << 0,
+ PARSE_OPT_STOP_AT_NON_OPTION = 1 << 1,
+ PARSE_OPT_KEEP_ARGV0 = 1 << 2,
+ PARSE_OPT_KEEP_UNKNOWN = 1 << 3,
+ PARSE_OPT_NO_INTERNAL_HELP = 1 << 4,
+ PARSE_OPT_ONE_SHOT = 1 << 5,
};
enum parse_opt_option_flags {
- PARSE_OPT_OPTARG = 1,
- PARSE_OPT_NOARG = 2,
- PARSE_OPT_NONEG = 4,
- PARSE_OPT_HIDDEN = 8,
- PARSE_OPT_LASTARG_DEFAULT = 16,
- PARSE_OPT_NODASH = 32,
- PARSE_OPT_LITERAL_ARGHELP = 64,
- PARSE_OPT_SHELL_EVAL = 256,
- PARSE_OPT_NOCOMPLETE = 512,
- PARSE_OPT_COMP_ARG = 1024,
- PARSE_OPT_CMDMODE = 2048
+ PARSE_OPT_OPTARG = 1 << 0,
+ PARSE_OPT_NOARG = 1 << 1,
+ PARSE_OPT_NONEG = 1 << 2,
+ PARSE_OPT_HIDDEN = 1 << 3,
+ PARSE_OPT_LASTARG_DEFAULT = 1 << 4,
+ PARSE_OPT_NODASH = 1 << 5,
+ PARSE_OPT_LITERAL_ARGHELP = 1 << 6,
+ PARSE_OPT_FROM_ALIAS = 1 << 7,
+ PARSE_OPT_SHELL_EVAL = 1 << 8,
+ PARSE_OPT_NOCOMPLETE = 1 << 9,
+ PARSE_OPT_COMP_ARG = 1 << 10,
+ PARSE_OPT_CMDMODE = 1 << 11,
};
enum parse_opt_result {
diff --git a/patch-ids.c b/patch-ids.c
index 3f404e4b0b..8bf425555d 100644
--- a/patch-ids.c
+++ b/patch-ids.c
@@ -124,7 +124,7 @@ struct patch_id *add_commit_patch_id(struct commit *commit,
if (!patch_id_defined(commit))
return NULL;
- key = xcalloc(1, sizeof(*key));
+ CALLOC_ARRAY(key, 1);
if (init_patch_id_entry(key, commit, ids)) {
free(key);
return NULL;
diff --git a/path.c b/path.c
index 7b385e5eb2..7bccd830e9 100644
--- a/path.c
+++ b/path.c
@@ -1493,6 +1493,11 @@ int is_ntfs_dotgitattributes(const char *name)
return is_ntfs_dot_str(name, "gitattributes", "gi7d29");
}
+int is_ntfs_dotmailmap(const char *name)
+{
+ return is_ntfs_dot_str(name, "mailmap", "maba30");
+}
+
int looks_like_command_line_option(const char *str)
{
return str && str[0] == '-';
@@ -1534,5 +1539,6 @@ REPO_GIT_PATH_FUNC(merge_rr, "MERGE_RR")
REPO_GIT_PATH_FUNC(merge_mode, "MERGE_MODE")
REPO_GIT_PATH_FUNC(merge_head, "MERGE_HEAD")
REPO_GIT_PATH_FUNC(merge_autostash, "MERGE_AUTOSTASH")
+REPO_GIT_PATH_FUNC(auto_merge, "AUTO_MERGE")
REPO_GIT_PATH_FUNC(fetch_head, "FETCH_HEAD")
REPO_GIT_PATH_FUNC(shallow, "shallow")
diff --git a/path.h b/path.h
index e7e77da6aa..251c78d980 100644
--- a/path.h
+++ b/path.h
@@ -176,6 +176,7 @@ struct path_cache {
const char *merge_mode;
const char *merge_head;
const char *merge_autostash;
+ const char *auto_merge;
const char *fetch_head;
const char *shallow;
};
@@ -191,6 +192,7 @@ const char *git_path_merge_rr(struct repository *r);
const char *git_path_merge_mode(struct repository *r);
const char *git_path_merge_head(struct repository *r);
const char *git_path_merge_autostash(struct repository *r);
+const char *git_path_auto_merge(struct repository *r);
const char *git_path_fetch_head(struct repository *r);
const char *git_path_shallow(struct repository *r);
diff --git a/pathspec.c b/pathspec.c
index 7a229d8d22..08f8d3eedc 100644
--- a/pathspec.c
+++ b/pathspec.c
@@ -20,8 +20,9 @@
* to use find_pathspecs_matching_against_index() instead.
*/
void add_pathspec_matches_against_index(const struct pathspec *pathspec,
- const struct index_state *istate,
- char *seen)
+ struct index_state *istate,
+ char *seen,
+ enum ps_skip_worktree_action sw_action)
{
int num_unmatched = 0, i;
@@ -36,8 +37,12 @@ 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))
+ continue;
ce_path_match(istate, ce, pathspec, seen);
}
}
@@ -51,10 +56,26 @@ void add_pathspec_matches_against_index(const struct pathspec *pathspec,
* given pathspecs achieves against all items in the index.
*/
char *find_pathspecs_matching_against_index(const struct pathspec *pathspec,
- const struct index_state *istate)
+ struct index_state *istate,
+ enum ps_skip_worktree_action sw_action)
+{
+ char *seen = xcalloc(pathspec->nr, 1);
+ add_pathspec_matches_against_index(pathspec, istate, seen, sw_action);
+ return seen;
+}
+
+char *find_pathspecs_matching_skip_worktree(const struct pathspec *pathspec)
{
+ struct index_state *istate = the_repository->index;
char *seen = xcalloc(pathspec->nr, 1);
- add_pathspec_matches_against_index(pathspec, istate, seen);
+ int i;
+
+ for (i = 0; i < istate->cache_nr; i++) {
+ struct cache_entry *ce = istate->cache[i];
+ if (ce_skip_worktree(ce))
+ ce_path_match(istate, ce, pathspec, seen);
+ }
+
return seen;
}
@@ -154,7 +175,7 @@ static void parse_pathspec_attr_match(struct pathspec_item *item, const char *va
string_list_remove_empty_items(&list, 0);
item->attr_check = attr_check_alloc();
- item->attr_match = xcalloc(list.nr, sizeof(struct attr_match));
+ CALLOC_ARRAY(item->attr_match, list.nr);
for_each_string_list_item(si, &list) {
size_t attr_len;
@@ -561,7 +582,7 @@ void parse_pathspec(struct pathspec *pathspec,
if (!(flags & PATHSPEC_PREFER_CWD))
BUG("PATHSPEC_PREFER_CWD requires arguments");
- pathspec->items = item = xcalloc(1, sizeof(*item));
+ pathspec->items = CALLOC_ARRAY(item, 1);
item->match = xstrdup(prefix);
item->original = xstrdup(prefix);
item->nowildcard_len = item->len = strlen(prefix);
@@ -702,7 +723,7 @@ void clear_pathspec(struct pathspec *pathspec)
pathspec->nr = 0;
}
-int match_pathspec_attrs(const struct index_state *istate,
+int match_pathspec_attrs(struct index_state *istate,
const char *name, int namelen,
const struct pathspec_item *item)
{
diff --git a/pathspec.h b/pathspec.h
index 454ce364fa..fceebb876f 100644
--- a/pathspec.h
+++ b/pathspec.h
@@ -149,12 +149,26 @@ static inline int ps_strcmp(const struct pathspec_item *item,
return strcmp(s1, s2);
}
+enum ps_skip_worktree_action {
+ PS_HEED_SKIP_WORKTREE = 0,
+ PS_IGNORE_SKIP_WORKTREE = 1
+};
void add_pathspec_matches_against_index(const struct pathspec *pathspec,
- const struct index_state *istate,
- char *seen);
+ struct index_state *istate,
+ char *seen,
+ enum ps_skip_worktree_action sw_action);
char *find_pathspecs_matching_against_index(const struct pathspec *pathspec,
- const struct index_state *istate);
-int match_pathspec_attrs(const struct index_state *istate,
+ struct index_state *istate,
+ enum ps_skip_worktree_action sw_action);
+char *find_pathspecs_matching_skip_worktree(const struct pathspec *pathspec);
+static inline int matches_skip_worktree(const struct pathspec *pathspec,
+ int item, char **seen_ptr)
+{
+ if (!*seen_ptr)
+ *seen_ptr = find_pathspecs_matching_skip_worktree(pathspec);
+ return (*seen_ptr)[item];
+}
+int match_pathspec_attrs(struct index_state *istate,
const char *name, int namelen,
const struct pathspec_item *item);
diff --git a/perl/Git/I18N.pm b/perl/Git/I18N.pm
index 2037f387c8..895e759c57 100644
--- a/perl/Git/I18N.pm
+++ b/perl/Git/I18N.pm
@@ -16,9 +16,19 @@ BEGIN {
our @EXPORT = qw(__ __n N__);
our @EXPORT_OK = @EXPORT;
+# See Git::LoadCPAN's NO_PERL_CPAN_FALLBACKS_STR for a description of
+# this "'@@' [...] '@@'" pattern.
+use constant NO_GETTEXT_STR => '@@' . 'NO_GETTEXT' . '@@';
+use constant NO_GETTEXT => (
+ q[@@NO_GETTEXT@@] ne ''
+ and
+ q[@@NO_GETTEXT@@] ne NO_GETTEXT_STR
+);
+
sub __bootstrap_locale_messages {
our $TEXTDOMAIN = 'git';
our $TEXTDOMAINDIR ||= $ENV{GIT_TEXTDOMAINDIR} || '@@LOCALEDIR@@';
+ die "NO_GETTEXT=" . NO_GETTEXT_STR if NO_GETTEXT;
require POSIX;
POSIX->import(qw(setlocale));
diff --git a/pkt-line.c b/pkt-line.c
index d633005ef7..98304ce374 100644
--- a/pkt-line.c
+++ b/pkt-line.c
@@ -194,27 +194,53 @@ int packet_write_fmt_gently(int fd, const char *fmt, ...)
return status;
}
-static int packet_write_gently(const int fd_out, const char *buf, size_t size)
+static int do_packet_write(const int fd_out, const char *buf, size_t size,
+ struct strbuf *err)
{
- static char packet_write_buffer[LARGE_PACKET_MAX];
+ char header[4];
size_t packet_size;
- if (size > sizeof(packet_write_buffer) - 4)
- return error(_("packet write failed - data exceeds max packet size"));
+ if (size > LARGE_PACKET_DATA_MAX) {
+ strbuf_addstr(err, _("packet write failed - data exceeds max packet size"));
+ return -1;
+ }
packet_trace(buf, size, 1);
packet_size = size + 4;
- set_packet_header(packet_write_buffer, packet_size);
- memcpy(packet_write_buffer + 4, buf, size);
- if (write_in_full(fd_out, packet_write_buffer, packet_size) < 0)
- return error(_("packet write failed"));
+
+ set_packet_header(header, packet_size);
+
+ /*
+ * Write the header and the buffer in 2 parts so that we do
+ * not need to allocate a buffer or rely on a static buffer.
+ * This also avoids putting a large buffer on the stack which
+ * might have multi-threading issues.
+ */
+
+ if (write_in_full(fd_out, header, 4) < 0 ||
+ write_in_full(fd_out, buf, size) < 0) {
+ strbuf_addf(err, _("packet write failed: %s"), strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+static int packet_write_gently(const int fd_out, const char *buf, size_t size)
+{
+ struct strbuf err = STRBUF_INIT;
+ if (do_packet_write(fd_out, buf, size, &err)) {
+ error("%s", err.buf);
+ strbuf_release(&err);
+ return -1;
+ }
return 0;
}
void packet_write(int fd_out, const char *buf, size_t size)
{
- if (packet_write_gently(fd_out, buf, size))
- die_errno(_("packet write failed"));
+ struct strbuf err = STRBUF_INIT;
+ if (do_packet_write(fd_out, buf, size, &err))
+ die("%s", err.buf);
}
void packet_buf_write(struct strbuf *buf, const char *fmt, ...)
@@ -242,26 +268,27 @@ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)
packet_trace(data, len, 1);
}
-int write_packetized_from_fd(int fd_in, int fd_out)
+int write_packetized_from_fd_no_flush(int fd_in, int fd_out)
{
- static char buf[LARGE_PACKET_DATA_MAX];
+ char *buf = xmalloc(LARGE_PACKET_DATA_MAX);
int err = 0;
ssize_t bytes_to_write;
while (!err) {
- bytes_to_write = xread(fd_in, buf, sizeof(buf));
- if (bytes_to_write < 0)
+ bytes_to_write = xread(fd_in, buf, LARGE_PACKET_DATA_MAX);
+ if (bytes_to_write < 0) {
+ free(buf);
return COPY_READ_ERROR;
+ }
if (bytes_to_write == 0)
break;
err = packet_write_gently(fd_out, buf, bytes_to_write);
}
- if (!err)
- err = packet_flush_gently(fd_out);
+ free(buf);
return err;
}
-int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)
+int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out)
{
int err = 0;
size_t bytes_written = 0;
@@ -277,8 +304,6 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)
err = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write);
bytes_written += bytes_to_write;
}
- if (!err)
- err = packet_flush_gently(fd_out);
return err;
}
@@ -298,8 +323,11 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,
*src_size -= ret;
} else {
ret = read_in_full(fd, dst, size);
- if (ret < 0)
+ if (ret < 0) {
+ if (options & PACKET_READ_GENTLE_ON_READ_ERROR)
+ return error_errno(_("read error"));
die_errno(_("read error"));
+ }
}
/* And complain if we didn't get enough bytes to satisfy the read. */
@@ -307,6 +335,8 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,
if (options & PACKET_READ_GENTLE_ON_EOF)
return -1;
+ if (options & PACKET_READ_GENTLE_ON_READ_ERROR)
+ return error(_("the remote end hung up unexpectedly"));
die(_("the remote end hung up unexpectedly"));
}
@@ -335,6 +365,9 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer,
len = packet_length(linelen);
if (len < 0) {
+ if (options & PACKET_READ_GENTLE_ON_READ_ERROR)
+ return error(_("protocol error: bad line length "
+ "character: %.4s"), linelen);
die(_("protocol error: bad line length character: %.4s"), linelen);
} else if (!len) {
packet_trace("0000", 4, 0);
@@ -349,12 +382,19 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer,
*pktlen = 0;
return PACKET_READ_RESPONSE_END;
} else if (len < 4) {
+ if (options & PACKET_READ_GENTLE_ON_READ_ERROR)
+ return error(_("protocol error: bad line length %d"),
+ len);
die(_("protocol error: bad line length %d"), len);
}
len -= 4;
- if ((unsigned)len >= size)
+ if ((unsigned)len >= size) {
+ if (options & PACKET_READ_GENTLE_ON_READ_ERROR)
+ return error(_("protocol error: bad line length %d"),
+ len);
die(_("protocol error: bad line length %d"), len);
+ }
if (get_packet_data(fd, src_buffer, src_len, buffer, len, options) < 0) {
*pktlen = -1;
@@ -421,7 +461,7 @@ char *packet_read_line_buf(char **src, size_t *src_len, int *dst_len)
return packet_read_line_generic(-1, src, src_len, dst_len);
}
-ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)
+ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out, int options)
{
int packet_len;
@@ -437,7 +477,7 @@ ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)
* that there is already room for the extra byte.
*/
sb_out->buf + sb_out->len, LARGE_PACKET_DATA_MAX+1,
- PACKET_READ_GENTLE_ON_EOF);
+ options);
if (packet_len <= 0)
break;
sb_out->len += packet_len;
diff --git a/pkt-line.h b/pkt-line.h
index 8c90daa59e..5af5f45687 100644
--- a/pkt-line.h
+++ b/pkt-line.h
@@ -32,8 +32,8 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((f
void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len);
int packet_flush_gently(int fd);
int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
-int write_packetized_from_fd(int fd_in, int fd_out);
-int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);
+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);
/*
* Read a packetized line into the buffer, which must be at least size bytes
@@ -68,10 +68,15 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);
*
* If options contains PACKET_READ_DIE_ON_ERR_PACKET, it dies when it sees an
* ERR packet.
+ *
+ * If options contains PACKET_READ_GENTLE_ON_READ_ERROR, we will not die
+ * on read errors, but instead return -1. However, we may still die on an
+ * ERR packet (if requested).
*/
-#define PACKET_READ_GENTLE_ON_EOF (1u<<0)
-#define PACKET_READ_CHOMP_NEWLINE (1u<<1)
-#define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2)
+#define PACKET_READ_GENTLE_ON_EOF (1u<<0)
+#define PACKET_READ_CHOMP_NEWLINE (1u<<1)
+#define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2)
+#define PACKET_READ_GENTLE_ON_READ_ERROR (1u<<3)
int packet_read(int fd, char **src_buffer, size_t *src_len, char
*buffer, unsigned size, int options);
@@ -131,7 +136,7 @@ char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size);
/*
* Reads a stream of variable sized packets until a flush packet is detected.
*/
-ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out);
+ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out, int options);
/*
* Receive multiplexed output stream over git native protocol.
diff --git a/pretty.c b/pretty.c
index c5f5ecc40d..b1ecd039ce 100644
--- a/pretty.c
+++ b/pretty.c
@@ -12,6 +12,7 @@
#include "reflog-walk.h"
#include "gpg-interface.h"
#include "trailer.h"
+#include "run-command.h"
static char *user_format;
static struct cmt_fmt_map {
@@ -678,7 +679,7 @@ static int mailmap_name(const char **email, size_t *email_len,
{
static struct string_list *mail_map;
if (!mail_map) {
- mail_map = xcalloc(1, sizeof(*mail_map));
+ CALLOC_ARRAY(mail_map, 1);
read_mailmap(mail_map);
}
return mail_map->nr && map_user(mail_map, email, email_len, name, name_len);
@@ -744,6 +745,9 @@ static size_t format_person_part(struct strbuf *sb, char part,
case 'I': /* date, ISO 8601 strict */
strbuf_addstr(sb, show_ident_date(&s, DATE_MODE(ISO8601_STRICT)));
return placeholder_len;
+ case 'h': /* date, human */
+ strbuf_addstr(sb, show_ident_date(&s, DATE_MODE(HUMAN)));
+ return placeholder_len;
case 's':
strbuf_addstr(sb, show_ident_date(&s, DATE_MODE(SHORT)));
return placeholder_len;
@@ -1206,6 +1210,34 @@ int format_set_trailers_options(struct process_trailer_options *opts,
return 0;
}
+static size_t parse_describe_args(const char *start, struct strvec *args)
+{
+ const char *options[] = { "match", "exclude" };
+ const char *arg = start;
+
+ for (;;) {
+ const char *matched = NULL;
+ const char *argval;
+ size_t arglen = 0;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(options); i++) {
+ if (match_placeholder_arg_value(arg, options[i], &arg,
+ &argval, &arglen)) {
+ matched = options[i];
+ break;
+ }
+ }
+ if (!matched)
+ break;
+
+ if (!arglen)
+ return 0;
+ strvec_pushf(args, "--%s=%.*s", matched, (int)arglen, argval);
+ }
+ return arg - start;
+}
+
static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
const char *placeholder,
void *context)
@@ -1271,6 +1303,41 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
return parse_padding_placeholder(placeholder, c);
}
+ if (skip_prefix(placeholder, "(describe", &arg)) {
+ struct child_process cmd = CHILD_PROCESS_INIT;
+ struct strbuf out = STRBUF_INIT;
+ struct strbuf err = STRBUF_INIT;
+ struct pretty_print_describe_status *describe_status;
+
+ describe_status = c->pretty_ctx->describe_status;
+ if (describe_status) {
+ if (!describe_status->max_invocations)
+ return 0;
+ describe_status->max_invocations--;
+ }
+
+ cmd.git_cmd = 1;
+ strvec_push(&cmd.args, "describe");
+
+ if (*arg == ':') {
+ arg++;
+ arg += parse_describe_args(arg, &cmd.args);
+ }
+
+ if (*arg != ')') {
+ child_process_clear(&cmd);
+ return 0;
+ }
+
+ strvec_push(&cmd.args, oid_to_hex(&commit->object.oid));
+ pipe_command(&cmd, NULL, 0, &out, 0, &err, 0);
+ strbuf_rtrim(&out);
+ strbuf_addbuf(sb, &out);
+ strbuf_release(&out);
+ strbuf_release(&err);
+ return arg - placeholder + 1;
+ }
+
/* these depend on the commit */
if (!commit->object.parsed)
parse_object(the_repository, &commit->object.oid);
diff --git a/pretty.h b/pretty.h
index d902cdd70a..f034609e4d 100644
--- a/pretty.h
+++ b/pretty.h
@@ -24,6 +24,10 @@ enum cmit_fmt {
CMIT_FMT_UNSPECIFIED
};
+struct pretty_print_describe_status {
+ unsigned int max_invocations;
+};
+
struct pretty_print_context {
/*
* Callers should tweak these to change the behavior of pp_* functions.
@@ -45,6 +49,7 @@ struct pretty_print_context {
int color;
struct ident_split *from_ident;
unsigned encode_email_headers:1;
+ struct pretty_print_describe_status *describe_status;
/*
* Fields below here are manipulated internally by pp_* functions and
diff --git a/progress.c b/progress.c
index 31014e6fca..680c6a8bf9 100644
--- a/progress.c
+++ b/progress.c
@@ -196,7 +196,7 @@ void display_throughput(struct progress *progress, uint64_t total)
now_ns = progress_getnanotime(progress);
if (!tp) {
- progress->throughput = tp = xcalloc(1, sizeof(*tp));
+ progress->throughput = CALLOC_ARRAY(tp, 1);
tp->prev_total = tp->curr_total = total;
tp->prev_ns = now_ns;
strbuf_init(&tp->display, 0);
diff --git a/promisor-remote.c b/promisor-remote.c
index 3c572b1c81..da3f2ca261 100644
--- a/promisor-remote.c
+++ b/promisor-remote.c
@@ -208,7 +208,7 @@ static int remove_fetched_oids(struct repository *repo,
if (remaining_nr) {
int j = 0;
- new_oids = xcalloc(remaining_nr, sizeof(*new_oids));
+ CALLOC_ARRAY(new_oids, remaining_nr);
for (i = 0; i < oid_nr; i++)
if (remaining[i])
oidcpy(&new_oids[j++], &old_oids[i]);
diff --git a/protocol-caps.c b/protocol-caps.c
new file mode 100644
index 0000000000..13a9e63a04
--- /dev/null
+++ b/protocol-caps.c
@@ -0,0 +1,113 @@
+#include "git-compat-util.h"
+#include "protocol-caps.h"
+#include "gettext.h"
+#include "pkt-line.h"
+#include "strvec.h"
+#include "hash.h"
+#include "object.h"
+#include "object-store.h"
+#include "string-list.h"
+#include "strbuf.h"
+
+struct requested_info {
+ unsigned size : 1;
+};
+
+/*
+ * Parses oids from the given line and collects them in the given
+ * oid_str_list. Returns 1 if parsing was successful and 0 otherwise.
+ */
+static int parse_oid(const char *line, struct string_list *oid_str_list)
+{
+ const char *arg;
+
+ if (!skip_prefix(line, "oid ", &arg))
+ return 0;
+
+ string_list_append(oid_str_list, arg);
+
+ return 1;
+}
+
+/*
+ * Validates and send requested info back to the client. Any errors detected
+ * are returned as they are detected.
+ */
+static void send_info(struct repository *r, struct packet_writer *writer,
+ struct string_list *oid_str_list,
+ struct requested_info *info)
+{
+ struct string_list_item *item;
+ struct strbuf send_buffer = STRBUF_INIT;
+
+ if (!oid_str_list->nr)
+ return;
+
+ if (info->size)
+ packet_writer_write(writer, "size");
+
+ for_each_string_list_item (item, oid_str_list) {
+ const char *oid_str = item->string;
+ struct object_id oid;
+ unsigned long object_size;
+
+ if (get_oid_hex(oid_str, &oid) < 0) {
+ packet_writer_error(
+ writer,
+ "object-info: protocol error, expected to get oid, not '%s'",
+ oid_str);
+ continue;
+ }
+
+ strbuf_addstr(&send_buffer, oid_str);
+
+ if (info->size) {
+ if (oid_object_info(r, &oid, &object_size) < 0) {
+ strbuf_addstr(&send_buffer, " ");
+ } else {
+ strbuf_addf(&send_buffer, " %lu", object_size);
+ }
+ }
+
+ packet_writer_write(writer, "%s",
+ strbuf_detach(&send_buffer, NULL));
+ }
+}
+
+int cap_object_info(struct repository *r, struct strvec *keys,
+ struct packet_reader *request)
+{
+ struct requested_info info;
+ struct packet_writer writer;
+ struct string_list oid_str_list = STRING_LIST_INIT_DUP;
+
+ packet_writer_init(&writer, 1);
+
+ while (packet_reader_read(request) == PACKET_READ_NORMAL) {
+ if (!strcmp("size", request->line)) {
+ info.size = 1;
+ continue;
+ }
+
+ if (parse_oid(request->line, &oid_str_list))
+ continue;
+
+ packet_writer_error(&writer,
+ "object-info: unexpected line: '%s'",
+ request->line);
+ }
+
+ if (request->status != PACKET_READ_FLUSH) {
+ packet_writer_error(
+ &writer, "object-info: expected flush after arguments");
+ die(_("object-info: expected flush after arguments"));
+ }
+
+ send_info(r, &writer, &oid_str_list, &info);
+
+ string_list_clear(&oid_str_list, 1);
+
+ packet_flush(1);
+
+ return 0;
+}
diff --git a/protocol-caps.h b/protocol-caps.h
new file mode 100644
index 0000000000..6351648e37
--- /dev/null
+++ b/protocol-caps.h
@@ -0,0 +1,10 @@
+#ifndef PROTOCOL_CAPS_H
+#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);
+
+#endif /* PROTOCOL_CAPS_H */ \ No newline at end of file
diff --git a/range-diff.c b/range-diff.c
index a3cc7c94a3..1a4471fe4c 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -96,7 +96,7 @@ static int read_patches(const char *range, struct string_list *list,
string_list_append(list, buf.buf)->util = util;
strbuf_reset(&buf);
}
- util = xcalloc(sizeof(*util), 1);
+ CALLOC_ARRAY(util, 1);
if (get_oid(p, &util->oid)) {
error(_("could not parse commit '%s'"), p);
free(util);
@@ -445,7 +445,7 @@ static struct diff_filespec *get_filespec(const char *name, const char *p)
{
struct diff_filespec *spec = alloc_filespec(name);
- fill_filespec(spec, &null_oid, 0, 0100644);
+ fill_filespec(spec, null_oid(), 0, 0100644);
spec->data = (char *)p;
spec->size = strlen(p);
spec->should_munmap = 0;
diff --git a/reachable.c b/reachable.c
index 77a60c70a5..c59847257a 100644
--- a/reachable.c
+++ b/reachable.c
@@ -223,21 +223,16 @@ void mark_reachable_objects(struct rev_info *revs, int mark_reflog,
cp.progress = progress;
cp.count = 0;
- bitmap_git = prepare_bitmap_walk(revs, NULL);
+ bitmap_git = prepare_bitmap_walk(revs, NULL, 0);
if (bitmap_git) {
traverse_bitmap_commit_list(bitmap_git, revs, mark_object_seen);
free_bitmap_index(bitmap_git);
- return;
+ } else {
+ if (prepare_revision_walk(revs))
+ die("revision walk setup failed");
+ traverse_commit_list(revs, mark_commit, mark_object, &cp);
}
- /*
- * Set up the revision walk - this will move all commits
- * from the pending list to the commit walking list.
- */
- if (prepare_revision_walk(revs))
- die("revision walk setup failed");
- traverse_commit_list(revs, mark_commit, mark_object, &cp);
-
if (mark_recent) {
revs->ignore_missing_links = 1;
if (add_unseen_recent_objects_to_traversal(revs, mark_recent))
diff --git a/read-cache.c b/read-cache.c
index 1e9a50c6c7..1b3c2eb408 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -25,6 +25,7 @@
#include "fsmonitor.h"
#include "thread-utils.h"
#include "progress.h"
+#include "sparse-index.h"
/* Mask for the name length in ce_flags in the on-disk index */
@@ -47,6 +48,7 @@
#define CACHE_EXT_FSMONITOR 0x46534D4E /* "FSMN" */
#define CACHE_EXT_ENDOFINDEXENTRIES 0x454F4945 /* "EOIE" */
#define CACHE_EXT_INDEXENTRYOFFSETTABLE 0x49454F54 /* "IEOT" */
+#define CACHE_EXT_SPARSE_DIRECTORIES 0x73646972 /* "sdir" */
/* changes that can be kept in $GIT_DIR/index (basically all extensions) */
#define EXTMASK (RESOLVE_UNDO_CHANGED | CACHE_TREE_CHANGED | \
@@ -101,6 +103,9 @@ static const char *alternate_index_output;
static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)
{
+ if (S_ISSPARSEDIR(ce->ce_mode))
+ istate->sparse_index = 1;
+
istate->cache[nr] = ce;
add_name_hash(istate, ce);
}
@@ -544,7 +549,7 @@ int cache_name_stage_compare(const char *name1, int len1, int stage1, const char
return 0;
}
-static int index_name_stage_pos(const struct index_state *istate, const char *name, int namelen, int stage)
+static int index_name_stage_pos(struct index_state *istate, const char *name, int namelen, int stage)
{
int first, last;
@@ -562,10 +567,31 @@ static int index_name_stage_pos(const struct index_state *istate, const char *na
}
first = next+1;
}
+
+ if (istate->sparse_index &&
+ first > 0) {
+ /* Note: first <= istate->cache_nr */
+ struct cache_entry *ce = istate->cache[first - 1];
+
+ /*
+ * If we are in a sparse-index _and_ the entry before the
+ * insertion position is a sparse-directory entry that is
+ * an ancestor of 'name', then we need to expand the index
+ * and search again. This will only trigger once, because
+ * thereafter the index is fully expanded.
+ */
+ if (S_ISSPARSEDIR(ce->ce_mode) &&
+ ce_namelen(ce) < namelen &&
+ !strncmp(name, ce->name, ce_namelen(ce))) {
+ ensure_full_index(istate);
+ return index_name_stage_pos(istate, name, namelen, stage);
+ }
+ }
+
return -first-1;
}
-int index_name_pos(const struct index_state *istate, const char *name, int namelen)
+int index_name_pos(struct index_state *istate, const char *name, int namelen)
{
return index_name_stage_pos(istate, name, namelen, 0);
}
@@ -813,8 +839,11 @@ struct cache_entry *make_empty_cache_entry(struct index_state *istate, size_t le
return mem_pool__ce_calloc(find_mem_pool(istate), len);
}
-struct cache_entry *make_empty_transient_cache_entry(size_t len)
+struct cache_entry *make_empty_transient_cache_entry(size_t len,
+ struct mem_pool *ce_mem_pool)
{
+ if (ce_mem_pool)
+ return mem_pool__ce_calloc(ce_mem_pool, len);
return xcalloc(1, cache_entry_size(len));
}
@@ -848,8 +877,11 @@ struct cache_entry *make_cache_entry(struct index_state *istate,
return ret;
}
-struct cache_entry *make_transient_cache_entry(unsigned int mode, const struct object_id *oid,
- const char *path, int stage)
+struct cache_entry *make_transient_cache_entry(unsigned int mode,
+ const struct object_id *oid,
+ const char *path,
+ int stage,
+ struct mem_pool *ce_mem_pool)
{
struct cache_entry *ce;
int len;
@@ -860,7 +892,7 @@ struct cache_entry *make_transient_cache_entry(unsigned int mode, const struct o
}
len = strlen(path);
- ce = make_empty_transient_cache_entry(len);
+ ce = make_empty_transient_cache_entry(len, ce_mem_pool);
oidcpy(&ce->oid, oid);
memcpy(ce->name, path, len);
@@ -985,7 +1017,7 @@ inside:
}
}
if (protect_ntfs) {
-#ifdef GIT_WINDOWS_NATIVE
+#if defined GIT_WINDOWS_NATIVE || defined __CYGWIN__
if (c == '\\')
return 0;
#endif
@@ -999,8 +1031,14 @@ inside:
c = *path++;
if ((c == '.' && !verify_dotfile(path, mode)) ||
- is_dir_sep(c) || c == '\0')
+ is_dir_sep(c))
return 0;
+ /*
+ * allow terminating directory separators for
+ * sparse directory entries.
+ */
+ if (c == '\0')
+ return S_ISDIR(mode);
} else if (c == '\\' && protect_ntfs) {
if (is_ntfs_dotgit(path))
return 0;
@@ -1514,6 +1552,7 @@ int refresh_index(struct index_state *istate, unsigned int flags,
int quiet = (flags & REFRESH_QUIET) != 0;
int not_new = (flags & REFRESH_IGNORE_MISSING) != 0;
int ignore_submodules = (flags & REFRESH_IGNORE_SUBMODULES) != 0;
+ int ignore_skip_worktree = (flags & REFRESH_IGNORE_SKIP_WORKTREE) != 0;
int first = 1;
int in_porcelain = (flags & REFRESH_IN_PORCELAIN);
unsigned int options = (CE_MATCH_REFRESH |
@@ -1545,6 +1584,8 @@ int refresh_index(struct index_state *istate, unsigned int flags,
*/
preload_index(istate, pathspec, 0);
trace2_region_enter("index", "refresh", NULL);
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(istate);
for (i = 0; i < istate->cache_nr; i++) {
struct cache_entry *ce, *new_entry;
int cache_errno = 0;
@@ -1556,6 +1597,8 @@ int refresh_index(struct index_state *istate, unsigned int flags,
ce = istate->cache[i];
if (ignore_submodules && S_ISGITLINK(ce->ce_mode))
continue;
+ if (ignore_skip_worktree && ce_skip_worktree(ce))
+ continue;
if (pathspec && !ce_path_match(istate, ce, pathspec, seen))
filtered = 1;
@@ -1760,6 +1803,10 @@ static int read_index_extension(struct index_state *istate,
case CACHE_EXT_INDEXENTRYOFFSETTABLE:
/* already handled in do_read_index() */
break;
+ case CACHE_EXT_SPARSE_DIRECTORIES:
+ /* no content, only an indicator */
+ istate->sparse_index = 1;
+ break;
default:
if (*ext < 'A' || 'Z' < *ext)
return error(_("index uses %.4s extension, which we do not understand"),
@@ -1845,7 +1892,7 @@ static struct cache_entry *create_from_disk(struct mem_pool *ce_mem_pool,
ce->ce_flags = flags & ~CE_NAMEMASK;
ce->ce_namelen = len;
ce->index = 0;
- hashcpy(ce->oid.hash, ondisk->data);
+ oidread(&ce->oid, ondisk->data);
memcpy(ce->name, name, len);
ce->name[len] = '\0';
@@ -2097,7 +2144,7 @@ static unsigned long load_cache_entries_threaded(struct index_state *istate, con
/* ensure we have no more threads than we have blocks to process */
if (nr_threads > ieot->nr)
nr_threads = ieot->nr;
- data = xcalloc(nr_threads, sizeof(*data));
+ CALLOC_ARRAY(data, nr_threads);
offset = ieot_start = 0;
ieot_blocks = DIV_ROUND_UP(ieot->nr, nr_threads);
@@ -2195,11 +2242,11 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)
if (verify_hdr(hdr, mmap_size) < 0)
goto unmap;
- hashcpy(istate->oid.hash, (const unsigned char *)hdr + mmap_size - the_hash_algo->rawsz);
+ oidread(&istate->oid, (const unsigned char *)hdr + mmap_size - the_hash_algo->rawsz);
istate->version = ntohl(hdr->hdr_version);
istate->cache_nr = ntohl(hdr->hdr_entries);
istate->cache_alloc = alloc_nr(istate->cache_nr);
- istate->cache = xcalloc(istate->cache_alloc, sizeof(*istate->cache));
+ CALLOC_ARRAY(istate->cache, istate->cache_alloc);
istate->initialized = 1;
p.istate = istate;
@@ -2273,6 +2320,12 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)
trace2_data_intmax("index", the_repository, "read/cache_nr",
istate->cache_nr);
+ if (!istate->repo)
+ istate->repo = the_repository;
+ prepare_repo_settings(istate->repo);
+ if (istate->repo->settings.command_requires_full_index)
+ ensure_full_index(istate);
+
return istate->cache_nr;
unmap:
@@ -2326,7 +2379,7 @@ int read_index_from(struct index_state *istate, const char *path,
if (split_index->base)
discard_index(split_index->base);
else
- split_index->base = xcalloc(1, sizeof(*split_index->base));
+ CALLOC_ARRAY(split_index->base, 1);
base_oid_hex = oid_to_hex(&split_index->base_oid);
base_path = xstrfmt("%s/sharedindex.%s", gitdir, base_oid_hex);
@@ -2373,6 +2426,7 @@ int discard_index(struct index_state *istate)
cache_tree_free(&(istate->cache_tree));
istate->initialized = 0;
istate->fsmonitor_has_run_once = 0;
+ FREE_AND_NULL(istate->fsmonitor_last_update);
FREE_AND_NULL(istate->cache);
istate->cache_alloc = 0;
discard_split_index(istate);
@@ -2456,6 +2510,8 @@ int repo_index_has_changes(struct repository *repo,
diff_flush(&opt);
return opt.flags.has_changes != 0;
} else {
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(istate);
for (i = 0; sb && i < istate->cache_nr; i++) {
if (i)
strbuf_addch(sb, ' ');
@@ -3011,6 +3067,10 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
if (err)
return -1;
}
+ if (istate->sparse_index) {
+ if (write_index_ext_header(&c, &eoie_c, newfd, CACHE_EXT_SPARSE_DIRECTORIES, 0) < 0)
+ return -1;
+ }
/*
* CACHE_EXT_ENDOFINDEXENTRIES must be written as the last entry before the SHA1
@@ -3070,6 +3130,14 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l
unsigned flags)
{
int ret;
+ int was_full = !istate->sparse_index;
+
+ ret = convert_to_sparse(istate);
+
+ if (ret) {
+ warning(_("failed to convert to a sparse-index"));
+ return ret;
+ }
/*
* TODO trace2: replace "the_repository" with the actual repo instance
@@ -3081,6 +3149,9 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l
trace2_region_leave_printf("index", "do_write_index", the_repository,
"%s", get_lock_file_path(lock));
+ if (was_full)
+ ensure_full_index(istate);
+
if (ret)
return ret;
if (flags & COMMIT_LOCK)
@@ -3171,9 +3242,10 @@ static int write_shared_index(struct index_state *istate,
struct tempfile **temp)
{
struct split_index *si = istate->split_index;
- int ret;
+ int ret, was_full = !istate->sparse_index;
move_cache_to_base_index(istate);
+ convert_to_sparse(istate);
trace2_region_enter_printf("index", "shared/do_write_index",
the_repository, "%s", get_tempfile_path(*temp));
@@ -3181,6 +3253,9 @@ static int write_shared_index(struct index_state *istate,
trace2_region_leave_printf("index", "shared/do_write_index",
the_repository, "%s", get_tempfile_path(*temp));
+ if (was_full)
+ ensure_full_index(istate);
+
if (ret)
return ret;
ret = adjust_shared_perm(get_tempfile_path(*temp));
@@ -3349,8 +3424,8 @@ int repo_read_index_unmerged(struct repository *repo)
* We helpfully remove a trailing "/" from directories so that
* the output of read_directory can be used as-is.
*/
-int index_name_is_other(const struct index_state *istate, const char *name,
- int namelen)
+int index_name_is_other(struct index_state *istate, const char *name,
+ int namelen)
{
int pos;
if (namelen && name[namelen - 1] == '/')
@@ -3368,7 +3443,7 @@ int index_name_is_other(const struct index_state *istate, const char *name,
return 1;
}
-void *read_blob_data_from_index(const struct index_state *istate,
+void *read_blob_data_from_index(struct index_state *istate,
const char *path, unsigned long *size)
{
int pos, len;
@@ -3427,7 +3502,7 @@ void stat_validity_update(struct stat_validity *sv, int fd)
stat_validity_clear(sv);
else {
if (!sv->sd)
- sv->sd = xcalloc(1, sizeof(struct stat_data));
+ CALLOC_ARRAY(sv->sd, 1);
fill_stat_data(sv->sd, &st);
}
}
diff --git a/rebase-interactive.c b/rebase-interactive.c
index 762853bc7e..b6cbd16a17 100644
--- a/rebase-interactive.c
+++ b/rebase-interactive.c
@@ -44,7 +44,10 @@ void append_todo_help(int command_count,
"r, reword <commit> = use commit, but edit the commit message\n"
"e, edit <commit> = use commit, but stop for amending\n"
"s, squash <commit> = use commit, but meld into previous commit\n"
-"f, fixup <commit> = like \"squash\", but discard this commit's log message\n"
+"f, fixup [-C | -c] <commit> = like \"squash\" but keep only the previous\n"
+" commit's log message, unless -C is used, in which case\n"
+" keep only this commit's message; -c is same as -C but\n"
+" opens the editor\n"
"x, exec <command> = run command (the rest of the line) using shell\n"
"b, break = stop here (continue rebase later with 'git rebase --continue')\n"
"d, drop <commit> = remove commit\n"
@@ -53,7 +56,7 @@ void append_todo_help(int command_count,
"m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\n"
". create a merge commit using the original merge commit's\n"
". message (or the oneline, if no original merge commit was\n"
-". specified). Use -c <commit> to reword the commit message.\n"
+". specified); use -c <commit> to reword the commit message\n"
"\n"
"These lines can be re-ordered; they are executed from top to bottom.\n");
unsigned edit_todo = !(shortrevisions && shortonto);
diff --git a/ref-filter.c b/ref-filter.c
index e84efb53db..97116e12d7 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -772,7 +772,8 @@ static int if_atom_handler(struct atom_value *atomv, struct ref_formatting_state
struct strbuf *unused_err)
{
struct ref_formatting_stack *new_stack;
- struct if_then_else *if_then_else = xcalloc(sizeof(struct if_then_else), 1);
+ struct if_then_else *if_then_else = xcalloc(1,
+ sizeof(struct if_then_else));
if_then_else->str = atomv->atom->u.if_then_else.str;
if_then_else->cmp_status = atomv->atom->u.if_then_else.cmp_status;
@@ -1607,7 +1608,7 @@ static int get_object(struct ref_array_item *ref, int deref, struct object **obj
if (oi->info.contentp) {
*obj = parse_object_buffer(the_repository, &oi->oid, oi->type, oi->size, oi->content, &eaten);
- if (!obj) {
+ if (!*obj) {
if (!eaten)
free(oi->content);
return strbuf_addf_ret(err, -1, _("parse_object_buffer failed on %s for %s"),
@@ -1676,7 +1677,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
int i;
struct object_info empty = OBJECT_INFO_INIT;
- ref->value = xcalloc(used_atom_cnt, sizeof(struct atom_value));
+ CALLOC_ARRAY(ref->value, used_atom_cnt);
if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) {
ref->symref = resolve_refdup(ref->refname, RESOLVE_REF_READING,
@@ -1729,7 +1730,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
else
v->s = xstrdup("");
continue;
- } else if (atom->u.remote_ref.push) {
+ } else if (!strcmp(atom->name, "push") || starts_with(atom->name, "push:")) {
const char *branch_name;
v->s = xstrdup("");
if (!skip_prefix(ref->refname, "refs/heads/",
@@ -2185,7 +2186,7 @@ static void reach_filter(struct ref_array *array,
if (!check_reachable)
return;
- to_clear = xcalloc(sizeof(struct commit *), array->nr);
+ CALLOC_ARRAY(to_clear, array->nr);
repo_init_revisions(the_repository, &revs, NULL);
@@ -2434,27 +2435,22 @@ int format_ref_array_item(struct ref_array_item *info,
return 0;
}
-void show_ref_array_item(struct ref_array_item *info,
- const struct ref_format *format)
-{
- struct strbuf final_buf = STRBUF_INIT;
- struct strbuf error_buf = STRBUF_INIT;
-
- if (format_ref_array_item(info, format, &final_buf, &error_buf))
- die("%s", error_buf.buf);
- fwrite(final_buf.buf, 1, final_buf.len, stdout);
- strbuf_release(&error_buf);
- strbuf_release(&final_buf);
- putchar('\n');
-}
-
void pretty_print_ref(const char *name, const struct object_id *oid,
const struct ref_format *format)
{
struct ref_array_item *ref_item;
+ struct strbuf output = STRBUF_INIT;
+ struct strbuf err = STRBUF_INIT;
+
ref_item = new_ref_array_item(name, oid);
ref_item->kind = ref_kind_from_refname(name);
- show_ref_array_item(ref_item, format);
+ if (format_ref_array_item(ref_item, format, &output, &err))
+ die("%s", err.buf);
+ fwrite(output.buf, 1, output.len, stdout);
+ putchar('\n');
+
+ strbuf_release(&err);
+ strbuf_release(&output);
free_array_item(ref_item);
}
@@ -2490,7 +2486,7 @@ void parse_ref_sorting(struct ref_sorting **sorting_tail, const char *arg)
{
struct ref_sorting *s;
- s = xcalloc(1, sizeof(*s));
+ CALLOC_ARRAY(s, 1);
s->next = *sorting_tail;
*sorting_tail = s;
diff --git a/ref-filter.h b/ref-filter.h
index 19ea4c4134..baf72a7189 100644
--- a/ref-filter.h
+++ b/ref-filter.h
@@ -119,8 +119,6 @@ int format_ref_array_item(struct ref_array_item *info,
const struct ref_format *format,
struct strbuf *final_buf,
struct strbuf *error_buf);
-/* Print the ref using the given format and quote_style */
-void show_ref_array_item(struct ref_array_item *info, const struct ref_format *format);
/* Parse a single sort specifier and add it to the list */
void parse_ref_sorting(struct ref_sorting **sorting_tail, const char *atom);
/* Callback function for parsing the sort option */
diff --git a/reflog-walk.c b/reflog-walk.c
index 3a25b27d8f..e9cd328369 100644
--- a/reflog-walk.c
+++ b/reflog-walk.c
@@ -112,7 +112,7 @@ struct reflog_walk_info {
void init_reflog_walk(struct reflog_walk_info **info)
{
- *info = xcalloc(1, sizeof(struct reflog_walk_info));
+ CALLOC_ARRAY(*info, 1);
(*info)->complete_reflogs.strdup_strings = 1;
}
@@ -181,7 +181,7 @@ int add_reflog_for_walk(struct reflog_walk_info *info,
}
free(branch);
- commit_reflog = xcalloc(1, sizeof(struct commit_reflog));
+ CALLOC_ARRAY(commit_reflog, 1);
if (recno < 0) {
commit_reflog->recno = get_reflog_recno_by_time(reflogs, timestamp);
if (commit_reflog->recno < 0) {
diff --git a/refs.c b/refs.c
index a665ed5e10..8c9490235e 100644
--- a/refs.c
+++ b/refs.c
@@ -337,7 +337,7 @@ static int filter_refs(const char *refname, const struct object_id *oid,
enum peel_status peel_object(const struct object_id *name, struct object_id *oid)
{
- struct object *o = lookup_unknown_object(name);
+ struct object *o = lookup_unknown_object(the_repository, name);
if (o->type == OBJ_NONE) {
int type = oid_object_info(the_repository, name, NULL);
@@ -1007,7 +1007,7 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,
struct ref_transaction *tr;
assert(err);
- tr = xcalloc(1, sizeof(struct ref_transaction));
+ CALLOC_ARRAY(tr, 1);
tr->ref_store = refs;
return tr;
}
@@ -1107,7 +1107,7 @@ int ref_transaction_create(struct ref_transaction *transaction,
if (!new_oid || is_null_oid(new_oid))
BUG("create called without valid new_oid");
return ref_transaction_update(transaction, refname, new_oid,
- &null_oid, flags, msg, err);
+ null_oid(), flags, msg, err);
}
int ref_transaction_delete(struct ref_transaction *transaction,
@@ -1119,7 +1119,7 @@ int ref_transaction_delete(struct ref_transaction *transaction,
if (old_oid && is_null_oid(old_oid))
BUG("delete called with old_oid set to zeros");
return ref_transaction_update(transaction, refname,
- &null_oid, old_oid,
+ null_oid(), old_oid,
flags, msg, err);
}
@@ -1306,7 +1306,7 @@ int parse_hide_refs_config(const char *var, const char *value, const char *secti
while (len && ref[len - 1] == '/')
ref[--len] = '\0';
if (!hide_refs) {
- hide_refs = xcalloc(1, sizeof(*hide_refs));
+ CALLOC_ARRAY(hide_refs, 1);
hide_refs->strdup_strings = 1;
}
string_list_append(hide_refs, ref);
diff --git a/refs/debug.c b/refs/debug.c
index 922e64fa6a..7db4abccc3 100644
--- a/refs/debug.c
+++ b/refs/debug.c
@@ -243,7 +243,8 @@ static int debug_read_raw_ref(struct ref_store *ref_store, const char *refname,
struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
int res = 0;
- oidcpy(oid, &null_oid);
+ oidcpy(oid, null_oid());
+ errno = 0;
res = drefs->refs->be->read_raw_ref(drefs->refs, refname, oid, referent,
type);
@@ -251,7 +252,9 @@ static int debug_read_raw_ref(struct ref_store *ref_store, const char *refname,
trace_printf_key(&trace_refs, "read_raw_ref: %s: %s (=> %s) type %x: %d\n",
refname, oid_to_hex(oid), referent->buf, *type, res);
} else {
- trace_printf_key(&trace_refs, "read_raw_ref: %s: %d\n", refname, res);
+ trace_printf_key(&trace_refs,
+ "read_raw_ref: %s: %d (errno %d)\n", refname,
+ res, errno);
}
return res;
}
@@ -353,6 +356,40 @@ static int debug_delete_reflog(struct ref_store *ref_store, const char *refname)
return res;
}
+struct debug_reflog_expiry_should_prune {
+ reflog_expiry_prepare_fn *prepare;
+ reflog_expiry_should_prune_fn *should_prune;
+ reflog_expiry_cleanup_fn *cleanup;
+ void *cb_data;
+};
+
+static void debug_reflog_expiry_prepare(const char *refname,
+ 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);
+ prune->prepare(refname, oid, prune->cb_data);
+}
+
+static int debug_reflog_expiry_should_prune_fn(struct object_id *ooid,
+ struct object_id *noid,
+ const char *email,
+ timestamp_t timestamp, int tz,
+ const char *message, void *cb_data) {
+ struct debug_reflog_expiry_should_prune *prune = cb_data;
+
+ int result = prune->should_prune(ooid, noid, email, timestamp, tz, message, prune->cb_data);
+ trace_printf_key(&trace_refs, "reflog_expire_should_prune: %s %ld: %d\n", message, (long int) timestamp, result);
+ return result;
+}
+
+static void debug_reflog_expiry_cleanup(void *cb_data)
+{
+ struct debug_reflog_expiry_should_prune *prune = cb_data;
+ prune->cleanup(prune->cb_data);
+}
+
static int debug_reflog_expire(struct ref_store *ref_store, const char *refname,
const struct object_id *oid, unsigned int flags,
reflog_expiry_prepare_fn prepare_fn,
@@ -361,10 +398,17 @@ static int debug_reflog_expire(struct ref_store *ref_store, const char *refname,
void *policy_cb_data)
{
struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
+ struct debug_reflog_expiry_should_prune prune = {
+ .prepare = prepare_fn,
+ .cleanup = cleanup_fn,
+ .should_prune = should_prune_fn,
+ .cb_data = policy_cb_data,
+ };
int res = drefs->refs->be->reflog_expire(drefs->refs, refname, oid,
- flags, prepare_fn,
- should_prune_fn, cleanup_fn,
- policy_cb_data);
+ flags, &debug_reflog_expiry_prepare,
+ &debug_reflog_expiry_should_prune_fn,
+ &debug_reflog_expiry_cleanup,
+ &prune);
trace_printf_key(&trace_refs, "reflog_expire: %s: %d\n", refname, res);
return res;
}
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 4fdc68810b..677b7e4cdd 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -45,10 +45,10 @@
#define REF_UPDATE_VIA_HEAD (1 << 8)
/*
- * Used as a flag in ref_update::flags when the loose reference has
- * been deleted.
+ * Used as a flag in ref_update::flags when a reference has been
+ * deleted and the ref's parent directories may need cleanup.
*/
-#define REF_DELETED_LOOSE (1 << 9)
+#define REF_DELETED_RMDIR (1 << 9)
struct ref_lock {
char *ref_name;
@@ -549,7 +549,7 @@ static int lock_raw_ref(struct files_ref_store *refs,
/* First lock the file so it can't change out from under us. */
- *lock_p = lock = xcalloc(1, sizeof(*lock));
+ *lock_p = CALLOC_ARRAY(lock, 1);
lock->ref_name = xstrdup(refname);
files_ref_path(refs, &ref_file, refname);
@@ -843,7 +843,7 @@ static struct ref_iterator *files_ref_iterator_begin(
overlay_iter = overlay_ref_iterator_begin(loose_iter, packed_iter);
- iter = xcalloc(1, sizeof(*iter));
+ CALLOC_ARRAY(iter, 1);
ref_iterator = &iter->base;
base_ref_iterator_init(ref_iterator, &files_ref_iterator_vtable,
overlay_iter->ordered);
@@ -930,7 +930,7 @@ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs,
files_assert_main_repository(refs, "lock_ref_oid_basic");
assert(err);
- lock = xcalloc(1, sizeof(struct ref_lock));
+ CALLOC_ARRAY(lock, 1);
if (mustexist)
resolve_flags |= RESOLVE_REF_READING;
@@ -1084,7 +1084,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)
ref_transaction_add_update(
transaction, r->name,
REF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,
- &null_oid, &r->oid, NULL);
+ null_oid(), &r->oid, NULL);
if (ref_transaction_commit(transaction, &err))
goto cleanup;
@@ -2152,7 +2152,7 @@ static struct ref_iterator *reflog_iterator_begin(struct ref_store *ref_store,
return empty_ref_iterator_begin();
}
- iter = xcalloc(1, sizeof(*iter));
+ CALLOC_ARRAY(iter, 1);
ref_iterator = &iter->base;
base_ref_iterator_init(ref_iterator, &files_reflog_iterator_vtable, 0);
@@ -2597,7 +2597,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
if (!transaction->nr)
goto cleanup;
- backend_data = xcalloc(1, sizeof(*backend_data));
+ CALLOC_ARRAY(backend_data, 1);
transaction->backend_data = backend_data;
/*
@@ -2852,6 +2852,7 @@ static int files_transaction_finish(struct ref_store *ref_store,
if (update->flags & REF_DELETING &&
!(update->flags & REF_LOG_ONLY)) {
+ update->flags |= REF_DELETED_RMDIR;
if (!(update->type & REF_ISPACKED) ||
update->type & REF_ISSYMREF) {
/* It is a loose reference. */
@@ -2861,7 +2862,6 @@ static int files_transaction_finish(struct ref_store *ref_store,
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
}
- update->flags |= REF_DELETED_LOOSE;
}
}
}
@@ -2874,9 +2874,9 @@ cleanup:
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
- if (update->flags & REF_DELETED_LOOSE) {
+ if (update->flags & REF_DELETED_RMDIR) {
/*
- * The loose reference was deleted. Delete any
+ * The reference was deleted. Delete any
* empty parent directories. (Note that this
* can only work because we have already
* removed the lockfile.)
diff --git a/refs/iterator.c b/refs/iterator.c
index 629e00a122..a89d132d4f 100644
--- a/refs/iterator.c
+++ b/refs/iterator.c
@@ -393,7 +393,7 @@ struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0,
if (!*prefix && !trim)
return iter0; /* optimization: no need to wrap iterator */
- iter = xcalloc(1, sizeof(*iter));
+ CALLOC_ARRAY(iter, 1);
ref_iterator = &iter->base;
base_ref_iterator_init(ref_iterator, &prefix_ref_iterator_vtable, iter0->ordered);
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index b912f2505f..dfecdbc1db 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -941,7 +941,7 @@ static struct ref_iterator *packed_ref_iterator_begin(
if (start == snapshot->eof)
return empty_ref_iterator_begin();
- iter = xcalloc(1, sizeof(*iter));
+ CALLOC_ARRAY(iter, 1);
ref_iterator = &iter->base;
base_ref_iterator_init(ref_iterator, &packed_ref_iterator_vtable, 1);
@@ -1424,7 +1424,7 @@ static int packed_transaction_prepare(struct ref_store *ref_store,
* do so itself.
*/
- data = xcalloc(1, sizeof(*data));
+ CALLOC_ARRAY(data, 1);
string_list_init(&data->updates, 0);
transaction->backend_data = data;
diff --git a/refs/ref-cache.c b/refs/ref-cache.c
index b7052f72e2..46f1e54284 100644
--- a/refs/ref-cache.c
+++ b/refs/ref-cache.c
@@ -530,7 +530,7 @@ struct ref_iterator *cache_ref_iterator_begin(struct ref_cache *cache,
if (prime_dir)
prime_ref_dir(dir, prefix);
- iter = xcalloc(1, sizeof(*iter));
+ CALLOC_ARRAY(iter, 1);
ref_iterator = &iter->base;
base_ref_iterator_init(ref_iterator, &cache_ref_iterator_vtable, 1);
ALLOC_GROW(iter->levels, 10, iter->levels_alloc);
diff --git a/remote-curl.c b/remote-curl.c
index 0290b04891..9d432c299a 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -555,6 +555,8 @@ static void output_refs(struct ref *refs)
struct ref *posn;
if (options.object_format && options.hash_algo) {
printf(":object-format %s\n", options.hash_algo->name);
+ repo_set_hash_algo(the_repository,
+ hash_algo_by_ptr(options.hash_algo));
}
for (posn = refs; posn; posn = posn->next) {
if (posn->symref)
diff --git a/remote.c b/remote.c
index c3f85c17ca..6d1e8d02df 100644
--- a/remote.c
+++ b/remote.c
@@ -151,7 +151,7 @@ static struct remote *make_remote(const char *name, int len)
if (e)
return container_of(e, struct remote, ent);
- ret = xcalloc(1, sizeof(struct remote));
+ CALLOC_ARRAY(ret, 1);
ret->prune = -1; /* unspecified */
ret->prune_tags = -1; /* unspecified */
ret->name = xstrndup(name, len);
@@ -186,7 +186,7 @@ static struct branch *make_branch(const char *name, size_t len)
}
ALLOC_GROW(branches, branches_nr + 1, branches_alloc);
- ret = xcalloc(1, sizeof(struct branch));
+ CALLOC_ARRAY(ret, 1);
branches[branches_nr++] = ret;
ret->name = xstrndup(name, len);
ret->refname = xstrfmt("refs/heads/%s", ret->name);
@@ -207,7 +207,7 @@ static struct rewrite *make_rewrite(struct rewrites *r,
}
ALLOC_GROW(r->rewrite, r->rewrite_nr + 1, r->rewrite_alloc);
- ret = xcalloc(1, sizeof(struct rewrite));
+ CALLOC_ARRAY(ret, 1);
r->rewrite[r->rewrite_nr++] = ret;
ret->base = xstrndup(base, len);
ret->baselen = len;
@@ -1664,7 +1664,7 @@ static void set_merge(struct branch *ret)
remote = remote_get(ret->remote_name);
- ret->merge = xcalloc(ret->merge_nr, sizeof(*ret->merge));
+ CALLOC_ARRAY(ret->merge, ret->merge_nr);
for (i = 0; i < ret->merge_nr; i++) {
ret->merge[i] = xcalloc(1, sizeof(**ret->merge));
ret->merge[i]->src = xstrdup(ret->merge_name[i]);
diff --git a/repo-settings.c b/repo-settings.c
index f7fff0f5ab..0cfe8b787d 100644
--- a/repo-settings.c
+++ b/repo-settings.c
@@ -77,4 +77,19 @@ void prepare_repo_settings(struct repository *r)
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
+ * codebase around uses of the index, this setting will be
+ * 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 c98298acd0..448cd557d4 100644
--- a/repository.c
+++ b/repository.c
@@ -10,6 +10,7 @@
#include "object.h"
#include "lockfile.h"
#include "submodule-config.h"
+#include "sparse-index.h"
/* The main repository */
static struct repository the_repo;
@@ -72,7 +73,7 @@ void repo_set_gitdir(struct repository *repo,
repo_set_commondir(repo, o->commondir);
if (!repo->objects->odb) {
- repo->objects->odb = xcalloc(1, sizeof(*repo->objects->odb));
+ CALLOC_ARRAY(repo->objects->odb, 1);
repo->objects->odb_tail = &repo->objects->odb->next;
}
expand_base_dir(&repo->objects->odb->path, o->object_dir,
@@ -261,8 +262,10 @@ void repo_clear(struct repository *repo)
int repo_read_index(struct repository *repo)
{
+ int res;
+
if (!repo->index)
- repo->index = xcalloc(1, sizeof(*repo->index));
+ CALLOC_ARRAY(repo->index, 1);
/* Complete the double-reference */
if (!repo->index->repo)
@@ -270,7 +273,13 @@ int repo_read_index(struct repository *repo)
else if (repo->index->repo != repo)
BUG("repo's index should point back at itself");
- return read_index_from(repo->index, repo->index_file, repo->gitdir);
+ res = read_index_from(repo->index, repo->index_file, repo->gitdir);
+
+ prepare_repo_settings(repo);
+ if (repo->settings.command_requires_full_index)
+ ensure_full_index(repo->index);
+
+ return res;
}
int repo_hold_locked_index(struct repository *repo,
diff --git a/repository.h b/repository.h
index b385ca3c94..a45f7520fd 100644
--- a/repository.h
+++ b/repository.h
@@ -41,6 +41,9 @@ 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 {
diff --git a/rerere.c b/rerere.c
index dee60dc6df..d83d58df4f 100644
--- a/rerere.c
+++ b/rerere.c
@@ -1190,13 +1190,11 @@ void rerere_gc(struct repository *r, struct string_list *rr)
if (!dir)
die_errno(_("unable to open rr-cache directory"));
/* Collect stale conflict IDs ... */
- while ((e = readdir(dir))) {
+ while ((e = readdir_skip_dot_and_dotdot(dir))) {
struct rerere_dir *rr_dir;
struct rerere_id id;
int now_empty;
- if (is_dot_or_dotdot(e->d_name))
- continue;
if (!is_rr_cache_dirname(e->d_name))
continue; /* or should we remove e->d_name? */
diff --git a/reset.c b/reset.c
index 2f4fbd07c5..4bea758053 100644
--- a/reset.c
+++ b/reset.c
@@ -128,7 +128,7 @@ reset_head_refs:
}
if (run_hook)
run_hook_le(NULL, "post-checkout",
- oid_to_hex(orig ? orig : &null_oid),
+ oid_to_hex(orig ? orig : null_oid()),
oid_to_hex(oid), "1", NULL);
leave_reset_head:
diff --git a/resolve-undo.c b/resolve-undo.c
index 236320f179..e81096e2d4 100644
--- a/resolve-undo.c
+++ b/resolve-undo.c
@@ -15,7 +15,7 @@ void record_resolve_undo(struct index_state *istate, struct cache_entry *ce)
return;
if (!istate->resolve_undo) {
- resolve_undo = xcalloc(1, sizeof(*resolve_undo));
+ CALLOC_ARRAY(resolve_undo, 1);
resolve_undo->strdup_strings = 1;
istate->resolve_undo = resolve_undo;
}
@@ -57,7 +57,7 @@ struct string_list *resolve_undo_read(const char *data, unsigned long size)
int i;
const unsigned rawsz = the_hash_algo->rawsz;
- resolve_undo = xcalloc(1, sizeof(*resolve_undo));
+ CALLOC_ARRAY(resolve_undo, 1);
resolve_undo->strdup_strings = 1;
while (size) {
@@ -172,6 +172,8 @@ void unmerge_marked_index(struct index_state *istate)
if (!istate->resolve_undo)
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 (ce->ce_flags & CE_MATCHED)
@@ -186,6 +188,8 @@ void unmerge_index(struct index_state *istate, const struct pathspec *pathspec)
if (!istate->resolve_undo)
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 (!ce_path_match(istate, ce, pathspec, NULL))
diff --git a/revision.c b/revision.c
index b78733f508..8140561b6c 100644
--- a/revision.c
+++ b/revision.c
@@ -154,7 +154,7 @@ static void paths_and_oids_insert(struct hashmap *map,
entry = hashmap_get_entry(map, &key, ent, NULL);
if (!entry) {
- entry = xcalloc(1, sizeof(struct path_and_oids_entry));
+ CALLOC_ARRAY(entry, 1);
hashmap_entry_init(&entry->ent, hash);
entry->path = xstrdup(key.path);
oidset_init(&entry->trees, 16);
@@ -1123,7 +1123,7 @@ static int process_parents(struct rev_info *revs, struct commit *commit,
mark_parents_uninteresting(p);
if (p->object.flags & SEEN)
continue;
- p->object.flags |= SEEN;
+ p->object.flags |= (SEEN | NOT_USER_GIVEN);
if (list)
commit_list_insert_by_date(p, list);
if (queue)
@@ -1165,7 +1165,7 @@ static int process_parents(struct rev_info *revs, struct commit *commit,
}
p->object.flags |= left_flag;
if (!(p->object.flags & SEEN)) {
- p->object.flags |= SEEN;
+ p->object.flags |= (SEEN | NOT_USER_GIVEN);
if (list)
commit_list_insert_by_date(p, list);
if (queue)
@@ -1393,20 +1393,20 @@ static int limit_list(struct rev_info *revs)
{
int slop = SLOP;
timestamp_t date = TIME_MAX;
- struct commit_list *list = revs->commits;
+ struct commit_list *original_list = revs->commits;
struct commit_list *newlist = NULL;
struct commit_list **p = &newlist;
struct commit_list *bottom = NULL;
struct commit *interesting_cache = NULL;
if (revs->ancestry_path) {
- bottom = collect_bottom_commits(list);
+ bottom = collect_bottom_commits(original_list);
if (!bottom)
die("--ancestry-path given but there are no bottom commits");
}
- while (list) {
- struct commit *commit = pop_commit(&list);
+ while (original_list) {
+ struct commit *commit = pop_commit(&original_list);
struct object *obj = &commit->object;
show_early_output_fn_t show;
@@ -1415,11 +1415,11 @@ static int limit_list(struct rev_info *revs)
if (revs->max_age != -1 && (commit->date < revs->max_age))
obj->flags |= UNINTERESTING;
- if (process_parents(revs, commit, &list, NULL) < 0)
+ if (process_parents(revs, commit, &original_list, NULL) < 0)
return -1;
if (obj->flags & UNINTERESTING) {
mark_parents_uninteresting(commit);
- slop = still_interesting(list, date, slop, &interesting_cache);
+ slop = still_interesting(original_list, date, slop, &interesting_cache);
if (slop)
continue;
break;
@@ -1452,14 +1452,17 @@ static int limit_list(struct rev_info *revs)
* Check if any commits have become TREESAME by some of their parents
* becoming UNINTERESTING.
*/
- if (limiting_can_increase_treesame(revs))
+ if (limiting_can_increase_treesame(revs)) {
+ struct commit_list *list = NULL;
for (list = newlist; list; list = list->next) {
struct commit *c = list->item;
if (c->object.flags & (UNINTERESTING | TREESAME))
continue;
update_treesame(revs, c);
}
+ }
+ free_commit_list(original_list);
revs->commits = newlist;
return 0;
}
@@ -1555,7 +1558,7 @@ void clear_ref_exclusion(struct string_list **ref_excludes_p)
void add_ref_exclusion(struct string_list **ref_excludes_p, const char *exclude)
{
if (!*ref_excludes_p) {
- *ref_excludes_p = xcalloc(1, sizeof(**ref_excludes_p));
+ CALLOC_ARRAY(*ref_excludes_p, 1);
(*ref_excludes_p)->strdup_strings = 1;
}
string_list_append(*ref_excludes_p, exclude);
@@ -1680,6 +1683,8 @@ static void do_add_index_objects_to_pending(struct rev_info *revs,
{
int i;
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(istate);
for (i = 0; i < istate->cache_nr; i++) {
struct cache_entry *ce = istate->cache[i];
struct blob *blob;
@@ -2336,6 +2341,16 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
revs->unpacked = 1;
} else if (starts_with(arg, "--unpacked=")) {
die(_("--unpacked=<packfile> no longer supported"));
+ } else if (!strcmp(arg, "--no-kept-objects")) {
+ revs->no_kept_objects = 1;
+ revs->keep_pack_cache_flags |= IN_CORE_KEEP_PACKS;
+ revs->keep_pack_cache_flags |= ON_DISK_KEEP_PACKS;
+ } else if (skip_prefix(arg, "--no-kept-objects=", &optarg)) {
+ revs->no_kept_objects = 1;
+ if (!strcmp(optarg, "in-core"))
+ revs->keep_pack_cache_flags |= IN_CORE_KEEP_PACKS;
+ if (!strcmp(optarg, "on-disk"))
+ revs->keep_pack_cache_flags |= ON_DISK_KEEP_PACKS;
} else if (!strcmp(arg, "-r")) {
revs->diff = 1;
revs->diffopt.flags.recursive = 1;
@@ -2929,7 +2944,7 @@ static struct merge_simplify_state *locate_simplify_state(struct rev_info *revs,
st = lookup_decoration(&revs->merge_simplification, &commit->object);
if (!st) {
- st = xcalloc(1, sizeof(*st));
+ CALLOC_ARRAY(st, 1);
add_decoration(&revs->merge_simplification, &commit->object, st);
}
return st;
@@ -3261,7 +3276,7 @@ static int mark_uninteresting(const struct object_id *oid,
void *cb)
{
struct rev_info *revs = cb;
- struct object *o = parse_object(revs->repo, oid);
+ struct object *o = lookup_unknown_object(revs->repo, oid);
o->flags |= UNINTERESTING | SEEN;
return 0;
}
@@ -3795,6 +3810,11 @@ enum commit_action get_commit_action(struct rev_info *revs, struct commit *commi
return commit_ignore;
if (revs->unpacked && has_object_pack(&commit->object.oid))
return commit_ignore;
+ if (revs->no_kept_objects) {
+ if (has_object_kept_pack(&commit->object.oid,
+ revs->keep_pack_cache_flags))
+ return commit_ignore;
+ }
if (commit->object.flags & UNINTERESTING)
return commit_ignore;
if (revs->line_level_traverse && !want_ancestry(revs)) {
diff --git a/revision.h b/revision.h
index e6be3c845e..93aa012f51 100644
--- a/revision.h
+++ b/revision.h
@@ -44,9 +44,6 @@
/*
* Indicates object was reached by traversal. i.e. not given by user on
* command-line or stdin.
- * NEEDSWORK: NOT_USER_GIVEN doesn't apply to commits because we only support
- * filtering trees and blobs, but it may be useful to support filtering commits
- * in the future.
*/
#define NOT_USER_GIVEN (1u<<25)
#define TRACK_LINEAR (1u<<26)
@@ -148,6 +145,7 @@ struct rev_info {
edge_hint_aggressive:1,
limited:1,
unpacked:1,
+ no_kept_objects:1,
boundary:2,
count:1,
left_right:1,
@@ -235,7 +233,7 @@ struct rev_info {
const char *mime_boundary;
const char *patch_suffix;
int numbered_files;
- int reroll_count;
+ const char *reroll_count;
char *message_id;
struct ident_split from_ident;
struct string_list *ref_message_ids;
@@ -317,6 +315,9 @@ struct rev_info {
* This is loaded from the commit-graph being used.
*/
struct bloom_filter_settings *bloom_filter_settings;
+
+ /* misc. flags related to '--no-kept-objects' */
+ unsigned keep_pack_cache_flags;
};
int ref_excluded(struct string_list *, const char *path);
diff --git a/run-command.c b/run-command.c
index 4e34623e2e..be6bc128cd 100644
--- a/run-command.c
+++ b/run-command.c
@@ -1638,8 +1638,8 @@ static void pp_init(struct parallel_processes *pp,
pp->nr_processes = 0;
pp->output_owner = 0;
pp->shutdown = 0;
- pp->children = xcalloc(n, sizeof(*pp->children));
- pp->pfd = xcalloc(n, sizeof(*pp->pfd));
+ CALLOC_ARRAY(pp->children, n);
+ CALLOC_ARRAY(pp->pfd, n);
strbuf_init(&pp->buffered_output, 0);
for (i = 0; i < n; i++) {
diff --git a/send-pack.c b/send-pack.c
index 9045f8a082..9cb9f71650 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -56,7 +56,9 @@ static void feed_object(const struct object_id *oid, FILE *fh, int negative)
/*
* Make a pack stream and spit it out into file descriptor fd
*/
-static int pack_objects(int fd, struct ref *refs, struct oid_array *extra, struct send_pack_args *args)
+static int pack_objects(int fd, struct ref *refs, struct oid_array *advertised,
+ struct oid_array *negotiated,
+ struct send_pack_args *args)
{
/*
* The child becomes pack-objects --revs; we feed
@@ -94,8 +96,10 @@ static int pack_objects(int fd, struct ref *refs, struct oid_array *extra, struc
* parameters by writing to the pipe.
*/
po_in = xfdopen(po.in, "w");
- for (i = 0; i < extra->nr; i++)
- feed_object(&extra->oid[i], po_in, 1);
+ for (i = 0; i < advertised->nr; i++)
+ feed_object(&advertised->oid[i], po_in, 1);
+ for (i = 0; i < negotiated->nr; i++)
+ feed_object(&negotiated->oid[i], po_in, 1);
while (refs) {
if (!is_null_oid(&refs->old_oid))
@@ -188,13 +192,13 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)
}
if (new_report) {
if (!hint->report) {
- hint->report = xcalloc(1, sizeof(struct ref_push_report));
+ CALLOC_ARRAY(hint->report, 1);
report = hint->report;
} else {
report = hint->report;
while (report->next)
report = report->next;
- report->next = xcalloc(1, sizeof(struct ref_push_report));
+ CALLOC_ARRAY(report->next, 1);
report = report->next;
}
new_report = 0;
@@ -409,11 +413,55 @@ static void reject_invalid_nonce(const char *nonce, int len)
}
}
+static void get_commons_through_negotiation(const char *url,
+ const struct ref *remote_refs,
+ struct oid_array *commons)
+{
+ struct child_process child = CHILD_PROCESS_INIT;
+ const struct ref *ref;
+ int len = the_hash_algo->hexsz + 1; /* hash + NL */
+
+ child.git_cmd = 1;
+ 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));
+ strvec_push(&child.args, url);
+
+ if (start_command(&child))
+ die(_("send-pack: unable to fork off fetch subprocess"));
+
+ do {
+ char hex_hash[GIT_MAX_HEXSZ + 1];
+ int read_len = read_in_full(child.out, hex_hash, len);
+ struct object_id oid;
+ const char *end;
+
+ if (!read_len)
+ break;
+ if (read_len != len)
+ die("invalid length read %d", read_len);
+ if (parse_oid_hex(hex_hash, &oid, &end) || *end != '\n')
+ die("invalid hash");
+ oid_array_append(commons, &oid);
+ } while (1);
+
+ if (finish_command(&child)) {
+ /*
+ * The information that push negotiation provides is useful but
+ * not mandatory.
+ */
+ warning(_("push negotiation failed; proceeding anyway with push"));
+ }
+}
+
int send_pack(struct send_pack_args *args,
int fd[], struct child_process *conn,
struct ref *remote_refs,
struct oid_array *extra_have)
{
+ struct oid_array commons = OID_ARRAY_INIT;
int in = fd[0];
int out = fd[1];
struct strbuf req_buf = STRBUF_INIT;
@@ -426,6 +474,7 @@ int send_pack(struct send_pack_args *args,
int quiet_supported = 0;
int agent_supported = 0;
int advertise_sid = 0;
+ int push_negotiate = 0;
int use_atomic = 0;
int atomic_supported = 0;
int use_push_options = 0;
@@ -437,6 +486,10 @@ int send_pack(struct send_pack_args *args,
const char *push_cert_nonce = NULL;
struct packet_reader reader;
+ git_config_get_bool("push.negotiate", &push_negotiate);
+ if (push_negotiate)
+ get_commons_through_negotiation(args->url, remote_refs, &commons);
+
git_config_get_bool("transfer.advertisesid", &advertise_sid);
/* Does the other end support the reporting? */
@@ -625,7 +678,7 @@ int send_pack(struct send_pack_args *args,
PACKET_READ_DIE_ON_ERR_PACKET);
if (need_pack_data && cmds_sent) {
- if (pack_objects(out, remote_refs, extra_have, args) < 0) {
+ if (pack_objects(out, remote_refs, extra_have, &commons, args) < 0) {
if (args->stateless_rpc)
close(out);
if (git_connection_is_socket(conn))
diff --git a/sequencer.c b/sequencer.c
index d2332d3e17..0bec01cf38 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -164,6 +164,7 @@ static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy")
static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts")
static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate")
static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, "rebase-merge/reschedule-failed-exec")
+static GIT_PATH_FUNC(rebase_path_no_reschedule_failed_exec, "rebase-merge/no-reschedule-failed-exec")
static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, "rebase-merge/drop_redundant_commits")
static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, "rebase-merge/keep_redundant_commits")
@@ -524,7 +525,7 @@ static int fast_forward_to(struct repository *r,
if (!transaction ||
ref_transaction_update(transaction, "HEAD",
to, unborn && !is_rebase_i(opts) ?
- &null_oid : from,
+ null_oid() : from,
0, sb.buf, &err) ||
ref_transaction_commit(transaction, &err)) {
ref_transaction_free(transaction);
@@ -1131,7 +1132,7 @@ int update_head_with_reflog(const struct commit *old_head,
transaction = ref_transaction_begin(err);
if (!transaction ||
ref_transaction_update(transaction, "HEAD", new_head,
- old_head ? &old_head->object.oid : &null_oid,
+ old_head ? &old_head->object.oid : null_oid(),
0, sb.buf, err) ||
ref_transaction_commit(transaction, err)) {
ret = -1;
@@ -1726,13 +1727,183 @@ static int is_pick_or_similar(enum todo_command command)
}
}
+enum todo_item_flags {
+ TODO_EDIT_MERGE_MSG = (1 << 0),
+ TODO_REPLACE_FIXUP_MSG = (1 << 1),
+ TODO_EDIT_FIXUP_MSG = (1 << 2),
+};
+
+static const char first_commit_msg_str[] = N_("This is the 1st commit message:");
+static const char nth_commit_msg_fmt[] = N_("This is the commit message #%d:");
+static const char skip_first_commit_msg_str[] = N_("The 1st commit message will be skipped:");
+static const char skip_nth_commit_msg_fmt[] = N_("The commit message #%d will be skipped:");
+static const char combined_commit_msg_fmt[] = N_("This is a combination of %d commits.");
+
+static int is_fixup_flag(enum todo_command command, unsigned flag)
+{
+ return command == TODO_FIXUP && ((flag & TODO_REPLACE_FIXUP_MSG) ||
+ (flag & TODO_EDIT_FIXUP_MSG));
+}
+
+/*
+ * Wrapper around strbuf_add_commented_lines() which avoids double
+ * commenting commit subjects.
+ */
+static void add_commented_lines(struct strbuf *buf, const void *str, size_t len)
+{
+ const char *s = str;
+ while (len > 0 && s[0] == comment_line_char) {
+ size_t count;
+ const char *n = memchr(s, '\n', len);
+ if (!n)
+ count = len;
+ else
+ count = n - s + 1;
+ strbuf_add(buf, s, count);
+ s += count;
+ len -= count;
+ }
+ strbuf_add_commented_lines(buf, s, len);
+}
+
+/* Does the current fixup chain contain a squash command? */
+static int seen_squash(struct replay_opts *opts)
+{
+ return starts_with(opts->current_fixups.buf, "squash") ||
+ strstr(opts->current_fixups.buf, "\nsquash");
+}
+
+static void update_comment_bufs(struct strbuf *buf1, struct strbuf *buf2, int n)
+{
+ strbuf_setlen(buf1, 2);
+ strbuf_addf(buf1, _(nth_commit_msg_fmt), n);
+ strbuf_addch(buf1, '\n');
+ strbuf_setlen(buf2, 2);
+ strbuf_addf(buf2, _(skip_nth_commit_msg_fmt), n);
+ strbuf_addch(buf2, '\n');
+}
+
+/*
+ * Comment out any un-commented commit messages, updating the message comments
+ * to say they will be skipped but do not comment out the empty lines that
+ * surround commit messages and their comments.
+ */
+static void update_squash_message_for_fixup(struct strbuf *msg)
+{
+ void (*copy_lines)(struct strbuf *, const void *, size_t) = strbuf_add;
+ struct strbuf buf1 = STRBUF_INIT, buf2 = STRBUF_INIT;
+ const char *s, *start;
+ char *orig_msg;
+ size_t orig_msg_len;
+ int i = 1;
+
+ strbuf_addf(&buf1, "# %s\n", _(first_commit_msg_str));
+ strbuf_addf(&buf2, "# %s\n", _(skip_first_commit_msg_str));
+ s = start = orig_msg = strbuf_detach(msg, &orig_msg_len);
+ while (s) {
+ const char *next;
+ size_t off;
+ if (skip_prefix(s, buf1.buf, &next)) {
+ /*
+ * Copy the last message, preserving the blank line
+ * preceding the current line
+ */
+ off = (s > start + 1 && s[-2] == '\n') ? 1 : 0;
+ copy_lines(msg, start, s - start - off);
+ if (off)
+ strbuf_addch(msg, '\n');
+ /*
+ * The next message needs to be commented out but the
+ * message header is already commented out so just copy
+ * it and the blank line that follows it.
+ */
+ strbuf_addbuf(msg, &buf2);
+ if (*next == '\n')
+ strbuf_addch(msg, *next++);
+ start = s = next;
+ copy_lines = add_commented_lines;
+ update_comment_bufs(&buf1, &buf2, ++i);
+ } else if (skip_prefix(s, buf2.buf, &next)) {
+ off = (s > start + 1 && s[-2] == '\n') ? 1 : 0;
+ copy_lines(msg, start, s - start - off);
+ start = s - off;
+ s = next;
+ copy_lines = strbuf_add;
+ update_comment_bufs(&buf1, &buf2, ++i);
+ } else {
+ s = strchr(s, '\n');
+ if (s)
+ s++;
+ }
+ }
+ copy_lines(msg, start, orig_msg_len - (start - orig_msg));
+ free(orig_msg);
+ strbuf_release(&buf1);
+ strbuf_release(&buf2);
+}
+
+static int append_squash_message(struct strbuf *buf, const char *body,
+ enum todo_command command, struct replay_opts *opts,
+ unsigned flag)
+{
+ const char *fixup_msg;
+ size_t commented_len = 0, fixup_off;
+ /*
+ * amend is non-interactive and not normally used with fixup!
+ * or squash! commits, so only comment out those subjects when
+ * squashing commit messages.
+ */
+ if (starts_with(body, "amend!") ||
+ ((command == TODO_SQUASH || seen_squash(opts)) &&
+ (starts_with(body, "squash!") || starts_with(body, "fixup!"))))
+ commented_len = commit_subject_length(body);
+
+ strbuf_addf(buf, "\n%c ", comment_line_char);
+ strbuf_addf(buf, _(nth_commit_msg_fmt),
+ ++opts->current_fixup_count + 1);
+ strbuf_addstr(buf, "\n\n");
+ strbuf_add_commented_lines(buf, body, commented_len);
+ /* buf->buf may be reallocated so store an offset into the buffer */
+ fixup_off = buf->len;
+ strbuf_addstr(buf, body + commented_len);
+
+ /* fixup -C after squash behaves like squash */
+ if (is_fixup_flag(command, flag) && !seen_squash(opts)) {
+ /*
+ * We're replacing the commit message so we need to
+ * append the Signed-off-by: trailer if the user
+ * requested '--signoff'.
+ */
+ if (opts->signoff)
+ append_signoff(buf, 0, 0);
+
+ if ((command == TODO_FIXUP) &&
+ (flag & TODO_REPLACE_FIXUP_MSG) &&
+ (file_exists(rebase_path_fixup_msg()) ||
+ !file_exists(rebase_path_squash_msg()))) {
+ fixup_msg = skip_blank_lines(buf->buf + fixup_off);
+ if (write_message(fixup_msg, strlen(fixup_msg),
+ rebase_path_fixup_msg(), 0) < 0)
+ return error(_("cannot write '%s'"),
+ rebase_path_fixup_msg());
+ } else {
+ unlink(rebase_path_fixup_msg());
+ }
+ } else {
+ unlink(rebase_path_fixup_msg());
+ }
+
+ return 0;
+}
+
static int update_squash_messages(struct repository *r,
enum todo_command command,
struct commit *commit,
- struct replay_opts *opts)
+ struct replay_opts *opts,
+ unsigned flag)
{
struct strbuf buf = STRBUF_INIT;
- int res;
+ int res = 0;
const char *message, *body;
const char *encoding = get_commit_output_encoding();
@@ -1748,10 +1919,12 @@ static int update_squash_messages(struct repository *r,
buf.buf : strchrnul(buf.buf, '\n');
strbuf_addf(&header, "%c ", comment_line_char);
- strbuf_addf(&header, _("This is a combination of %d commits."),
+ strbuf_addf(&header, _(combined_commit_msg_fmt),
opts->current_fixup_count + 2);
strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);
strbuf_release(&header);
+ if (is_fixup_flag(command, flag) && !seen_squash(opts))
+ update_squash_message_for_fixup(&buf);
} else {
struct object_id head;
struct commit *head_commit;
@@ -1765,19 +1938,22 @@ static int update_squash_messages(struct repository *r,
return error(_("could not read HEAD's commit message"));
find_commit_subject(head_message, &body);
- if (write_message(body, strlen(body),
- rebase_path_fixup_msg(), 0)) {
+ if (command == TODO_FIXUP && !flag && write_message(body, strlen(body),
+ rebase_path_fixup_msg(), 0) < 0) {
unuse_commit_buffer(head_commit, head_message);
- return error(_("cannot write '%s'"),
- rebase_path_fixup_msg());
+ return error(_("cannot write '%s'"), rebase_path_fixup_msg());
}
-
strbuf_addf(&buf, "%c ", comment_line_char);
- strbuf_addf(&buf, _("This is a combination of %d commits."), 2);
+ strbuf_addf(&buf, _(combined_commit_msg_fmt), 2);
strbuf_addf(&buf, "\n%c ", comment_line_char);
- strbuf_addstr(&buf, _("This is the 1st commit message:"));
+ strbuf_addstr(&buf, is_fixup_flag(command, flag) ?
+ _(skip_first_commit_msg_str) :
+ _(first_commit_msg_str));
strbuf_addstr(&buf, "\n\n");
- strbuf_addstr(&buf, body);
+ if (is_fixup_flag(command, flag))
+ strbuf_add_commented_lines(&buf, body, strlen(body));
+ else
+ strbuf_addstr(&buf, body);
unuse_commit_buffer(head_commit, head_message);
}
@@ -1787,16 +1963,11 @@ static int update_squash_messages(struct repository *r,
oid_to_hex(&commit->object.oid));
find_commit_subject(message, &body);
- if (command == TODO_SQUASH) {
- unlink(rebase_path_fixup_msg());
- strbuf_addf(&buf, "\n%c ", comment_line_char);
- strbuf_addf(&buf, _("This is the commit message #%d:"),
- ++opts->current_fixup_count + 1);
- strbuf_addstr(&buf, "\n\n");
- strbuf_addstr(&buf, body);
+ if (command == TODO_SQUASH || is_fixup_flag(command, flag)) {
+ res = append_squash_message(&buf, body, command, opts, flag);
} else if (command == TODO_FIXUP) {
strbuf_addf(&buf, "\n%c ", comment_line_char);
- strbuf_addf(&buf, _("The commit message #%d will be skipped:"),
+ strbuf_addf(&buf, _(skip_nth_commit_msg_fmt),
++opts->current_fixup_count + 1);
strbuf_addstr(&buf, "\n\n");
strbuf_add_commented_lines(&buf, body, strlen(body));
@@ -1804,7 +1975,9 @@ static int update_squash_messages(struct repository *r,
return error(_("unknown command: %d"), command);
unuse_commit_buffer(commit, message);
- res = write_message(buf.buf, buf.len, rebase_path_squash_msg(), 0);
+ if (!res)
+ res = write_message(buf.buf, buf.len, rebase_path_squash_msg(),
+ 0);
strbuf_release(&buf);
if (!res) {
@@ -1860,14 +2033,24 @@ static void record_in_rewritten(struct object_id *oid,
flush_rewritten_pending();
}
+static int should_edit(struct replay_opts *opts) {
+ if (opts->edit < 0)
+ /*
+ * Note that we only handle the case of non-conflicted
+ * commits; continue_single_pick() handles the conflicted
+ * commits itself instead of calling this function.
+ */
+ return (opts->action == REPLAY_REVERT && isatty(0)) ? 1 : 0;
+ return opts->edit;
+}
+
static int do_pick_commit(struct repository *r,
- enum todo_command command,
- struct commit *commit,
+ struct todo_item *item,
struct replay_opts *opts,
int final_fixup, int *check_todo)
{
- unsigned int flags = opts->edit ? EDIT_MSG : 0;
- const char *msg_file = opts->edit ? NULL : git_path_merge_msg(r);
+ unsigned int flags = should_edit(opts) ? EDIT_MSG : 0;
+ const char *msg_file = should_edit(opts) ? NULL : git_path_merge_msg(r);
struct object_id head;
struct commit *base, *next, *parent;
const char *base_label, *next_label;
@@ -1875,6 +2058,8 @@ static int do_pick_commit(struct repository *r,
struct commit_message msg = { NULL, NULL, NULL, NULL };
struct strbuf msgbuf = STRBUF_INIT;
int res, unborn = 0, reword = 0, allow, drop_commit;
+ enum todo_command command = item->command;
+ struct commit *commit = item->commit;
if (opts->no_commit) {
/*
@@ -2004,7 +2189,8 @@ static int do_pick_commit(struct repository *r,
if (command == TODO_REWORD)
reword = 1;
else if (is_fixup(command)) {
- if (update_squash_messages(r, command, commit, opts))
+ if (update_squash_messages(r, command, commit,
+ opts, item->flags))
return -1;
flags |= AMEND_MSG;
if (!final_fixup)
@@ -2096,6 +2282,7 @@ static int do_pick_commit(struct repository *r,
refs_delete_ref(get_main_ref_store(r), "", "CHERRY_PICK_HEAD",
NULL, 0);
unlink(git_path_merge_msg(r));
+ unlink(git_path_auto_merge(r));
fprintf(stderr,
_("dropping %s %s -- patch contents already upstream\n"),
oid_to_hex(&commit->object.oid), msg.subject);
@@ -2169,10 +2356,6 @@ static int read_and_refresh_cache(struct repository *r,
return 0;
}
-enum todo_item_flags {
- TODO_EDIT_MERGE_MSG = 1
-};
-
void todo_list_release(struct todo_list *todo_list)
{
strbuf_release(&todo_list->buf);
@@ -2259,6 +2442,18 @@ static int parse_insn_line(struct repository *r, struct todo_item *item,
return 0;
}
+ if (item->command == TODO_FIXUP) {
+ if (skip_prefix(bol, "-C", &bol) &&
+ (*bol == ' ' || *bol == '\t')) {
+ bol += strspn(bol, " \t");
+ item->flags |= TODO_REPLACE_FIXUP_MSG;
+ } else if (skip_prefix(bol, "-c", &bol) &&
+ (*bol == ' ' || *bol == '\t')) {
+ bol += strspn(bol, " \t");
+ item->flags |= TODO_EDIT_FIXUP_MSG;
+ }
+ }
+
if (item->command == TODO_MERGE) {
if (skip_prefix(bol, "-C", &bol))
bol += strspn(bol, " \t");
@@ -2451,6 +2646,8 @@ void sequencer_post_commit_cleanup(struct repository *r, int verbose)
need_cleanup = 1;
}
+ unlink(git_path_auto_merge(r));
+
if (!need_cleanup)
return;
@@ -2672,6 +2869,8 @@ static int read_populate_opts(struct replay_opts *opts)
if (file_exists(rebase_path_reschedule_failed_exec()))
opts->reschedule_failed_exec = 1;
+ else if (file_exists(rebase_path_no_reschedule_failed_exec()))
+ opts->reschedule_failed_exec = 0;
if (file_exists(rebase_path_drop_redundant_commits()))
opts->drop_redundant_commits = 1;
@@ -2772,6 +2971,8 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,
write_file(rebase_path_ignore_date(), "%s", "");
if (opts->reschedule_failed_exec)
write_file(rebase_path_reschedule_failed_exec(), "%s", "");
+ else
+ write_file(rebase_path_no_reschedule_failed_exec(), "%s", "");
return 0;
}
@@ -3101,9 +3302,9 @@ static int save_opts(struct replay_opts *opts)
if (opts->no_commit)
res |= git_config_set_in_file_gently(opts_file,
"options.no-commit", "true");
- if (opts->edit)
- res |= git_config_set_in_file_gently(opts_file,
- "options.edit", "true");
+ if (opts->edit >= 0)
+ res |= git_config_set_in_file_gently(opts_file, "options.edit",
+ opts->edit ? "true" : "false");
if (opts->allow_empty)
res |= git_config_set_in_file_gently(opts_file,
"options.allow-empty", "true");
@@ -4077,7 +4278,7 @@ static int pick_commits(struct repository *r,
prev_reflog_action = xstrdup(getenv(GIT_REFLOG_ACTION));
if (opts->allow_ff)
assert(!(opts->signoff || opts->no_commit ||
- opts->record_origin || opts->edit ||
+ opts->record_origin || should_edit(opts) ||
opts->committer_date_is_author_date ||
opts->ignore_date));
if (read_and_refresh_cache(r, opts))
@@ -4111,6 +4312,7 @@ static int pick_commits(struct repository *r,
unlink(rebase_path_stopped_sha());
unlink(rebase_path_amend());
unlink(git_path_merge_head(r));
+ unlink(git_path_auto_merge(r));
delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
if (item->command == TODO_BREAK) {
@@ -4124,8 +4326,8 @@ static int pick_commits(struct repository *r,
setenv(GIT_REFLOG_ACTION, reflog_message(opts,
command_to_string(item->command), NULL),
1);
- res = do_pick_commit(r, item->command, item->commit,
- opts, is_final_fixup(todo_list),
+ res = do_pick_commit(r, item, opts,
+ is_final_fixup(todo_list),
&check_todo);
if (is_rebase_i(opts))
setenv(GIT_REFLOG_ACTION, prev_reflog_action, 1);
@@ -4370,14 +4572,33 @@ cleanup_head_ref:
return sequencer_remove_state(opts);
}
-static int continue_single_pick(struct repository *r)
+static int continue_single_pick(struct repository *r, struct replay_opts *opts)
{
- const char *argv[] = { "commit", NULL };
+ struct strvec argv = STRVEC_INIT;
+ int ret;
if (!refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD") &&
!refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD"))
return error(_("no cherry-pick or revert in progress"));
- return run_command_v_opt(argv, RUN_GIT_CMD);
+
+ strvec_push(&argv, "commit");
+
+ /*
+ * continue_single_pick() handles the case of recovering from a
+ * conflict. should_edit() doesn't handle that case; for a conflict,
+ * we want to edit if the user asked for it, or if they didn't specify
+ * and stdin is a tty.
+ */
+ if (!opts->edit || (opts->edit < 0 && !isatty(0)))
+ /*
+ * Include --cleanup=strip as well because we don't want the
+ * "# Conflicts:" messages.
+ */
+ strvec_pushl(&argv, "--no-edit", "--cleanup=strip", NULL);
+
+ ret = run_command_v_opt(argv.v, RUN_GIT_CMD);
+ strvec_clear(&argv);
+ return ret;
}
static int commit_staged_changes(struct repository *r,
@@ -4505,6 +4726,7 @@ static int commit_staged_changes(struct repository *r,
return error(_("could not commit staged changes."));
unlink(rebase_path_amend());
unlink(git_path_merge_head(r));
+ unlink(git_path_auto_merge(r));
if (final_fixup) {
unlink(rebase_path_fixup_msg());
unlink(rebase_path_squash_msg());
@@ -4547,7 +4769,7 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)
goto release_todo_list;
}
} else if (!file_exists(get_todo_path(opts)))
- return continue_single_pick(r);
+ return continue_single_pick(r, opts);
else if ((res = read_populate_todo(r, &todo_list, opts)))
goto release_todo_list;
@@ -4556,7 +4778,7 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)
if (refs_ref_exists(get_main_ref_store(r),
"CHERRY_PICK_HEAD") ||
refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD")) {
- res = continue_single_pick(r);
+ res = continue_single_pick(r, opts);
if (res)
goto release_todo_list;
}
@@ -4587,11 +4809,14 @@ static int single_pick(struct repository *r,
struct replay_opts *opts)
{
int check_todo;
+ struct todo_item item;
+
+ item.command = opts->action == REPLAY_PICK ?
+ TODO_PICK : TODO_REVERT;
+ item.commit = cmit;
setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
- return do_pick_commit(r, opts->action == REPLAY_PICK ?
- TODO_PICK : TODO_REVERT, cmit, opts, 0,
- &check_todo);
+ return do_pick_commit(r, &item, opts, 0, &check_todo);
}
int sequencer_pick_revisions(struct repository *r,
@@ -5171,7 +5396,7 @@ void todo_list_add_exec_commands(struct todo_list *todo_list,
int i, insert, nr = 0, alloc = 0;
struct todo_item *items = NULL, *base_items = NULL;
- base_items = xcalloc(commands->nr, sizeof(struct todo_item));
+ CALLOC_ARRAY(base_items, commands->nr);
for (i = 0; i < commands->nr; i++) {
size_t command_len = strlen(commands->items[i].string);
@@ -5262,6 +5487,14 @@ static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_lis
short_commit_name(item->commit) :
oid_to_hex(&item->commit->object.oid);
+ if (item->command == TODO_FIXUP) {
+ if (item->flags & TODO_EDIT_FIXUP_MSG)
+ strbuf_addstr(buf, " -c");
+ else if (item->flags & TODO_REPLACE_FIXUP_MSG) {
+ strbuf_addstr(buf, " -C");
+ }
+ }
+
if (item->command == TODO_MERGE) {
if (item->flags & TODO_EDIT_MERGE_MSG)
strbuf_addstr(buf, " -c");
@@ -5462,6 +5695,12 @@ static int subject2item_cmp(const void *fndata,
define_commit_slab(commit_todo_item, struct todo_item *);
+static int skip_fixupish(const char *subject, const char **p) {
+ return skip_prefix(subject, "fixup! ", p) ||
+ skip_prefix(subject, "amend! ", p) ||
+ skip_prefix(subject, "squash! ", p);
+}
+
/*
* Rearrange the todo list that has both "pick commit-id msg" and "pick
* commit-id fixup!/squash! msg" in it so that the latter is put immediately
@@ -5520,15 +5759,13 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)
format_subject(&buf, subject, " ");
subject = subjects[i] = strbuf_detach(&buf, &subject_len);
unuse_commit_buffer(item->commit, commit_buffer);
- if ((skip_prefix(subject, "fixup! ", &p) ||
- skip_prefix(subject, "squash! ", &p))) {
+ if (skip_fixupish(subject, &p)) {
struct commit *commit2;
for (;;) {
while (isspace(*p))
p++;
- if (!skip_prefix(p, "fixup! ", &p) &&
- !skip_prefix(p, "squash! ", &p))
+ if (!skip_fixupish(p, &p))
break;
}
@@ -5558,9 +5795,14 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)
}
if (i2 >= 0) {
rearranged = 1;
- todo_list->items[i].command =
- starts_with(subject, "fixup!") ?
- TODO_FIXUP : TODO_SQUASH;
+ if (starts_with(subject, "fixup!")) {
+ todo_list->items[i].command = TODO_FIXUP;
+ } else if (starts_with(subject, "amend!")) {
+ todo_list->items[i].command = TODO_FIXUP;
+ todo_list->items[i].flags = TODO_REPLACE_FIXUP_MSG;
+ } else {
+ todo_list->items[i].command = TODO_SQUASH;
+ }
if (tail[i2] < 0) {
next[i] = next[i2];
next[i2] = i;
diff --git a/sequencer.h b/sequencer.h
index f8b2e4ab85..d57d8ea23d 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -31,8 +31,10 @@ enum commit_msg_cleanup_mode {
struct replay_opts {
enum replay_action action;
- /* Boolean options */
+ /* Tri-state options: unspecified, false, or true */
int edit;
+
+ /* Boolean options */
int record_origin;
int no_commit;
int signoff;
@@ -71,7 +73,7 @@ struct replay_opts {
/* Only used by REPLAY_NONE */
struct rev_info *revs;
};
-#define REPLAY_OPTS_INIT { .action = -1, .current_fixups = STRBUF_INIT }
+#define REPLAY_OPTS_INIT { .edit = -1, .action = -1, .current_fixups = STRBUF_INIT }
/*
* Note that ordering matters in this enum. Not only must it match the mapping
diff --git a/serve.c b/serve.c
index ac20c72763..aa8209f147 100644
--- a/serve.c
+++ b/serve.c
@@ -5,6 +5,7 @@
#include "version.h"
#include "strvec.h"
#include "ls-refs.h"
+#include "protocol-caps.h"
#include "serve.h"
#include "upload-pack.h"
@@ -78,6 +79,7 @@ static struct protocol_capability capabilities[] = {
{ "server-option", always_advertise, NULL },
{ "object-format", object_format_advertise, NULL },
{ "session-id", session_id_advertise, NULL },
+ { "object-info", always_advertise, cap_object_info },
};
static void advertise_capabilities(void)
diff --git a/server-info.c b/server-info.c
index bae2cdfd51..de0aa4498c 100644
--- a/server-info.c
+++ b/server-info.c
@@ -296,7 +296,7 @@ static void init_pack_info(const char *infofile, int force)
i = num_pack++;
ALLOC_GROW(info, num_pack, alloc);
- info[i] = xcalloc(1, sizeof(struct pack_info));
+ CALLOC_ARRAY(info[i], 1);
info[i]->p = p;
info[i]->old_num = -1;
}
diff --git a/setup.c b/setup.c
index c04cd25a30..59e2facd9d 100644
--- a/setup.c
+++ b/setup.c
@@ -1274,18 +1274,10 @@ const char *setup_git_directory_gently(int *nongit_ok)
* the GIT_PREFIX environment variable must always match. For details
* see Documentation/config/alias.txt.
*/
- if (nongit_ok && *nongit_ok) {
+ if (nongit_ok && *nongit_ok)
startup_info->have_repository = 0;
- startup_info->prefix = NULL;
- setenv(GIT_PREFIX_ENVIRONMENT, "", 1);
- } else {
+ else
startup_info->have_repository = 1;
- startup_info->prefix = prefix;
- if (prefix)
- setenv(GIT_PREFIX_ENVIRONMENT, prefix, 1);
- else
- setenv(GIT_PREFIX_ENVIRONMENT, "", 1);
- }
/*
* Not all paths through the setup code will call 'set_git_dir()' (which
@@ -1311,6 +1303,22 @@ const char *setup_git_directory_gently(int *nongit_ok)
if (startup_info->have_repository)
repo_set_hash_algo(the_repository, repo_fmt.hash_algo);
}
+ /*
+ * Since precompose_string_if_needed() needs to look at
+ * the core.precomposeunicode configuration, this
+ * has to happen after the above block that finds
+ * out where the repository is, i.e. a preparation
+ * for calling git_config_get_bool().
+ */
+ if (prefix) {
+ prefix = precompose_string_if_needed(prefix);
+ startup_info->prefix = prefix;
+ setenv(GIT_PREFIX_ENVIRONMENT, prefix, 1);
+ } else {
+ startup_info->prefix = NULL;
+ setenv(GIT_PREFIX_ENVIRONMENT, "", 1);
+ }
+
strbuf_release(&dir);
strbuf_release(&gitdir);
diff --git a/simple-ipc.h b/simple-ipc.h
new file mode 100644
index 0000000000..2c48a5ee00
--- /dev/null
+++ b/simple-ipc.h
@@ -0,0 +1,235 @@
+#ifndef GIT_SIMPLE_IPC_H
+#define GIT_SIMPLE_IPC_H
+
+/*
+ * 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.
+ */
+ IPC_STATE__LISTENING = 0,
+
+ /*
+ * The pipe/socket exists, but the daemon is not listening.
+ * Perhaps it is very busy.
+ * Perhaps the daemon died without deleting the path.
+ * Perhaps it is shutting down and draining existing clients.
+ * Perhaps it is dead, but other clients are lingering and
+ * still holding a reference to the pathname.
+ */
+ IPC_STATE__NOT_LISTENING,
+
+ /*
+ * The requested pathname is bogus and no amount of retries
+ * will fix that.
+ */
+ IPC_STATE__INVALID_PATH,
+
+ /*
+ * The requested pathname is not found. This usually means
+ * that there is no daemon present.
+ */
+ IPC_STATE__PATH_NOT_FOUND,
+
+ IPC_STATE__OTHER_ERROR,
+};
+
+struct ipc_client_connect_options {
+ /*
+ * Spin under timeout if the server is running but can't
+ * accept our connection yet. This should always be set
+ * unless you just want to poke the server and see if it
+ * is alive.
+ */
+ unsigned int wait_if_busy:1;
+
+ /*
+ * Spin under timeout if the pipe/socket is not yet present
+ * on the file system. This is useful if we just started
+ * the service and need to wait for it to become ready.
+ */
+ unsigned int wait_if_not_found:1;
+
+ /*
+ * Disallow chdir() when creating a Unix domain socket.
+ */
+ 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, \
+}
+
+/*
+ * Determine if a server is listening on this named pipe or socket using
+ * platform-specific logic. This might just probe the filesystem or it
+ * might make a trivial connection to the server using this pathname.
+ */
+enum ipc_active_state ipc_get_active_state(const char *path);
+
+struct ipc_client_connection {
+ int fd;
+};
+
+/*
+ * Try to connect to the daemon on the named pipe or socket.
+ *
+ * Returns IPC_STATE__LISTENING and a connection handle.
+ *
+ * Otherwise, returns info to help decide whether to retry or to
+ * spawn/respawn the server.
+ */
+enum ipc_active_state ipc_client_try_connect(
+ const char *path,
+ const struct ipc_client_connect_options *options,
+ struct ipc_client_connection **p_connection);
+
+void ipc_client_close_connection(struct ipc_client_connection *connection);
+
+/*
+ * Used by the client to synchronously send and receive a message with
+ * the server on the provided client connection.
+ *
+ * Returns 0 when successful.
+ *
+ * Calls error() and returns non-zero otherwise.
+ */
+int ipc_client_send_command_to_connection(
+ struct ipc_client_connection *connection,
+ const char *message, struct strbuf *answer);
+
+/*
+ * Used by the client to synchronously connect and send and receive a
+ * message to the server listening at the given path.
+ *
+ * Returns 0 when successful.
+ *
+ * Calls error() and returns non-zero otherwise.
+ */
+int ipc_client_send_command(const char *path,
+ const struct ipc_client_connect_options *options,
+ const char *message, struct strbuf *answer);
+
+/*
+ * Simple IPC Server Side API.
+ */
+
+struct ipc_server_reply_data;
+
+typedef int (ipc_server_reply_cb)(struct ipc_server_reply_data *,
+ const char *response,
+ size_t response_len);
+
+/*
+ * Prototype for an application-supplied callback to process incoming
+ * client IPC messages and compose a reply. The `application_cb` should
+ * use the provided `reply_cb` and `reply_data` to send an IPC response
+ * back to the client. The `reply_cb` callback can be called multiple
+ * times for chunking purposes. A reply message is optional and may be
+ * omitted if not necessary for the application.
+ *
+ * The return value from the application callback is ignored.
+ * The value `SIMPLE_IPC_QUIT` can be used to shutdown the server.
+ */
+typedef int (ipc_server_application_cb)(void *application_data,
+ const char *request,
+ ipc_server_reply_cb *reply_cb,
+ struct ipc_server_reply_data *reply_data);
+
+#define SIMPLE_IPC_QUIT -2
+
+/*
+ * Opaque instance data to represent an IPC server instance.
+ */
+struct ipc_server_data;
+
+/*
+ * Control parameters for the IPC server instance.
+ * Use this to hide platform-specific settings.
+ */
+struct ipc_server_opts
+{
+ int nr_threads;
+
+ /*
+ * Disallow chdir() when creating a Unix domain socket.
+ */
+ unsigned int uds_disallow_chdir:1;
+};
+
+/*
+ * Start an IPC server instance in one or more background threads
+ * and return a handle to the pool.
+ *
+ * Returns 0 if the asynchronous server pool was started successfully.
+ * Returns -1 if not.
+ * Returns -2 if we could not startup because another server is using
+ * the socket or named pipe.
+ *
+ * When a client IPC message is received, the `application_cb` will be
+ * called (possibly on a random thread) to handle the message and
+ * optionally compose a reply message.
+ */
+int ipc_server_run_async(struct ipc_server_data **returned_server_data,
+ const char *path, const struct ipc_server_opts *opts,
+ ipc_server_application_cb *application_cb,
+ void *application_data);
+
+/*
+ * Gently signal the IPC server pool to shutdown. No new client
+ * connections will be accepted, but existing connections will be
+ * allowed to complete.
+ */
+int ipc_server_stop_async(struct ipc_server_data *server_data);
+
+/*
+ * Block the calling thread until all threads in the IPC server pool
+ * have completed and been joined.
+ */
+int ipc_server_await(struct ipc_server_data *server_data);
+
+/*
+ * Close and free all resource handles associated with the IPC server
+ * pool.
+ */
+void ipc_server_free(struct ipc_server_data *server_data);
+
+/*
+ * Run an IPC server instance and block the calling thread of the
+ * current process. It does not return until the IPC server has
+ * either shutdown or had an unrecoverable error.
+ *
+ * The IPC server handles incoming IPC messages from client processes
+ * and may use one or more background threads as necessary.
+ *
+ * Returns 0 after the server has completed successfully.
+ * Returns -1 if the server cannot be started.
+ * Returns -2 if we could not startup because another server is using
+ * the socket or named pipe.
+ *
+ * When a client IPC message is received, the `application_cb` will be
+ * called (possibly on a random thread) to handle the message and
+ * optionally compose a reply message.
+ *
+ * Note that `ipc_server_run()` is a synchronous wrapper around the
+ * above asynchronous routines. It effectively hides all of the
+ * server state and thread details from the caller and presents a
+ * simple synchronous interface.
+ */
+int ipc_server_run(const char *path, const struct ipc_server_opts *opts,
+ ipc_server_application_cb *application_cb,
+ void *application_data);
+
+#endif /* SUPPORTS_SIMPLE_IPC */
+#endif /* GIT_SIMPLE_IPC_H */
diff --git a/sparse-index.c b/sparse-index.c
new file mode 100644
index 0000000000..affc4048f2
--- /dev/null
+++ b/sparse-index.c
@@ -0,0 +1,349 @@
+#include "cache.h"
+#include "repository.h"
+#include "sparse-index.h"
+#include "tree.h"
+#include "pathspec.h"
+#include "trace2.h"
+#include "cache-tree.h"
+#include "config.h"
+#include "dir.h"
+#include "fsmonitor.h"
+
+static struct cache_entry *construct_sparse_dir_entry(
+ struct index_state *istate,
+ const char *sparse_dir,
+ struct cache_tree *tree)
+{
+ struct cache_entry *de;
+
+ de = make_cache_entry(istate, S_IFDIR, &tree->oid, sparse_dir, 0, 0);
+
+ de->ce_flags |= CE_SKIP_WORKTREE;
+ return de;
+}
+
+/*
+ * Returns the number of entries "inserted" into the index.
+ */
+static int convert_to_sparse_rec(struct index_state *istate,
+ int num_converted,
+ int start, int end,
+ const char *ct_path, size_t ct_pathlen,
+ struct cache_tree *ct)
+{
+ 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)
+ can_convert = 0;
+
+ for (i = start; can_convert && i < end; i++) {
+ struct cache_entry *ce = istate->cache[i];
+
+ if (ce_stage(ce) ||
+ S_ISGITLINK(ce->ce_mode) ||
+ !(ce->ce_flags & CE_SKIP_WORKTREE))
+ can_convert = 0;
+ }
+
+ if (can_convert) {
+ struct cache_entry *se;
+ se = construct_sparse_dir_entry(istate, ct_path, ct);
+
+ istate->cache[num_converted++] = se;
+ return 1;
+ }
+
+ for (i = start; i < end; ) {
+ int count, span, pos = -1;
+ const char *base, *slash;
+ struct cache_entry *ce = istate->cache[i];
+
+ /*
+ * Detect if this is a normal entry outside of any subtree
+ * entry.
+ */
+ base = ce->name + ct_pathlen;
+ slash = strchr(base, '/');
+
+ if (slash)
+ pos = cache_tree_subtree_pos(ct, base, slash - base);
+
+ if (pos < 0) {
+ istate->cache[num_converted++] = ce;
+ i++;
+ continue;
+ }
+
+ strbuf_setlen(&child_path, 0);
+ strbuf_add(&child_path, ce->name, slash - ce->name + 1);
+
+ span = ct->down[pos]->cache_tree->entry_count;
+ count = convert_to_sparse_rec(istate,
+ num_converted, i, i + span,
+ child_path.buf, child_path.len,
+ ct->down[pos]->cache_tree);
+ num_converted += count;
+ i += span;
+ }
+
+ strbuf_release(&child_path);
+ return num_converted - start_converted;
+}
+
+int set_sparse_index_config(struct repository *repo, int enable)
+{
+ int res;
+ char *config_path = repo_git_path(repo, "config.worktree");
+ res = git_config_set_in_file_gently(config_path,
+ "index.sparse",
+ enable ? "true" : NULL);
+ free(config_path);
+
+ prepare_repo_settings(repo);
+ repo->settings.sparse_index = enable;
+ return res;
+}
+
+int convert_to_sparse(struct index_state *istate)
+{
+ int test_env;
+ if (istate->split_index || istate->sparse_index ||
+ !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 (!istate->sparse_checkout_patterns) {
+ istate->sparse_checkout_patterns = xcalloc(1, sizeof(struct pattern_list));
+ if (get_sparse_checkout_patterns(istate->sparse_checkout_patterns) < 0)
+ return 0;
+ }
+
+ if (!istate->sparse_checkout_patterns->use_cone_patterns) {
+ warning(_("attempting to use sparse-index without cone mode"));
+ return -1;
+ }
+
+ if (cache_tree_update(istate, 0)) {
+ warning(_("unable to update cache-tree, staying full"));
+ return -1;
+ }
+
+ remove_fsmonitor(istate);
+
+ trace2_region_enter("index", "convert_to_sparse", istate->repo);
+ istate->cache_nr = convert_to_sparse_rec(istate,
+ 0, 0, istate->cache_nr,
+ "", 0, istate->cache_tree);
+
+ /* Clear and recompute the cache-tree */
+ cache_tree_free(&istate->cache_tree);
+ cache_tree_update(istate, 0);
+
+ istate->sparse_index = 1;
+ trace2_region_leave("index", "convert_to_sparse", istate->repo);
+ return 0;
+}
+
+static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)
+{
+ ALLOC_GROW(istate->cache, nr + 1, istate->cache_alloc);
+
+ istate->cache[nr] = ce;
+ add_name_hash(istate, ce);
+}
+
+static int add_path_to_index(const struct object_id *oid,
+ struct strbuf *base, const char *path,
+ unsigned int mode, void *context)
+{
+ struct index_state *istate = (struct index_state *)context;
+ struct cache_entry *ce;
+ size_t len = base->len;
+
+ if (S_ISDIR(mode))
+ return READ_TREE_RECURSIVE;
+
+ strbuf_addstr(base, path);
+
+ ce = make_cache_entry(istate, mode, oid, base->buf, 0, 0);
+ ce->ce_flags |= CE_SKIP_WORKTREE;
+ set_index_entry(istate, istate->cache_nr++, ce);
+
+ strbuf_setlen(base, len);
+ return 0;
+}
+
+void ensure_full_index(struct index_state *istate)
+{
+ int i;
+ struct index_state *full;
+ struct strbuf base = STRBUF_INIT;
+
+ if (!istate || !istate->sparse_index)
+ return;
+
+ if (!istate->repo)
+ istate->repo = the_repository;
+
+ trace2_region_enter("index", "ensure_full_index", istate->repo);
+
+ /* initialize basics of new index */
+ full = xcalloc(1, sizeof(struct index_state));
+ memcpy(full, istate, sizeof(struct index_state));
+
+ /* then change the necessary things */
+ full->sparse_index = 0;
+ full->cache_alloc = (3 * istate->cache_alloc) / 2;
+ full->cache_nr = 0;
+ ALLOC_ARRAY(full->cache, full->cache_alloc);
+
+ for (i = 0; i < istate->cache_nr; i++) {
+ struct cache_entry *ce = istate->cache[i];
+ struct tree *tree;
+ struct pathspec ps;
+
+ if (!S_ISSPARSEDIR(ce->ce_mode)) {
+ set_index_entry(full, full->cache_nr++, ce);
+ continue;
+ }
+ if (!(ce->ce_flags & CE_SKIP_WORKTREE))
+ warning(_("index entry is a directory, but not sparse (%08x)"),
+ ce->ce_flags);
+
+ /* recursively walk into cd->name */
+ tree = lookup_tree(istate->repo, &ce->oid);
+
+ memset(&ps, 0, sizeof(ps));
+ ps.recursive = 1;
+ ps.has_wildcard = 1;
+ ps.max_depth = -1;
+
+ strbuf_setlen(&base, 0);
+ strbuf_add(&base, ce->name, strlen(ce->name));
+
+ read_tree_at(istate->repo, tree, &base, &ps,
+ add_path_to_index, full);
+
+ /* free directory entries. full entries are re-used */
+ discard_cache_entry(ce);
+ }
+
+ /* Copy back into original index. */
+ memcpy(&istate->name_hash, &full->name_hash, sizeof(full->name_hash));
+ istate->sparse_index = 0;
+ free(istate->cache);
+ istate->cache = full->cache;
+ istate->cache_nr = full->cache_nr;
+ istate->cache_alloc = full->cache_alloc;
+
+ strbuf_release(&base);
+ free(full);
+
+ /* Clear and recompute the cache-tree */
+ cache_tree_free(&istate->cache_tree);
+ cache_tree_update(istate, 0);
+
+ trace2_region_leave("index", "ensure_full_index", istate->repo);
+}
+
+/*
+ * This static global helps avoid infinite recursion between
+ * expand_to_path() and index_file_exists().
+ */
+static int in_expand_to_path = 0;
+
+void expand_to_path(struct index_state *istate,
+ const char *path, size_t pathlen, int icase)
+{
+ struct strbuf path_mutable = STRBUF_INIT;
+ size_t substr_len;
+
+ /* prevent extra recursion */
+ if (in_expand_to_path)
+ return;
+
+ if (!istate || !istate->sparse_index)
+ return;
+
+ if (!istate->repo)
+ istate->repo = the_repository;
+
+ in_expand_to_path = 1;
+
+ /*
+ * We only need to actually expand a region if the
+ * following are both true:
+ *
+ * 1. 'path' is not already in the index.
+ * 2. Some parent directory of 'path' is a sparse directory.
+ */
+
+ if (index_file_exists(istate, path, pathlen, icase))
+ goto cleanup;
+
+ strbuf_add(&path_mutable, path, pathlen);
+ strbuf_addch(&path_mutable, '/');
+
+ /* Check the name hash for all parent directories */
+ substr_len = 0;
+ while (substr_len < pathlen) {
+ char temp;
+ char *replace = strchr(path_mutable.buf + substr_len, '/');
+
+ if (!replace)
+ break;
+
+ /* replace the character _after_ the slash */
+ replace++;
+ temp = *replace;
+ *replace = '\0';
+ if (index_file_exists(istate, path_mutable.buf,
+ path_mutable.len, icase)) {
+ /*
+ * We found a parent directory in the name-hash
+ * hashtable, because only sparse directory entries
+ * have a trailing '/' character. Since "path" wasn't
+ * in the index, perhaps it exists within this
+ * sparse-directory. Expand accordingly.
+ */
+ ensure_full_index(istate);
+ break;
+ }
+
+ *replace = temp;
+ substr_len = replace - path_mutable.buf;
+ }
+
+cleanup:
+ strbuf_release(&path_mutable);
+ in_expand_to_path = 0;
+}
diff --git a/sparse-index.h b/sparse-index.h
new file mode 100644
index 0000000000..1115a0d7dd
--- /dev/null
+++ b/sparse-index.h
@@ -0,0 +1,23 @@
+#ifndef SPARSE_INDEX_H__
+#define SPARSE_INDEX_H__
+
+struct index_state;
+int convert_to_sparse(struct index_state *istate);
+
+/*
+ * Some places in the codebase expect to search for a specific path.
+ * This path might be outside of the sparse-checkout definition, in
+ * which case a sparse-index may not contain a path for that index.
+ *
+ * Given an index and a path, check to see if a leading directory for
+ * 'path' exists in the index as a sparse directory. In that case,
+ * expand that sparse directory to a full range of cache entries and
+ * populate the index accordingly.
+ */
+void expand_to_path(struct index_state *istate,
+ const char *path, size_t pathlen, int icase);
+
+struct repository;
+int set_sparse_index_config(struct repository *repo, int enable);
+
+#endif
diff --git a/split-index.c b/split-index.c
index c0e8ad670d..4d6e52d46f 100644
--- a/split-index.c
+++ b/split-index.c
@@ -5,7 +5,7 @@
struct split_index *init_split_index(struct index_state *istate)
{
if (!istate->split_index) {
- istate->split_index = xcalloc(1, sizeof(*istate->split_index));
+ CALLOC_ARRAY(istate->split_index, 1);
istate->split_index->refcount = 1;
}
return istate->split_index;
@@ -21,7 +21,7 @@ int read_link_extension(struct index_state *istate,
if (sz < the_hash_algo->rawsz)
return error("corrupt link extension (too short)");
si = init_split_index(istate);
- hashcpy(si->base_oid.hash, data);
+ oidread(&si->base_oid, data);
data += the_hash_algo->rawsz;
sz -= the_hash_algo->rawsz;
if (!sz)
@@ -87,7 +87,7 @@ void move_cache_to_base_index(struct index_state *istate)
mem_pool_combine(istate->ce_mem_pool, istate->split_index->base->ce_mem_pool);
}
- si->base = xcalloc(1, sizeof(*si->base));
+ CALLOC_ARRAY(si->base, 1);
si->base->version = istate->version;
/* zero timestamp disables racy test in ce_write_index() */
si->base->timestamp = istate->timestamp;
diff --git a/strbuf.c b/strbuf.c
index e3397cc4c7..4df30b4549 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -209,6 +209,8 @@ void strbuf_list_free(struct strbuf **sbs)
{
struct strbuf **s = sbs;
+ if (!s)
+ return;
while (*s) {
strbuf_release(*s);
free(*s++);
diff --git a/streaming.c b/streaming.c
index 800f07a52c..5f480ad50c 100644
--- a/streaming.c
+++ b/streaming.c
@@ -8,54 +8,13 @@
#include "replace-object.h"
#include "packfile.h"
-enum input_source {
- stream_error = -1,
- incore = 0,
- loose = 1,
- pack_non_delta = 2
-};
-
typedef int (*open_istream_fn)(struct git_istream *,
struct repository *,
- struct object_info *,
const struct object_id *,
enum object_type *);
typedef int (*close_istream_fn)(struct git_istream *);
typedef ssize_t (*read_istream_fn)(struct git_istream *, char *, size_t);
-struct stream_vtbl {
- close_istream_fn close;
- read_istream_fn read;
-};
-
-#define open_method_decl(name) \
- int open_istream_ ##name \
- (struct git_istream *st, struct repository *r, \
- struct object_info *oi, const struct object_id *oid, \
- enum object_type *type)
-
-#define close_method_decl(name) \
- int close_istream_ ##name \
- (struct git_istream *st)
-
-#define read_method_decl(name) \
- ssize_t read_istream_ ##name \
- (struct git_istream *st, char *buf, size_t sz)
-
-/* forward declaration */
-static open_method_decl(incore);
-static open_method_decl(loose);
-static open_method_decl(pack_non_delta);
-static struct git_istream *attach_stream_filter(struct git_istream *st,
- struct stream_filter *filter);
-
-
-static open_istream_fn open_istream_tbl[] = {
- open_istream_incore,
- open_istream_loose,
- open_istream_pack_non_delta,
-};
-
#define FILTER_BUFFER (1024*16)
struct filtered_istream {
@@ -69,7 +28,10 @@ struct filtered_istream {
};
struct git_istream {
- const struct stream_vtbl *vtbl;
+ open_istream_fn open;
+ close_istream_fn close;
+ read_istream_fn read;
+
unsigned long size; /* inflated size of full object */
git_zstream z;
enum { z_unused, z_used, z_done, z_error } z_state;
@@ -97,80 +59,6 @@ struct git_istream {
} u;
};
-int close_istream(struct git_istream *st)
-{
- int r = st->vtbl->close(st);
- free(st);
- return r;
-}
-
-ssize_t read_istream(struct git_istream *st, void *buf, size_t sz)
-{
- return st->vtbl->read(st, buf, sz);
-}
-
-static enum input_source istream_source(struct repository *r,
- const struct object_id *oid,
- enum object_type *type,
- struct object_info *oi)
-{
- unsigned long size;
- int status;
-
- oi->typep = type;
- oi->sizep = &size;
- status = oid_object_info_extended(r, oid, oi, 0);
- if (status < 0)
- return stream_error;
-
- switch (oi->whence) {
- case OI_LOOSE:
- return loose;
- case OI_PACKED:
- if (!oi->u.packed.is_delta && big_file_threshold < size)
- return pack_non_delta;
- /* fallthru */
- default:
- return incore;
- }
-}
-
-struct git_istream *open_istream(struct repository *r,
- const struct object_id *oid,
- enum object_type *type,
- unsigned long *size,
- struct stream_filter *filter)
-{
- struct git_istream *st;
- struct object_info oi = OBJECT_INFO_INIT;
- const struct object_id *real = lookup_replace_object(r, oid);
- enum input_source src = istream_source(r, real, type, &oi);
-
- if (src < 0)
- return NULL;
-
- st = xmalloc(sizeof(*st));
- if (open_istream_tbl[src](st, r, &oi, real, type)) {
- if (open_istream_incore(st, r, &oi, real, type)) {
- free(st);
- return NULL;
- }
- }
- if (filter) {
- /* Add "&& !is_null_stream_filter(filter)" for performance */
- struct git_istream *nst = attach_stream_filter(st, filter);
- if (!nst) {
- close_istream(st);
- return NULL;
- }
- st = nst;
- }
-
- *size = st->size;
- return st;
-}
-
-
/*****************************************************************
*
* Common helpers
@@ -190,13 +78,14 @@ static void close_deflated_stream(struct git_istream *st)
*
*****************************************************************/
-static close_method_decl(filtered)
+static int close_istream_filtered(struct git_istream *st)
{
free_stream_filter(st->u.filtered.filter);
return close_istream(st->u.filtered.upstream);
}
-static read_method_decl(filtered)
+static ssize_t read_istream_filtered(struct git_istream *st, char *buf,
+ size_t sz)
{
struct filtered_istream *fs = &(st->u.filtered);
size_t filled = 0;
@@ -255,18 +144,14 @@ static read_method_decl(filtered)
return filled;
}
-static struct stream_vtbl filtered_vtbl = {
- close_istream_filtered,
- read_istream_filtered,
-};
-
static struct git_istream *attach_stream_filter(struct git_istream *st,
struct stream_filter *filter)
{
struct git_istream *ifs = xmalloc(sizeof(*ifs));
struct filtered_istream *fs = &(ifs->u.filtered);
- ifs->vtbl = &filtered_vtbl;
+ ifs->close = close_istream_filtered;
+ ifs->read = read_istream_filtered;
fs->upstream = st;
fs->filter = filter;
fs->i_end = fs->i_ptr = 0;
@@ -282,7 +167,7 @@ static struct git_istream *attach_stream_filter(struct git_istream *st,
*
*****************************************************************/
-static read_method_decl(loose)
+static ssize_t read_istream_loose(struct git_istream *st, char *buf, size_t sz)
{
size_t total_read = 0;
@@ -327,19 +212,16 @@ static read_method_decl(loose)
return total_read;
}
-static close_method_decl(loose)
+static int close_istream_loose(struct git_istream *st)
{
close_deflated_stream(st);
munmap(st->u.loose.mapped, st->u.loose.mapsize);
return 0;
}
-static struct stream_vtbl loose_vtbl = {
- close_istream_loose,
- read_istream_loose,
-};
-
-static open_method_decl(loose)
+static int open_istream_loose(struct git_istream *st, struct repository *r,
+ const struct object_id *oid,
+ enum object_type *type)
{
st->u.loose.mapped = map_loose_object(r, oid, &st->u.loose.mapsize);
if (!st->u.loose.mapped)
@@ -358,8 +240,9 @@ static open_method_decl(loose)
st->u.loose.hdr_used = strlen(st->u.loose.hdr) + 1;
st->u.loose.hdr_avail = st->z.total_out;
st->z_state = z_used;
+ st->close = close_istream_loose;
+ st->read = read_istream_loose;
- st->vtbl = &loose_vtbl;
return 0;
}
@@ -370,7 +253,8 @@ static open_method_decl(loose)
*
*****************************************************************/
-static read_method_decl(pack_non_delta)
+static ssize_t read_istream_pack_non_delta(struct git_istream *st, char *buf,
+ size_t sz)
{
size_t total_read = 0;
@@ -428,24 +312,20 @@ static read_method_decl(pack_non_delta)
return total_read;
}
-static close_method_decl(pack_non_delta)
+static int close_istream_pack_non_delta(struct git_istream *st)
{
close_deflated_stream(st);
return 0;
}
-static struct stream_vtbl pack_non_delta_vtbl = {
- close_istream_pack_non_delta,
- read_istream_pack_non_delta,
-};
-
-static open_method_decl(pack_non_delta)
+static int open_istream_pack_non_delta(struct git_istream *st,
+ struct repository *r,
+ const struct object_id *oid,
+ enum object_type *type)
{
struct pack_window *window;
enum object_type in_pack_type;
- st->u.in_pack.pack = oi->u.packed.pack;
- st->u.in_pack.pos = oi->u.packed.offset;
window = NULL;
in_pack_type = unpack_object_header(st->u.in_pack.pack,
@@ -463,7 +343,9 @@ static open_method_decl(pack_non_delta)
break;
}
st->z_state = z_unused;
- st->vtbl = &pack_non_delta_vtbl;
+ st->close = close_istream_pack_non_delta;
+ st->read = read_istream_pack_non_delta;
+
return 0;
}
@@ -474,13 +356,13 @@ static open_method_decl(pack_non_delta)
*
*****************************************************************/
-static close_method_decl(incore)
+static int close_istream_incore(struct git_istream *st)
{
free(st->u.incore.buf);
return 0;
}
-static read_method_decl(incore)
+static ssize_t read_istream_incore(struct git_istream *st, char *buf, size_t sz)
{
size_t read_size = sz;
size_t remainder = st->size - st->u.incore.read_ptr;
@@ -494,25 +376,105 @@ static read_method_decl(incore)
return read_size;
}
-static struct stream_vtbl incore_vtbl = {
- close_istream_incore,
- read_istream_incore,
-};
-
-static open_method_decl(incore)
+static int open_istream_incore(struct git_istream *st, struct repository *r,
+ const struct object_id *oid, enum object_type *type)
{
st->u.incore.buf = read_object_file_extended(r, oid, type, &st->size, 0);
st->u.incore.read_ptr = 0;
- st->vtbl = &incore_vtbl;
+ st->close = close_istream_incore;
+ st->read = read_istream_incore;
return st->u.incore.buf ? 0 : -1;
}
+/*****************************************************************************
+ * static helpers variables and functions for users of streaming interface
+ *****************************************************************************/
+
+static int istream_source(struct git_istream *st,
+ struct repository *r,
+ const struct object_id *oid,
+ enum object_type *type)
+{
+ unsigned long size;
+ int status;
+ struct object_info oi = OBJECT_INFO_INIT;
+
+ oi.typep = type;
+ oi.sizep = &size;
+ status = oid_object_info_extended(r, oid, &oi, 0);
+ if (status < 0)
+ return status;
+
+ switch (oi.whence) {
+ case OI_LOOSE:
+ st->open = open_istream_loose;
+ return 0;
+ case OI_PACKED:
+ if (!oi.u.packed.is_delta && big_file_threshold < size) {
+ st->u.in_pack.pack = oi.u.packed.pack;
+ st->u.in_pack.pos = oi.u.packed.offset;
+ st->open = open_istream_pack_non_delta;
+ return 0;
+ }
+ /* fallthru */
+ default:
+ st->open = open_istream_incore;
+ return 0;
+ }
+}
/****************************************************************
* Users of streaming interface
****************************************************************/
+int close_istream(struct git_istream *st)
+{
+ int r = st->close(st);
+ free(st);
+ return r;
+}
+
+ssize_t read_istream(struct git_istream *st, void *buf, size_t sz)
+{
+ return st->read(st, buf, sz);
+}
+
+struct git_istream *open_istream(struct repository *r,
+ const struct object_id *oid,
+ enum object_type *type,
+ unsigned long *size,
+ struct stream_filter *filter)
+{
+ struct git_istream *st = xmalloc(sizeof(*st));
+ const struct object_id *real = lookup_replace_object(r, oid);
+ int ret = istream_source(st, r, real, type);
+
+ if (ret) {
+ free(st);
+ return NULL;
+ }
+
+ if (st->open(st, r, real, type)) {
+ if (open_istream_incore(st, r, real, type)) {
+ free(st);
+ return NULL;
+ }
+ }
+ if (filter) {
+ /* Add "&& !is_null_stream_filter(filter)" for performance */
+ struct git_istream *nst = attach_stream_filter(st, filter);
+ if (!nst) {
+ close_istream(st);
+ return NULL;
+ }
+ st = nst;
+ }
+
+ *size = st->size;
+ return st;
+}
+
int stream_blob_to_fd(int fd, const struct object_id *oid, struct stream_filter *filter,
int can_seek)
{
diff --git a/submodule-config.c b/submodule-config.c
index f502505566..2026120fb3 100644
--- a/submodule-config.c
+++ b/submodule-config.c
@@ -671,7 +671,7 @@ static int gitmodules_cb(const char *var, const char *value, void *data)
parameter.cache = repo->submodule_cache;
parameter.treeish_name = NULL;
- parameter.gitmodules_oid = &null_oid;
+ parameter.gitmodules_oid = null_oid();
parameter.overwrite = 1;
return parse_config(var, value, &parameter);
diff --git a/submodule.c b/submodule.c
index 9767ba9893..0b1d9c1dde 100644
--- a/submodule.c
+++ b/submodule.c
@@ -33,7 +33,7 @@ static struct oid_array ref_tips_after_fetch;
* will be disabled because we can't guess what might be configured in
* .gitmodules unless the user resolves the conflict.
*/
-int is_gitmodules_unmerged(const struct index_state *istate)
+int is_gitmodules_unmerged(struct index_state *istate)
{
int pos = index_name_pos(istate, GITMODULES_FILE, strlen(GITMODULES_FILE));
if (pos < 0) { /* .gitmodules not found or isn't merged */
@@ -113,7 +113,7 @@ int update_path_in_gitmodules(const char *oldpath, const char *newpath)
if (is_gitmodules_unmerged(the_repository->index))
die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first"));
- submodule = submodule_from_path(the_repository, &null_oid, oldpath);
+ submodule = submodule_from_path(the_repository, null_oid(), oldpath);
if (!submodule || !submodule->name) {
warning(_("Could not find section in .gitmodules where path=%s"), oldpath);
return -1;
@@ -142,7 +142,7 @@ int remove_path_from_gitmodules(const char *path)
if (is_gitmodules_unmerged(the_repository->index))
die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first"));
- submodule = submodule_from_path(the_repository, &null_oid, path);
+ submodule = submodule_from_path(the_repository, null_oid(), path);
if (!submodule || !submodule->name) {
warning(_("Could not find section in .gitmodules where path=%s"), path);
return -1;
@@ -188,7 +188,8 @@ void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
const char *path)
{
const struct submodule *submodule = submodule_from_path(the_repository,
- &null_oid, path);
+ null_oid(),
+ path);
if (submodule) {
const char *ignore;
char *key;
@@ -244,7 +245,7 @@ int is_submodule_active(struct repository *repo, const char *path)
const struct string_list *sl;
const struct submodule *module;
- module = submodule_from_path(repo, &null_oid, path);
+ module = submodule_from_path(repo, null_oid(), path);
/* early return if there isn't a path->module mapping */
if (!module)
@@ -301,7 +302,7 @@ int is_submodule_populated_gently(const char *path, int *return_error_code)
/*
* Dies if the provided 'prefix' corresponds to an unpopulated submodule
*/
-void die_in_unpopulated_submodule(const struct index_state *istate,
+void die_in_unpopulated_submodule(struct index_state *istate,
const char *prefix)
{
int i, prefixlen;
@@ -331,7 +332,7 @@ void die_in_unpopulated_submodule(const struct index_state *istate,
/*
* Dies if any paths in the provided pathspec descends into a submodule
*/
-void die_path_inside_submodule(const struct index_state *istate,
+void die_path_inside_submodule(struct index_state *istate,
const struct pathspec *ps)
{
int i, j;
@@ -745,7 +746,7 @@ const struct submodule *submodule_from_ce(const struct cache_entry *ce)
if (!should_update_submodules())
return NULL;
- return submodule_from_path(the_repository, &null_oid, ce->name);
+ return submodule_from_path(the_repository, null_oid(), ce->name);
}
static struct oid_array *submodule_commits(struct string_list *submodules,
@@ -1037,7 +1038,7 @@ int find_unpushed_submodules(struct repository *r,
const struct submodule *submodule;
const char *path = NULL;
- submodule = submodule_from_name(r, &null_oid, name->string);
+ submodule = submodule_from_name(r, null_oid(), name->string);
if (submodule)
path = submodule->path;
else
@@ -1224,7 +1225,7 @@ static void calculate_changed_submodule_paths(struct repository *r,
const struct submodule *submodule;
const char *path = NULL;
- submodule = submodule_from_name(r, &null_oid, name->string);
+ submodule = submodule_from_name(r, null_oid(), name->string);
if (submodule)
path = submodule->path;
else
@@ -1361,7 +1362,7 @@ static struct fetch_task *fetch_task_create(struct repository *r,
struct fetch_task *task = xmalloc(sizeof(*task));
memset(task, 0, sizeof(*task));
- task->sub = submodule_from_path(r, &null_oid, path);
+ task->sub = submodule_from_path(r, null_oid(), path);
if (!task->sub) {
/*
* No entry in .gitmodules? Technically not a submodule,
@@ -1917,7 +1918,7 @@ int submodule_move_head(const char *path,
if (old_head && !is_submodule_populated_gently(path, error_code_ptr))
return 0;
- sub = submodule_from_path(the_repository, &null_oid, path);
+ sub = submodule_from_path(the_repository, null_oid(), path);
if (!sub)
BUG("could not get submodule information for '%s'", path);
@@ -2076,7 +2077,7 @@ static void relocate_single_git_dir_into_superproject(const char *path)
real_old_git_dir = real_pathdup(old_git_dir, 1);
- sub = submodule_from_path(the_repository, &null_oid, path);
+ sub = submodule_from_path(the_repository, null_oid(), path);
if (!sub)
die(_("could not lookup name for submodule '%s'"), path);
@@ -2135,7 +2136,7 @@ void absorb_git_dir_into_superproject(const char *path,
* superproject did not rewrite the git file links yet,
* fix it now.
*/
- sub = submodule_from_path(the_repository, &null_oid, 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,
@@ -2283,7 +2284,8 @@ int submodule_to_gitdir(struct strbuf *buf, const char *submodule)
strbuf_addstr(buf, git_dir);
}
if (!is_git_directory(buf->buf)) {
- sub = submodule_from_path(the_repository, &null_oid, submodule);
+ sub = submodule_from_path(the_repository, null_oid(),
+ submodule);
if (!sub) {
ret = -1;
goto cleanup;
diff --git a/submodule.h b/submodule.h
index 4ac6e31cf1..84640c49c1 100644
--- a/submodule.h
+++ b/submodule.h
@@ -39,7 +39,7 @@ struct submodule_update_strategy {
};
#define SUBMODULE_UPDATE_STRATEGY_INIT {SM_UPDATE_UNSPECIFIED, NULL}
-int is_gitmodules_unmerged(const struct index_state *istate);
+int is_gitmodules_unmerged(struct index_state *istate);
int is_writing_gitmodules_ok(void);
int is_staging_gitmodules_ok(struct index_state *istate);
int update_path_in_gitmodules(const char *oldpath, const char *newpath);
@@ -60,9 +60,9 @@ int is_submodule_active(struct repository *repo, const char *path);
* Otherwise the return error code is the same as of resolve_gitdir_gently.
*/
int is_submodule_populated_gently(const char *path, int *return_error_code);
-void die_in_unpopulated_submodule(const struct index_state *istate,
+void die_in_unpopulated_submodule(struct index_state *istate,
const char *prefix);
-void die_path_inside_submodule(const struct index_state *istate,
+void die_path_inside_submodule(struct index_state *istate,
const struct pathspec *ps);
enum submodule_update_type parse_submodule_update_type(const char *value);
int parse_submodule_update_strategy(const char *value,
diff --git a/symlinks.c b/symlinks.c
index 7dbb6b23d9..5232d02020 100644
--- a/symlinks.c
+++ b/symlinks.c
@@ -1,6 +1,7 @@
#include "cache.h"
-static int threaded_check_leading_path(struct cache_def *cache, const char *name, int len);
+static int threaded_check_leading_path(struct cache_def *cache, const char *name,
+ int len, int warn_on_lstat_err);
static int threaded_has_dirs_only_path(struct cache_def *cache, const char *name, int len, int prefix_len);
/*
@@ -72,7 +73,7 @@ static int lstat_cache_matchlen(struct cache_def *cache,
int prefix_len_stat_func)
{
int match_len, last_slash, last_slash_dir, previous_slash;
- int save_flags, ret;
+ int save_flags, ret, saved_errno = 0;
struct stat st;
if (cache->track_flags != track_flags ||
@@ -139,6 +140,7 @@ static int lstat_cache_matchlen(struct cache_def *cache,
if (ret) {
*ret_flags = FL_LSTATERR;
+ saved_errno = errno;
if (errno == ENOENT)
*ret_flags |= FL_NOENT;
} else if (S_ISDIR(st.st_mode)) {
@@ -180,6 +182,8 @@ static int lstat_cache_matchlen(struct cache_def *cache,
} else {
reset_lstat_cache(cache);
}
+ if (saved_errno)
+ errno = saved_errno;
return match_len;
}
@@ -202,57 +206,47 @@ int threaded_has_symlink_leading_path(struct cache_def *cache, const char *name,
return lstat_cache(cache, name, len, FL_SYMLINK|FL_DIR, USE_ONLY_LSTAT) & FL_SYMLINK;
}
-/*
- * Return non-zero if path 'name' has a leading symlink component
- */
int has_symlink_leading_path(const char *name, int len)
{
return threaded_has_symlink_leading_path(&default_cache, name, len);
}
-/*
- * Return zero if path 'name' has a leading symlink component or
- * if some leading path component does not exists.
- *
- * Return -1 if leading path exists and is a directory.
- *
- * Return path length if leading path exists and is neither a
- * directory nor a symlink.
- */
-int check_leading_path(const char *name, int len)
+int check_leading_path(const char *name, int len, int warn_on_lstat_err)
{
- return threaded_check_leading_path(&default_cache, name, len);
+ return threaded_check_leading_path(&default_cache, name, len,
+ warn_on_lstat_err);
}
/*
- * Return zero if path 'name' has a leading symlink component or
- * if some leading path component does not exists.
+ * Return zero if some leading path component of 'name' does not exist.
*
* Return -1 if leading path exists and is a directory.
*
- * Return path length if leading path exists and is neither a
- * directory nor a symlink.
+ * Return the length of a leading component if it either exists but it's not a
+ * directory, or if we were unable to lstat() it. If warn_on_lstat_err is true,
+ * also emit a warning for this error.
*/
-static int threaded_check_leading_path(struct cache_def *cache, const char *name, int len)
+static int threaded_check_leading_path(struct cache_def *cache, const char *name,
+ int len, int warn_on_lstat_err)
{
int flags;
int match_len = lstat_cache_matchlen(cache, name, len, &flags,
FL_SYMLINK|FL_NOENT|FL_DIR, USE_ONLY_LSTAT);
+ int saved_errno = errno;
+
if (flags & FL_NOENT)
return 0;
else if (flags & FL_DIR)
return -1;
- else
- return match_len;
+ if (warn_on_lstat_err && (flags & FL_LSTATERR)) {
+ char *path = xmemdupz(name, match_len);
+ errno = saved_errno;
+ warning_errno(_("failed to lstat '%s'"), path);
+ free(path);
+ }
+ return match_len;
}
-/*
- * Return non-zero if all path components of 'name' exists as a
- * directory. If prefix_len > 0, we will test with the stat()
- * function instead of the lstat() function for a prefix length of
- * 'prefix_len', thus we then allow for symlinks in the prefix part as
- * long as those points to real existing directories.
- */
int has_dirs_only_path(const char *name, int len, int prefix_len)
{
return threaded_has_dirs_only_path(&default_cache, name, len, prefix_len);
diff --git a/t/README b/t/README
index 593d4a4e27..1a2072b2c8 100644
--- a/t/README
+++ b/t/README
@@ -196,6 +196,11 @@ appropriately before running "make". Short options can be bundled, i.e.
this feature by setting the GIT_TEST_CHAIN_LINT environment
variable to "1" or "0", respectively.
+ A few test scripts disable some of the more advanced
+ chain-linting detection in the name of efficiency. You can
+ override this by setting the GIT_TEST_CHAIN_LINT_HARDER
+ environment variable to "1".
+
--stress::
Run the test script repeatedly in multiple parallel jobs until
one of them fails. Useful for reproducing rare failures in
@@ -387,9 +392,6 @@ GIT_TEST_COMMIT_GRAPH=<boolean>, when true, forces the commit-graph to
be written after every 'git commit' command, and overrides the
'core.commitGraph' setting to true.
-GIT_TEST_COMMIT_GRAPH_NO_GDAT=<boolean>, when true, forces the
-commit-graph to be written without generation data chunk.
-
GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=<boolean>, when true, forces
commit-graph write to compute and write changed path Bloom filters for
every 'git commit-graph write', as if the `--changed-paths` option was
@@ -439,6 +441,13 @@ and "sha256".
GIT_TEST_WRITE_REV_INDEX=<boolean>, when true enables the
'pack.writeReverseIndex' setting.
+GIT_TEST_SPARSE_INDEX=<boolean>, when true enables index writes to use the
+sparse-index format by default.
+
+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.
+
Naming Tests
------------
diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh
index 29ce89090d..d3b299e75c 100644
--- a/t/annotate-tests.sh
+++ b/t/annotate-tests.sh
@@ -479,22 +479,26 @@ test_expect_success 'blame -L ^:RE (absolute: end-of-file)' '
check_count -f hello.c -L$n -L^:ma.. F 4 G 1 H 1
'
-test_expect_success 'setup -L :funcname with userdiff driver' '
- echo "fortran-* diff=fortran" >.gitattributes &&
- fortran_file=fortran-external-function &&
- orig_file="$TEST_DIRECTORY/t4018/$fortran_file" &&
- cp "$orig_file" . &&
- git add "$fortran_file" &&
- GIT_AUTHOR_NAME="A" GIT_AUTHOR_EMAIL="A@test.git" \
- git commit -m "add fortran file" &&
- sed -e "s/ChangeMe/IWasChanged/" <"$orig_file" >"$fortran_file" &&
- git add "$fortran_file" &&
- GIT_AUTHOR_NAME="B" GIT_AUTHOR_EMAIL="B@test.git" \
- git commit -m "change fortran file"
-'
-
test_expect_success 'blame -L :funcname with userdiff driver' '
- check_count -f fortran-external-function -L:RIGHT A 7 B 1
+ cat >file.template <<-\EOF &&
+ DO NOT MATCH THIS LINE
+ function RIGHT(a, b) result(c)
+ AS THE DEFAULT DRIVER WOULD
+
+ integer, intent(in) :: ChangeMe
+ EOF
+
+ fortran_file=file.f03 &&
+ test_when_finished "rm .gitattributes" &&
+ echo "$fortran_file diff=fortran" >.gitattributes &&
+
+ test_commit --author "A <A@test.git>" \
+ "add" "$fortran_file" \
+ "$(cat file.template)" &&
+ test_commit --author "B <B@test.git>" \
+ "change" "$fortran_file" \
+ "$(cat file.template | sed -e s/ChangeMe/IWasChanged/)" &&
+ check_count -f "$fortran_file" -L:RIGHT A 3 B 1
'
test_expect_success 'setup incremental' '
diff --git a/t/helper/test-bitmap.c b/t/helper/test-bitmap.c
new file mode 100644
index 0000000000..134a1e9d76
--- /dev/null
+++ b/t/helper/test-bitmap.c
@@ -0,0 +1,24 @@
+#include "test-tool.h"
+#include "cache.h"
+#include "pack-bitmap.h"
+
+static int bitmap_list_commits(void)
+{
+ return test_bitmap_commits(the_repository);
+}
+
+int cmd__bitmap(int argc, const char **argv)
+{
+ setup_git_directory();
+
+ if (argc != 2)
+ goto usage;
+
+ if (!strcmp(argv[1], "list-commits"))
+ return bitmap_list_commits();
+
+usage:
+ usage("\ttest-tool bitmap list-commits");
+
+ return -1;
+}
diff --git a/t/helper/test-bloom.c b/t/helper/test-bloom.c
index 46e97b04eb..ad3ef1cd77 100644
--- a/t/helper/test-bloom.c
+++ b/t/helper/test-bloom.c
@@ -48,7 +48,7 @@ static void get_bloom_filter_for_commit(const struct object_id *commit_oid)
static const char *bloom_usage = "\n"
" test-tool bloom get_murmur3 <string>\n"
" test-tool bloom generate_filter <string> [<string>...]\n"
-" test-tool get_filter_for_commit <commit-hex>\n";
+" test-tool bloom get_filter_for_commit <commit-hex>\n";
int cmd__bloom(int argc, const char **argv)
{
@@ -69,7 +69,7 @@ int cmd__bloom(int argc, const char **argv)
struct bloom_filter filter;
int i = 2;
filter.len = (settings.bits_per_entry + BITS_PER_WORD - 1) / BITS_PER_WORD;
- filter.data = xcalloc(filter.len, sizeof(unsigned char));
+ CALLOC_ARRAY(filter.data, filter.len);
if (argc - 1 < i)
usage(bloom_usage);
diff --git a/t/helper/test-chmtime.c b/t/helper/test-chmtime.c
index aa22af48c2..524b55ca49 100644
--- a/t/helper/test-chmtime.c
+++ b/t/helper/test-chmtime.c
@@ -109,9 +109,9 @@ int cmd__chmtime(int argc, const char **argv)
uintmax_t mtime;
if (stat(argv[i], &sb) < 0) {
- fprintf(stderr, "Failed to stat %s: %s\n",
+ fprintf(stderr, "Failed to stat %s: %s. Skipping\n",
argv[i], strerror(errno));
- return 1;
+ continue;
}
#ifdef GIT_WINDOWS_NATIVE
diff --git a/t/helper/test-example-decorate.c b/t/helper/test-example-decorate.c
index c8a1cde7d2..b9d1200eb9 100644
--- a/t/helper/test-example-decorate.c
+++ b/t/helper/test-example-decorate.c
@@ -26,8 +26,8 @@ int cmd__example_decorate(int argc, const char **argv)
* Add 2 objects, one with a non-NULL decoration and one with a NULL
* decoration.
*/
- one = lookup_unknown_object(&one_oid);
- two = lookup_unknown_object(&two_oid);
+ one = lookup_unknown_object(the_repository, &one_oid);
+ two = lookup_unknown_object(the_repository, &two_oid);
ret = add_decoration(&n, one, &decoration_a);
if (ret)
BUG("when adding a brand-new object, NULL should be returned");
@@ -56,7 +56,7 @@ int cmd__example_decorate(int argc, const char **argv)
ret = lookup_decoration(&n, two);
if (ret != &decoration_b)
BUG("lookup should return added declaration");
- three = lookup_unknown_object(&three_oid);
+ three = lookup_unknown_object(the_repository, &three_oid);
ret = lookup_decoration(&n, three);
if (ret)
BUG("lookup for unknown object should return NULL");
diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c
index 313a153209..229ed416b0 100644
--- a/t/helper/test-path-utils.c
+++ b/t/helper/test-path-utils.c
@@ -172,9 +172,22 @@ static struct test_data dirname_data[] = {
{ NULL, NULL }
};
-static int is_dotgitmodules(const char *path)
+static int check_dotfile(const char *x, const char **argv,
+ int (*is_hfs)(const char *),
+ int (*is_ntfs)(const char *))
{
- return is_hfs_dotgitmodules(path) || is_ntfs_dotgitmodules(path);
+ int res = 0, expect = 1;
+ for (; *argv; argv++) {
+ if (!strcmp("--not", *argv))
+ expect = !expect;
+ else if (expect != (is_hfs(*argv) || is_ntfs(*argv)))
+ res = error("'%s' is %s.git%s", *argv,
+ expect ? "not " : "", x);
+ else
+ fprintf(stderr, "ok: '%s' is %s.git%s\n",
+ *argv, expect ? "" : "not ", x);
+ }
+ return !!res;
}
static int cmp_by_st_size(const void *a, const void *b)
@@ -382,17 +395,24 @@ int cmd__path_utils(int argc, const char **argv)
return test_function(dirname_data, posix_dirname, argv[1]);
if (argc > 2 && !strcmp(argv[1], "is_dotgitmodules")) {
- int res = 0, expect = 1, i;
- for (i = 2; i < argc; i++)
- if (!strcmp("--not", argv[i]))
- expect = !expect;
- else if (expect != is_dotgitmodules(argv[i]))
- res = error("'%s' is %s.gitmodules", argv[i],
- expect ? "not " : "");
- else
- fprintf(stderr, "ok: '%s' is %s.gitmodules\n",
- argv[i], expect ? "" : "not ");
- return !!res;
+ return check_dotfile("modules", argv + 2,
+ is_hfs_dotgitmodules,
+ is_ntfs_dotgitmodules);
+ }
+ if (argc > 2 && !strcmp(argv[1], "is_dotgitignore")) {
+ return check_dotfile("ignore", argv + 2,
+ is_hfs_dotgitignore,
+ is_ntfs_dotgitignore);
+ }
+ if (argc > 2 && !strcmp(argv[1], "is_dotgitattributes")) {
+ return check_dotfile("attributes", argv + 2,
+ is_hfs_dotgitattributes,
+ is_ntfs_dotgitattributes);
+ }
+ if (argc > 2 && !strcmp(argv[1], "is_dotmailmap")) {
+ return check_dotfile("mailmap", argv + 2,
+ is_hfs_dotmailmap,
+ is_ntfs_dotmailmap);
}
if (argc > 2 && !strcmp(argv[1], "file-size")) {
diff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c
index 244977a29b..b52c174acc 100644
--- a/t/helper/test-read-cache.c
+++ b/t/helper/test-read-cache.c
@@ -1,36 +1,82 @@
#include "test-tool.h"
#include "cache.h"
#include "config.h"
+#include "blob.h"
+#include "commit.h"
+#include "tree.h"
+#include "sparse-index.h"
+
+static void print_cache_entry(struct cache_entry *ce)
+{
+ const char *type;
+ printf("%06o ", ce->ce_mode & 0177777);
+
+ if (S_ISSPARSEDIR(ce->ce_mode))
+ type = tree_type;
+ else if (S_ISGITLINK(ce->ce_mode))
+ type = commit_type;
+ else
+ type = blob_type;
+
+ printf("%s %s\t%s\n",
+ type,
+ oid_to_hex(&ce->oid),
+ ce->name);
+}
+
+static void print_cache(struct index_state *istate)
+{
+ int i;
+ for (i = 0; i < istate->cache_nr; i++)
+ print_cache_entry(istate->cache[i]);
+}
int cmd__read_cache(int argc, const char **argv)
{
+ struct repository *r = the_repository;
int i, cnt = 1;
const char *name = NULL;
+ int table = 0, expand = 0;
+
+ initialize_the_repository();
+ prepare_repo_settings(r);
+ r->settings.command_requires_full_index = 0;
- if (argc > 1 && skip_prefix(argv[1], "--print-and-refresh=", &name)) {
- argc--;
- argv++;
+ for (++argv, --argc; *argv && starts_with(*argv, "--"); ++argv, --argc) {
+ if (skip_prefix(*argv, "--print-and-refresh=", &name))
+ continue;
+ if (!strcmp(*argv, "--table"))
+ table = 1;
+ else if (!strcmp(*argv, "--expand"))
+ expand = 1;
}
- if (argc == 2)
- cnt = strtol(argv[1], NULL, 0);
+ if (argc == 1)
+ cnt = strtol(argv[0], NULL, 0);
setup_git_directory();
git_config(git_default_config, NULL);
+
for (i = 0; i < cnt; i++) {
- read_cache();
+ repo_read_index(r);
+
+ if (expand)
+ ensure_full_index(r->index);
+
if (name) {
int pos;
- refresh_index(&the_index, REFRESH_QUIET,
+ refresh_index(r->index, REFRESH_QUIET,
NULL, NULL, NULL);
- pos = index_name_pos(&the_index, name, strlen(name));
+ pos = index_name_pos(r->index, name, strlen(name));
if (pos < 0)
die("%s not in index", name);
printf("%s is%s up to date\n", name,
- ce_uptodate(the_index.cache[pos]) ? "" : " not");
+ ce_uptodate(r->index->cache[pos]) ? "" : " not");
write_file(name, "%d\n", i);
}
- discard_cache();
+ if (table)
+ print_cache(r->index);
+ discard_index(r->index);
}
return 0;
}
diff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c
index 2430880f78..7c2eb11a8e 100644
--- a/t/helper/test-read-midx.c
+++ b/t/helper/test-read-midx.c
@@ -4,7 +4,7 @@
#include "repository.h"
#include "object-store.h"
-static int read_midx_file(const char *object_dir)
+static int read_midx_file(const char *object_dir, int show_objects)
{
uint32_t i;
struct multi_pack_index *m;
@@ -43,13 +43,29 @@ static int read_midx_file(const char *object_dir)
printf("object-dir: %s\n", m->object_dir);
+ if (show_objects) {
+ struct object_id oid;
+ struct pack_entry e;
+
+ for (i = 0; i < m->num_objects; i++) {
+ nth_midxed_object_oid(&oid, m, i);
+ fill_midx_entry(the_repository, &oid, &e, m);
+
+ printf("%s %"PRIu64"\t%s\n",
+ oid_to_hex(&oid), e.offset, e.p->pack_name);
+ }
+ return 0;
+ }
+
return 0;
}
int cmd__read_midx(int argc, const char **argv)
{
- if (argc != 2)
- usage("read-midx <object-dir>");
+ if (!(argc == 2 || argc == 3))
+ usage("read-midx [--show-objects] <object-dir>");
- return read_midx_file(argv[1]);
+ if (!strcmp(argv[1], "--show-objects"))
+ return read_midx_file(argv[2], 1);
+ return read_midx_file(argv[1], 0);
}
diff --git a/t/helper/test-simple-ipc.c b/t/helper/test-simple-ipc.c
new file mode 100644
index 0000000000..42040ef81b
--- /dev/null
+++ b/t/helper/test-simple-ipc.c
@@ -0,0 +1,787 @@
+/*
+ * test-simple-ipc.c: verify that the Inter-Process Communication works.
+ */
+
+#include "test-tool.h"
+#include "cache.h"
+#include "strbuf.h"
+#include "simple-ipc.h"
+#include "parse-options.h"
+#include "thread-utils.h"
+#include "strvec.h"
+
+#ifndef SUPPORTS_SIMPLE_IPC
+int cmd__simple_ipc(int argc, const char **argv)
+{
+ die("simple IPC not available on this platform");
+}
+#else
+
+/*
+ * The test daemon defines an "application callback" that supports a
+ * series of commands (see `test_app_cb()`).
+ *
+ * Unknown commands are caught here and we send an error message back
+ * to the client process.
+ */
+static int app__unhandled_command(const char *command,
+ ipc_server_reply_cb *reply_cb,
+ struct ipc_server_reply_data *reply_data)
+{
+ struct strbuf buf = STRBUF_INIT;
+ int ret;
+
+ strbuf_addf(&buf, "unhandled command: %s", command);
+ ret = reply_cb(reply_data, buf.buf, buf.len);
+ strbuf_release(&buf);
+
+ return ret;
+}
+
+/*
+ * Reply with a single very large buffer. This is to ensure that
+ * long response are properly handled -- whether the chunking occurs
+ * in the kernel or in the (probably pkt-line) layer.
+ */
+#define BIG_ROWS (10000)
+static int app__big_command(ipc_server_reply_cb *reply_cb,
+ struct ipc_server_reply_data *reply_data)
+{
+ struct strbuf buf = STRBUF_INIT;
+ int row;
+ int ret;
+
+ for (row = 0; row < BIG_ROWS; row++)
+ strbuf_addf(&buf, "big: %.75d\n", row);
+
+ ret = reply_cb(reply_data, buf.buf, buf.len);
+ strbuf_release(&buf);
+
+ return ret;
+}
+
+/*
+ * Reply with a series of lines. This is to ensure that we can incrementally
+ * compute the response and chunk it to the client.
+ */
+#define CHUNK_ROWS (10000)
+static int app__chunk_command(ipc_server_reply_cb *reply_cb,
+ struct ipc_server_reply_data *reply_data)
+{
+ struct strbuf buf = STRBUF_INIT;
+ int row;
+ int ret;
+
+ for (row = 0; row < CHUNK_ROWS; row++) {
+ strbuf_setlen(&buf, 0);
+ strbuf_addf(&buf, "big: %.75d\n", row);
+ ret = reply_cb(reply_data, buf.buf, buf.len);
+ }
+
+ strbuf_release(&buf);
+
+ return ret;
+}
+
+/*
+ * Slowly reply with a series of lines. This is to model an expensive to
+ * compute chunked response (which might happen if this callback is running
+ * in a thread and is fighting for a lock with other threads).
+ */
+#define SLOW_ROWS (1000)
+#define SLOW_DELAY_MS (10)
+static int app__slow_command(ipc_server_reply_cb *reply_cb,
+ struct ipc_server_reply_data *reply_data)
+{
+ struct strbuf buf = STRBUF_INIT;
+ int row;
+ int ret;
+
+ for (row = 0; row < SLOW_ROWS; row++) {
+ strbuf_setlen(&buf, 0);
+ strbuf_addf(&buf, "big: %.75d\n", row);
+ ret = reply_cb(reply_data, buf.buf, buf.len);
+ sleep_millisec(SLOW_DELAY_MS);
+ }
+
+ strbuf_release(&buf);
+
+ return ret;
+}
+
+/*
+ * The client sent a command followed by a (possibly very) large buffer.
+ */
+static int app__sendbytes_command(const char *received,
+ ipc_server_reply_cb *reply_cb,
+ struct ipc_server_reply_data *reply_data)
+{
+ struct strbuf buf_resp = STRBUF_INIT;
+ const char *p = "?";
+ int len_ballast = 0;
+ int k;
+ int errs = 0;
+ int ret;
+
+ if (skip_prefix(received, "sendbytes ", &p))
+ len_ballast = strlen(p);
+
+ /*
+ * Verify that the ballast is n copies of a single letter.
+ * And that the multi-threaded IO layer didn't cross the streams.
+ */
+ for (k = 1; k < len_ballast; k++)
+ if (p[k] != p[0])
+ errs++;
+
+ if (errs)
+ strbuf_addf(&buf_resp, "errs:%d\n", errs);
+ else
+ strbuf_addf(&buf_resp, "rcvd:%c%08d\n", p[0], len_ballast);
+
+ ret = reply_cb(reply_data, buf_resp.buf, buf_resp.len);
+
+ strbuf_release(&buf_resp);
+
+ return ret;
+}
+
+/*
+ * An arbitrary fixed address to verify that the application instance
+ * data is handled properly.
+ */
+static int my_app_data = 42;
+
+static ipc_server_application_cb test_app_cb;
+
+/*
+ * This is the "application callback" that sits on top of the
+ * "ipc-server". It completely defines the set of commands supported
+ * by this application.
+ */
+static int test_app_cb(void *application_data,
+ const char *command,
+ ipc_server_reply_cb *reply_cb,
+ struct ipc_server_reply_data *reply_data)
+{
+ /*
+ * Verify that we received the application-data that we passed
+ * when we started the ipc-server. (We have several layers of
+ * callbacks calling callbacks and it's easy to get things mixed
+ * up (especially when some are "void*").)
+ */
+ if (application_data != (void*)&my_app_data)
+ BUG("application_cb: application_data pointer wrong");
+
+ if (!strcmp(command, "quit")) {
+ /*
+ * The client sent a "quit" command. This is an async
+ * request for the server to shutdown.
+ *
+ * We DO NOT send the client a response message
+ * (because we have nothing to say and the other
+ * server threads have not yet stopped).
+ *
+ * Tell the ipc-server layer to start shutting down.
+ * This includes: stop listening for new connections
+ * on the socket/pipe and telling all worker threads
+ * to finish/drain their outgoing responses to other
+ * clients.
+ *
+ * This DOES NOT force an immediate sync shutdown.
+ */
+ return SIMPLE_IPC_QUIT;
+ }
+
+ if (!strcmp(command, "ping")) {
+ const char *answer = "pong";
+ return reply_cb(reply_data, answer, strlen(answer));
+ }
+
+ if (!strcmp(command, "big"))
+ return app__big_command(reply_cb, reply_data);
+
+ if (!strcmp(command, "chunk"))
+ return app__chunk_command(reply_cb, reply_data);
+
+ if (!strcmp(command, "slow"))
+ return app__slow_command(reply_cb, reply_data);
+
+ if (starts_with(command, "sendbytes "))
+ return app__sendbytes_command(command, reply_cb, reply_data);
+
+ return app__unhandled_command(command, reply_cb, reply_data);
+}
+
+struct cl_args
+{
+ const char *subcommand;
+ const char *path;
+ const char *token;
+
+ int nr_threads;
+ int max_wait_sec;
+ int bytecount;
+ int batchsize;
+
+ char bytevalue;
+};
+
+static struct cl_args cl_args = {
+ .subcommand = NULL,
+ .path = "ipc-test",
+ .token = NULL,
+
+ .nr_threads = 5,
+ .max_wait_sec = 60,
+ .bytecount = 1024,
+ .batchsize = 10,
+
+ .bytevalue = 'x',
+};
+
+/*
+ * This process will run as a simple-ipc server and listen for IPC commands
+ * from client processes.
+ */
+static int daemon__run_server(void)
+{
+ int ret;
+
+ struct ipc_server_opts opts = {
+ .nr_threads = cl_args.nr_threads,
+ };
+
+ /*
+ * Synchronously run the ipc-server. We don't need any application
+ * instance data, so pass an arbitrary pointer (that we'll later
+ * verify made the round trip).
+ */
+ 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);
+ else if (ret == -1)
+ 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();
+
+ switch (*pid) {
+ case 0:
+ if (setsid() == -1)
+ error_errno(_("setsid failed"));
+ close(0);
+ close(1);
+ close(2);
+ sanitize_stdfds();
+
+ return ipc_server_run(cl_args.path, &opts, test_app_cb,
+ (void*)&my_app_data);
+
+ case -1:
+ return error_errno(_("could not spawn daemon in the background"));
+
+ default:
+ return 0;
+ }
+}
+#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)
+{
+ int status;
+ pid_t pid_seen;
+ enum ipc_active_state s;
+ time_t time_limit, now;
+
+ time(&time_limit);
+ time_limit += cl_args.max_wait_sec;
+
+ 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;
+
+ time(&now);
+ if (now > time_limit)
+ return error(_("daemon not online yet"));
+
+ continue;
+ }
+
+ 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;
+
+ /*
+ * 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"));
+ }
+
+ else
+ return error(_("waitpid is confused"));
+ }
+}
+
+/*
+ * 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.
+ *
+ * Returns 0 if the server is alive.
+ */
+static int client__probe_server(void)
+{
+ enum ipc_active_state s;
+
+ s = ipc_get_active_state(cl_args.path);
+ switch (s) {
+ case IPC_STATE__LISTENING:
+ return 0;
+
+ case IPC_STATE__NOT_LISTENING:
+ return error("no server listening at '%s'", cl_args.path);
+
+ case IPC_STATE__PATH_NOT_FOUND:
+ return error("path not found '%s'", cl_args.path);
+
+ case IPC_STATE__INVALID_PATH:
+ return error("invalid pipe/socket name '%s'", cl_args.path);
+
+ case IPC_STATE__OTHER_ERROR:
+ default:
+ return error("other error for '%s'", cl_args.path);
+ }
+}
+
+/*
+ * Send an IPC command token to an already-running server daemon and
+ * print the response.
+ *
+ * This is a simple 1 word command/token that `test_app_cb()` (in the
+ * daemon process) will understand.
+ */
+static int client__send_ipc(void)
+{
+ const char *command = "(no-command)";
+ struct strbuf buf = STRBUF_INIT;
+ struct ipc_client_connect_options options
+ = IPC_CLIENT_CONNECT_OPTIONS_INIT;
+
+ if (cl_args.token && *cl_args.token)
+ command = cl_args.token;
+
+ options.wait_if_busy = 1;
+ options.wait_if_not_found = 0;
+
+ if (!ipc_client_send_command(cl_args.path, &options, command, &buf)) {
+ if (buf.len) {
+ printf("%s\n", buf.buf);
+ fflush(stdout);
+ }
+ strbuf_release(&buf);
+
+ return 0;
+ }
+
+ return error("failed to send '%s' to '%s'", command, cl_args.path);
+}
+
+/*
+ * Send an IPC command to an already-running server and ask it to
+ * shutdown. "send quit" is an async request and queues a shutdown
+ * event in the server, so we spin and wait here for it to actually
+ * shutdown to make the unit tests a little easier to write.
+ */
+static int client__stop_server(void)
+{
+ int ret;
+ time_t time_limit, now;
+ enum ipc_active_state s;
+
+ time(&time_limit);
+ time_limit += cl_args.max_wait_sec;
+
+ cl_args.token = "quit";
+
+ ret = client__send_ipc();
+ if (ret)
+ return ret;
+
+ for (;;) {
+ sleep_millisec(100);
+
+ s = ipc_get_active_state(cl_args.path);
+
+ if (s != IPC_STATE__LISTENING) {
+ /*
+ * The socket/pipe is gone and/or has stopped
+ * responding. Lets assume that the daemon
+ * process has exited too.
+ */
+ return 0;
+ }
+
+ time(&now);
+ if (now > time_limit)
+ return error(_("daemon has not shutdown yet"));
+ }
+}
+
+/*
+ * Send an IPC command followed by ballast to confirm that a large
+ * message can be sent and that the kernel or pkt-line layers will
+ * properly chunk it and that the daemon receives the entire message.
+ */
+static int do_sendbytes(int bytecount, char byte, const char *path,
+ const struct ipc_client_connect_options *options)
+{
+ struct strbuf buf_send = STRBUF_INIT;
+ struct strbuf buf_resp = STRBUF_INIT;
+
+ strbuf_addstr(&buf_send, "sendbytes ");
+ strbuf_addchars(&buf_send, byte, bytecount);
+
+ if (!ipc_client_send_command(path, options, buf_send.buf, &buf_resp)) {
+ strbuf_rtrim(&buf_resp);
+ printf("sent:%c%08d %s\n", byte, bytecount, buf_resp.buf);
+ fflush(stdout);
+ strbuf_release(&buf_send);
+ strbuf_release(&buf_resp);
+
+ return 0;
+ }
+
+ return error("client failed to sendbytes(%d, '%c') to '%s'",
+ bytecount, byte, path);
+}
+
+/*
+ * Send an IPC command with ballast to an already-running server daemon.
+ */
+static int client__sendbytes(void)
+{
+ struct ipc_client_connect_options options
+ = IPC_CLIENT_CONNECT_OPTIONS_INIT;
+
+ options.wait_if_busy = 1;
+ options.wait_if_not_found = 0;
+ options.uds_disallow_chdir = 0;
+
+ return do_sendbytes(cl_args.bytecount, cl_args.bytevalue, cl_args.path,
+ &options);
+}
+
+struct multiple_thread_data {
+ pthread_t pthread_id;
+ struct multiple_thread_data *next;
+ const char *path;
+ int bytecount;
+ int batchsize;
+ int sum_errors;
+ int sum_good;
+ char letter;
+};
+
+static void *multiple_thread_proc(void *_multiple_thread_data)
+{
+ struct multiple_thread_data *d = _multiple_thread_data;
+ int k;
+ struct ipc_client_connect_options options
+ = IPC_CLIENT_CONNECT_OPTIONS_INIT;
+
+ options.wait_if_busy = 1;
+ options.wait_if_not_found = 0;
+ /*
+ * A multi-threaded client should not be randomly calling chdir().
+ * The test will pass without this restriction because the test is
+ * not otherwise accessing the filesystem, but it makes us honest.
+ */
+ options.uds_disallow_chdir = 1;
+
+ trace2_thread_start("multiple");
+
+ for (k = 0; k < d->batchsize; k++) {
+ if (do_sendbytes(d->bytecount + k, d->letter, d->path, &options))
+ d->sum_errors++;
+ else
+ d->sum_good++;
+ }
+
+ trace2_thread_exit();
+ return NULL;
+}
+
+/*
+ * Start a client-side thread pool. Each thread sends a series of
+ * IPC requests. Each request is on a new connection to the server.
+ */
+static int client__multiple(void)
+{
+ struct multiple_thread_data *list = NULL;
+ int k;
+ int sum_join_errors = 0;
+ int sum_thread_errors = 0;
+ int sum_good = 0;
+
+ for (k = 0; k < cl_args.nr_threads; k++) {
+ struct multiple_thread_data *d = xcalloc(1, sizeof(*d));
+ d->next = list;
+ d->path = cl_args.path;
+ d->bytecount = cl_args.bytecount + cl_args.batchsize*(k/26);
+ d->batchsize = cl_args.batchsize;
+ d->sum_errors = 0;
+ d->sum_good = 0;
+ d->letter = 'A' + (k % 26);
+
+ if (pthread_create(&d->pthread_id, NULL, multiple_thread_proc, d)) {
+ warning("failed to create thread[%d] skipping remainder", k);
+ free(d);
+ break;
+ }
+
+ list = d;
+ }
+
+ while (list) {
+ struct multiple_thread_data *d = list;
+
+ if (pthread_join(d->pthread_id, NULL))
+ sum_join_errors++;
+
+ sum_thread_errors += d->sum_errors;
+ sum_good += d->sum_good;
+
+ list = d->next;
+ free(d);
+ }
+
+ printf("client (good %d) (join %d), (errors %d)\n",
+ sum_good, sum_join_errors, sum_thread_errors);
+
+ return (sum_join_errors + sum_thread_errors) ? 1 : 0;
+}
+
+int cmd__simple_ipc(int argc, const char **argv)
+{
+ const char * const simple_ipc_usage[] = {
+ N_("test-helper simple-ipc is-active [<name>] [<options>]"),
+ N_("test-helper simple-ipc run-daemon [<name>] [<threads>]"),
+ N_("test-helper simple-ipc start-daemon [<name>] [<threads>] [<max-wait>]"),
+ N_("test-helper simple-ipc stop-daemon [<name>] [<max-wait>]"),
+ N_("test-helper simple-ipc send [<name>] [<token>]"),
+ N_("test-helper simple-ipc sendbytes [<name>] [<bytecount>] [<byte>]"),
+ N_("test-helper simple-ipc multiple [<name>] [<threads>] [<bytecount>] [<batchsize>]"),
+ NULL
+ };
+
+ const char *bytevalue = NULL;
+
+ struct option options[] = {
+#ifndef GIT_WINDOWS_NATIVE
+ OPT_STRING(0, "name", &cl_args.path, N_("name"), N_("name or pathname of unix domain socket")),
+#else
+ OPT_STRING(0, "name", &cl_args.path, N_("name"), N_("named-pipe name")),
+#endif
+ OPT_INTEGER(0, "threads", &cl_args.nr_threads, N_("number of threads in server thread pool")),
+ OPT_INTEGER(0, "max-wait", &cl_args.max_wait_sec, N_("seconds to wait for daemon to start or stop")),
+
+ OPT_INTEGER(0, "bytecount", &cl_args.bytecount, N_("number of bytes")),
+ OPT_INTEGER(0, "batchsize", &cl_args.batchsize, N_("number of requests per thread")),
+
+ OPT_STRING(0, "byte", &bytevalue, N_("byte"), N_("ballast character")),
+ OPT_STRING(0, "token", &cl_args.token, N_("token"), N_("command token to send to the server")),
+
+ OPT_END()
+ };
+
+ if (argc < 2)
+ usage_with_options(simple_ipc_usage, options);
+
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(simple_ipc_usage, options);
+
+ if (argc == 2 && !strcmp(argv[1], "SUPPORTS_SIMPLE_IPC"))
+ return 0;
+
+ cl_args.subcommand = argv[1];
+
+ argc--;
+ argv++;
+
+ argc = parse_options(argc, argv, NULL, options, simple_ipc_usage, 0);
+
+ if (cl_args.nr_threads < 1)
+ cl_args.nr_threads = 1;
+ if (cl_args.max_wait_sec < 0)
+ cl_args.max_wait_sec = 0;
+ if (cl_args.bytecount < 1)
+ cl_args.bytecount = 1;
+ if (cl_args.batchsize < 1)
+ cl_args.batchsize = 1;
+
+ if (bytevalue && *bytevalue)
+ cl_args.bytevalue = bytevalue[0];
+
+ /*
+ * Use '!!' on all dispatch functions to map from `error()` style
+ * (returns -1) style to `test_must_fail` style (expects 1). This
+ * makes shell error messages less confusing.
+ */
+
+ if (!strcmp(cl_args.subcommand, "is-active"))
+ return !!client__probe_server();
+
+ if (!strcmp(cl_args.subcommand, "run-daemon"))
+ return !!daemon__run_server();
+
+ if (!strcmp(cl_args.subcommand, "start-daemon"))
+ return !!daemon__start_server();
+
+ /*
+ * Client commands follow. Ensure a server is running before
+ * sending any data. This might be overkill, but then again
+ * this is a test harness.
+ */
+
+ if (!strcmp(cl_args.subcommand, "stop-daemon")) {
+ if (client__probe_server())
+ return 1;
+ return !!client__stop_server();
+ }
+
+ if (!strcmp(cl_args.subcommand, "send")) {
+ if (client__probe_server())
+ return 1;
+ return !!client__send_ipc();
+ }
+
+ if (!strcmp(cl_args.subcommand, "sendbytes")) {
+ if (client__probe_server())
+ return 1;
+ return !!client__sendbytes();
+ }
+
+ if (!strcmp(cl_args.subcommand, "multiple")) {
+ if (client__probe_server())
+ return 1;
+ return !!client__multiple();
+ }
+
+ die("Unhandled subcommand: '%s'", cl_args.subcommand);
+}
+#endif
diff --git a/t/helper/test-submodule-nested-repo-config.c b/t/helper/test-submodule-nested-repo-config.c
index c5fd4527dc..e3f11ff5a7 100644
--- a/t/helper/test-submodule-nested-repo-config.c
+++ b/t/helper/test-submodule-nested-repo-config.c
@@ -18,7 +18,7 @@ int cmd__submodule_nested_repo_config(int argc, const char **argv)
setup_git_directory();
- sub = submodule_from_path(the_repository, &null_oid, argv[1]);
+ sub = submodule_from_path(the_repository, null_oid(), argv[1]);
if (repo_submodule_init(&subrepo, the_repository, sub)) {
die_usage(argv, "Submodule not found.");
}
diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c
index f97cd9f48a..c5bd0c6d4c 100644
--- a/t/helper/test-tool.c
+++ b/t/helper/test-tool.c
@@ -15,6 +15,7 @@ struct test_cmd {
static struct test_cmd cmds[] = {
{ "advise", cmd__advise_if_enabled },
+ { "bitmap", cmd__bitmap },
{ "bloom", cmd__bloom },
{ "chmtime", cmd__chmtime },
{ "config", cmd__config },
@@ -65,12 +66,14 @@ static struct test_cmd cmds[] = {
{ "sha1", cmd__sha1 },
{ "sha256", cmd__sha256 },
{ "sigchain", cmd__sigchain },
+ { "simple-ipc", cmd__simple_ipc },
{ "strcmp-offset", cmd__strcmp_offset },
{ "string-list", cmd__string_list },
{ "submodule-config", cmd__submodule_config },
{ "submodule-nested-repo-config", cmd__submodule_nested_repo_config },
{ "subprocess", cmd__subprocess },
{ "trace2", cmd__trace2 },
+ { "userdiff", cmd__userdiff },
{ "urlmatch-normalization", cmd__urlmatch_normalization },
{ "xml-encode", cmd__xml_encode },
{ "wildmatch", cmd__wildmatch },
diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h
index 28072c0ad5..e8069a3b22 100644
--- a/t/helper/test-tool.h
+++ b/t/helper/test-tool.h
@@ -5,6 +5,7 @@
#include "git-compat-util.h"
int cmd__advise_if_enabled(int argc, const char **argv);
+int cmd__bitmap(int argc, const char **argv);
int cmd__bloom(int argc, const char **argv);
int cmd__chmtime(int argc, const char **argv);
int cmd__config(int argc, const char **argv);
@@ -55,12 +56,14 @@ int cmd__sha1(int argc, const char **argv);
int cmd__oid_array(int argc, const char **argv);
int cmd__sha256(int argc, const char **argv);
int cmd__sigchain(int argc, const char **argv);
+int cmd__simple_ipc(int argc, const char **argv);
int cmd__strcmp_offset(int argc, const char **argv);
int cmd__string_list(int argc, const char **argv);
int cmd__submodule_config(int argc, const char **argv);
int cmd__submodule_nested_repo_config(int argc, const char **argv);
int cmd__subprocess(int argc, const char **argv);
int cmd__trace2(int argc, const char **argv);
+int cmd__userdiff(int argc, const char **argv);
int cmd__urlmatch_normalization(int argc, const char **argv);
int cmd__xml_encode(int argc, const char **argv);
int cmd__wildmatch(int argc, const char **argv);
diff --git a/t/helper/test-userdiff.c b/t/helper/test-userdiff.c
new file mode 100644
index 0000000000..f013f8a31e
--- /dev/null
+++ b/t/helper/test-userdiff.c
@@ -0,0 +1,46 @@
+#include "test-tool.h"
+#include "cache.h"
+#include "userdiff.h"
+#include "config.h"
+
+static int driver_cb(struct userdiff_driver *driver,
+ enum userdiff_driver_type type, void *priv)
+{
+ enum userdiff_driver_type *want_type = priv;
+ if (type & *want_type && driver->funcname.pattern)
+ puts(driver->name);
+ return 0;
+}
+
+static int cmd__userdiff_config(const char *var, const char *value, void *cb)
+{
+ if (userdiff_config(var, value) < 0)
+ return -1;
+ return 0;
+}
+
+int cmd__userdiff(int argc, const char **argv)
+{
+ enum userdiff_driver_type want = 0;
+ if (argc != 2)
+ return 1;
+
+ if (!strcmp(argv[1], "list-drivers"))
+ want = (USERDIFF_DRIVER_TYPE_BUILTIN |
+ USERDIFF_DRIVER_TYPE_CUSTOM);
+ else if (!strcmp(argv[1], "list-builtin-drivers"))
+ want = USERDIFF_DRIVER_TYPE_BUILTIN;
+ else if (!strcmp(argv[1], "list-custom-drivers"))
+ want = USERDIFF_DRIVER_TYPE_CUSTOM;
+ else
+ return error("unknown argument %s", argv[1]);
+
+ if (want & USERDIFF_DRIVER_TYPE_CUSTOM) {
+ setup_git_directory();
+ git_config(cmd__userdiff_config, NULL);
+ }
+
+ for_each_userdiff_driver(driver_cb, &want);
+
+ return 0;
+}
diff --git a/t/lib-encoding.sh b/t/lib-encoding.sh
new file mode 100644
index 0000000000..2dabc8c73e
--- /dev/null
+++ b/t/lib-encoding.sh
@@ -0,0 +1,25 @@
+# Encoding helpers
+
+test_lazy_prereq NO_UTF16_BOM '
+ test $(printf abc | iconv -f UTF-8 -t UTF-16 | wc -c) = 6
+'
+
+test_lazy_prereq NO_UTF32_BOM '
+ test $(printf abc | iconv -f UTF-8 -t UTF-32 | wc -c) = 12
+'
+
+write_utf16 () {
+ if test_have_prereq NO_UTF16_BOM
+ then
+ printf '\376\377'
+ fi &&
+ iconv -f UTF-8 -t UTF-16
+}
+
+write_utf32 () {
+ if test_have_prereq NO_UTF32_BOM
+ then
+ printf '\0\0\376\377'
+ fi &&
+ iconv -f UTF-8 -t UTF-32
+}
diff --git a/t/lib-parallel-checkout.sh b/t/lib-parallel-checkout.sh
new file mode 100644
index 0000000000..21f5759732
--- /dev/null
+++ b/t/lib-parallel-checkout.sh
@@ -0,0 +1,45 @@
+# Helpers for tests invoking parallel-checkout
+
+# Parallel checkout tests need full control of the number of workers
+unset GIT_TEST_CHECKOUT_WORKERS
+
+set_checkout_config () {
+ if test $# -ne 2
+ then
+ BUG "usage: set_checkout_config <workers> <threshold>"
+ fi &&
+
+ test_config_global checkout.workers $1 &&
+ test_config_global checkout.thresholdForParallelism $2
+}
+
+# Run "${@:2}" and check that $1 checkout workers were used
+test_checkout_workers () {
+ if test $# -lt 2
+ then
+ BUG "too few arguments to test_checkout_workers"
+ fi &&
+
+ local expected_workers=$1 &&
+ shift &&
+
+ local trace_file=trace-test-checkout-workers &&
+ rm -f "$trace_file" &&
+ GIT_TRACE2="$(pwd)/$trace_file" "$@" 2>&8 &&
+
+ local workers=$(grep "child_start\[..*\] git checkout--worker" "$trace_file" | wc -l) &&
+ test $workers -eq $expected_workers &&
+ rm "$trace_file"
+} 8>&2 2>&4
+
+# Verify that both the working tree and the index were created correctly
+verify_checkout () {
+ if test $# -ne 1
+ then
+ BUG "usage: verify_checkout <repository path>"
+ fi &&
+
+ git -C "$1" diff-index --ignore-submodules=none --exit-code HEAD -- &&
+ git -C "$1" status --porcelain >"$1".status &&
+ test_must_be_empty "$1".status
+}
diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh
index 172d7459ff..dc75b83451 100644
--- a/t/lib-rebase.sh
+++ b/t/lib-rebase.sh
@@ -4,6 +4,7 @@
#
# - override the commit message with $FAKE_COMMIT_MESSAGE
# - amend the commit message with $FAKE_COMMIT_AMEND
+# - copy the original commit message to a file with $FAKE_MESSAGE_COPY
# - check that non-commit messages have a certain line count with $EXPECT_COUNT
# - check the commit count in the commit message header with $EXPECT_HEADER_COUNT
# - rewrite a rebase -i script as directed by $FAKE_LINES.
@@ -14,10 +15,11 @@
# specified line.
#
# "<cmd> <lineno>" -- add a line with the specified command
-# ("pick", "squash", "fixup", "edit", "reword" or "drop") and the
-# SHA1 taken from the specified line.
+# ("pick", "squash", "fixup"|"fixup_-C"|"fixup_-c", "edit", "reword" or "drop")
+# and the SHA1 taken from the specified line.
#
-# "exec_cmd_with_args" -- add an "exec cmd with args" line.
+# "_" -- add a space, like "fixup_-C" implies "fixup -C" and
+# "exec_cmd_with_args" add an "exec cmd with args" line.
#
# "#" -- Add a comment line.
#
@@ -32,6 +34,7 @@ set_fake_editor () {
exit
test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1"
test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1"
+ test -z "$FAKE_MESSAGE_COPY" || cat "$1" >"$FAKE_MESSAGE_COPY"
exit
;;
esac
@@ -50,6 +53,8 @@ set_fake_editor () {
action="$line";;
exec_*|x_*|break|b)
echo "$line" | sed 's/_/ /g' >> "$1";;
+ merge_*|fixup_*)
+ action=$(echo "$line" | sed 's/_/ /g');;
"#")
echo '# comment' >> "$1";;
">")
diff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh
new file mode 100755
index 0000000000..94513c9774
--- /dev/null
+++ b/t/perf/p2000-sparse-operations.sh
@@ -0,0 +1,101 @@
+#!/bin/sh
+
+test_description="test performance of Git operations using the index"
+
+. ./perf-lib.sh
+
+test_perf_default_repo
+
+SPARSE_CONE=f2/f4/f1
+
+test_expect_success 'setup repo and indexes' '
+ git reset --hard HEAD &&
+
+ # Remove submodules from the example repo, because our
+ # duplication of the entire repo creates an unlikely data shape.
+ if git config --file .gitmodules --get-regexp "submodule.*.path" >modules
+ then
+ git rm $(awk "{print \$2}" modules) &&
+ git commit -m "remove submodules" || return 1
+ fi &&
+
+ echo bogus >a &&
+ cp a b &&
+ git add a b &&
+ git commit -m "level 0" &&
+ BLOB=$(git rev-parse HEAD:a) &&
+ OLD_COMMIT=$(git rev-parse HEAD) &&
+ OLD_TREE=$(git rev-parse HEAD^{tree}) &&
+
+ for i in $(test_seq 1 4)
+ do
+ cat >in <<-EOF &&
+ 100755 blob $BLOB a
+ 040000 tree $OLD_TREE f1
+ 040000 tree $OLD_TREE f2
+ 040000 tree $OLD_TREE f3
+ 040000 tree $OLD_TREE f4
+ EOF
+ NEW_TREE=$(git mktree <in) &&
+ NEW_COMMIT=$(git commit-tree $NEW_TREE -p $OLD_COMMIT -m "level $i") &&
+ OLD_TREE=$NEW_TREE &&
+ OLD_COMMIT=$NEW_COMMIT || return 1
+ done &&
+
+ git sparse-checkout init --cone &&
+ git branch -f wide $OLD_COMMIT &&
+ git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-index-v3 &&
+ (
+ cd full-index-v3 &&
+ git sparse-checkout init --cone &&
+ git sparse-checkout set $SPARSE_CONE &&
+ git config index.version 3 &&
+ git update-index --index-version=3
+ ) &&
+ git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-index-v4 &&
+ (
+ cd full-index-v4 &&
+ git sparse-checkout init --cone &&
+ git sparse-checkout set $SPARSE_CONE &&
+ git config index.version 4 &&
+ git update-index --index-version=4
+ ) &&
+ git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-index-v3 &&
+ (
+ cd sparse-index-v3 &&
+ git sparse-checkout init --cone --sparse-index &&
+ git sparse-checkout set $SPARSE_CONE &&
+ git config index.version 3 &&
+ git update-index --index-version=3
+ ) &&
+ git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-index-v4 &&
+ (
+ cd sparse-index-v4 &&
+ git sparse-checkout init --cone --sparse-index &&
+ git sparse-checkout set $SPARSE_CONE &&
+ git config index.version 4 &&
+ git update-index --index-version=4
+ )
+'
+
+test_perf_on_all () {
+ command="$@"
+ for repo in full-index-v3 full-index-v4 \
+ sparse-index-v3 sparse-index-v4
+ do
+ test_perf "$command ($repo)" "
+ (
+ cd $repo &&
+ echo >>$SPARSE_CONE/a &&
+ $command
+ )
+ "
+ done
+}
+
+test_perf_on_all git status
+test_perf_on_all git add -A
+test_perf_on_all git add .
+test_perf_on_all git commit -a -m A
+
+test_done
diff --git a/t/perf/p5303-many-packs.sh b/t/perf/p5303-many-packs.sh
index ce0c42cc9f..35c0cbdf49 100755
--- a/t/perf/p5303-many-packs.sh
+++ b/t/perf/p5303-many-packs.sh
@@ -28,11 +28,18 @@ repack_into_n () {
push @commits, $_ if $. % 5 == 1;
}
print reverse @commits;
- ' "$1" >pushes
+ ' "$1" >pushes &&
# create base packfile
- head -n 1 pushes |
- git pack-objects --delta-base-offset --revs staging/pack
+ base_pack=$(
+ head -n 1 pushes |
+ git pack-objects --delta-base-offset --revs staging/pack
+ ) &&
+ test_export base_pack &&
+
+ # create an empty packfile
+ empty_pack=$(git pack-objects staging/pack </dev/null) &&
+ test_export empty_pack &&
# and then incrementals between each pair of commits
last= &&
@@ -49,6 +56,12 @@ repack_into_n () {
last=$rev
done <pushes &&
+ (
+ find staging -type f -name 'pack-*.pack' |
+ xargs -n 1 basename | grep -v "$base_pack" &&
+ printf "^pack-%s.pack\n" $base_pack
+ ) >stdin.packs
+
# and install the whole thing
rm -f .git/objects/pack/* &&
mv staging/* .git/objects/pack/
@@ -91,6 +104,23 @@ do
--reflog --indexed-objects --delta-base-offset \
--stdout </dev/null >/dev/null
'
+
+ test_perf "repack with kept ($nr_packs)" '
+ git pack-objects --keep-true-parents \
+ --keep-pack=pack-$empty_pack.pack \
+ --honor-pack-keep --non-empty --all \
+ --reflog --indexed-objects --delta-base-offset \
+ --stdout </dev/null >/dev/null
+ '
+
+ test_perf "repack with --stdin-packs ($nr_packs)" '
+ git pack-objects \
+ --keep-true-parents \
+ --stdin-packs \
+ --non-empty \
+ --delta-base-offset \
+ --stdout <stdin.packs >/dev/null
+ '
done
# Measure pack loading with 10,000 packs.
diff --git a/t/perf/p5310-pack-bitmaps.sh b/t/perf/p5310-pack-bitmaps.sh
index b3e725f031..452be01056 100755
--- a/t/perf/p5310-pack-bitmaps.sh
+++ b/t/perf/p5310-pack-bitmaps.sh
@@ -15,6 +15,12 @@ test_expect_success 'setup bitmap config' '
git config pack.writebitmaps 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_perf 'repack to disk' '
git repack -ad
'
@@ -43,6 +49,14 @@ test_perf 'rev-list (objects)' '
git rev-list --all --use-bitmap-index --objects >/dev/null
'
+test_perf 'rev-list with tag negated via --not --all (objects)' '
+ git rev-list perf-tag --not --all --use-bitmap-index --objects >/dev/null
+'
+
+test_perf 'rev-list with negative tag (objects)' '
+ git rev-list HEAD --not perf-tag --use-bitmap-index --objects >/dev/null
+'
+
test_perf 'rev-list count with blob:none' '
git rev-list --use-bitmap-index --count --objects --all \
--filter=blob:none >/dev/null
diff --git a/t/perf/p5600-partial-clone.sh b/t/perf/p5600-partial-clone.sh
index 3e04bd2ae1..a965f2c4d6 100755
--- a/t/perf/p5600-partial-clone.sh
+++ b/t/perf/p5600-partial-clone.sh
@@ -23,4 +23,20 @@ test_perf 'checkout of result' '
git -C worktree checkout -f
'
+test_perf 'fsck' '
+ git -C bare.git fsck
+'
+
+test_perf 'count commits' '
+ git -C bare.git rev-list --all --count
+'
+
+test_perf 'count non-promisor commits' '
+ git -C bare.git rev-list --all --count --exclude-promisor-objects
+'
+
+test_perf 'gc' '
+ git -C bare.git gc
+'
+
test_done
diff --git a/t/perf/p7519-fsmonitor.sh b/t/perf/p7519-fsmonitor.sh
index b657564aed..5eb5044a10 100755
--- a/t/perf/p7519-fsmonitor.sh
+++ b/t/perf/p7519-fsmonitor.sh
@@ -216,6 +216,10 @@ test_fsmonitor_suite() {
git diff
'
+ test_perf_w_drop_caches "diff HEAD ($DESC)" '
+ git diff HEAD
+ '
+
test_perf_w_drop_caches "diff -- 0_files ($DESC)" '
git diff -- 1_file
'
diff --git a/t/perf/perf-lib.sh b/t/perf/perf-lib.sh
index e385c6896f..601d9f67dd 100644
--- a/t/perf/perf-lib.sh
+++ b/t/perf/perf-lib.sh
@@ -70,6 +70,19 @@ test_perf_do_repo_symlink_config_ () {
test_have_prereq SYMLINKS || git config core.symlinks false
}
+test_perf_copy_repo_contents () {
+ for stuff in "$1"/*
+ do
+ case "$stuff" in
+ */objects|*/hooks|*/config|*/commondir|*/gitdir|*/worktrees)
+ ;;
+ *)
+ cp -R "$stuff" "$repo/.git/" || exit 1
+ ;;
+ esac
+ done
+}
+
test_perf_create_repo_from () {
test "$#" = 2 ||
BUG "not 2 parameters to test-create-repo"
@@ -77,20 +90,20 @@ test_perf_create_repo_from () {
source="$2"
source_git="$("$MODERN_GIT" -C "$source" rev-parse --git-dir)"
objects_dir="$("$MODERN_GIT" -C "$source" rev-parse --git-path objects)"
+ common_dir="$("$MODERN_GIT" -C "$source" rev-parse --git-common-dir)"
mkdir -p "$repo/.git"
(
cd "$source" &&
{ cp -Rl "$objects_dir" "$repo/.git/" 2>/dev/null ||
cp -R "$objects_dir" "$repo/.git/"; } &&
- for stuff in "$source_git"/*; do
- case "$stuff" in
- */objects|*/hooks|*/config|*/commondir)
- ;;
- *)
- cp -R "$stuff" "$repo/.git/" || exit 1
- ;;
- esac
- done
+
+ # common_dir must come first here, since we want source_git to
+ # take precedence and overwrite any overlapping files
+ test_perf_copy_repo_contents "$common_dir"
+ if test "$source_git" != "$common_dir"
+ then
+ test_perf_copy_repo_contents "$source_git"
+ fi
) &&
(
cd "$repo" &&
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index 0803994874..acd662e403 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -186,21 +186,33 @@ test_expect_success 'init with --template (blank)' '
test_path_is_missing template-blank/.git/info/exclude
'
-test_expect_success 'init with init.templatedir set' '
- mkdir templatedir-source &&
- echo Content >templatedir-source/file &&
- test_config_global init.templatedir "${HOME}/templatedir-source" &&
+init_no_templatedir_env () {
(
- mkdir templatedir-set &&
- cd templatedir-set &&
sane_unset GIT_TEMPLATE_DIR &&
NO_SET_GIT_TEMPLATE_DIR=t &&
export NO_SET_GIT_TEMPLATE_DIR &&
- git init
- ) &&
+ git init "$1"
+ )
+}
+
+test_expect_success 'init with init.templatedir set' '
+ mkdir templatedir-source &&
+ echo Content >templatedir-source/file &&
+ test_config_global init.templatedir "${HOME}/templatedir-source" &&
+
+ init_no_templatedir_env templatedir-set &&
test_cmp templatedir-source/file templatedir-set/.git/file
'
+test_expect_success 'init with init.templatedir using ~ expansion' '
+ mkdir -p templatedir-source &&
+ echo Content >templatedir-source/file &&
+ test_config_global init.templatedir "~/templatedir-source" &&
+
+ init_no_templatedir_env templatedir-expansion &&
+ test_cmp templatedir-source/file templatedir-expansion/.git/file
+'
+
test_expect_success 'init --bare/--shared overrides system/global config' '
test_config_global core.bare false &&
test_config_global core.sharedRepository 0640 &&
diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh
index b660593c20..1e4c672b84 100755
--- a/t/t0003-attributes.sh
+++ b/t/t0003-attributes.sh
@@ -4,12 +4,16 @@ test_description=gitattributes
. ./test-lib.sh
-attr_check () {
+attr_check_basic () {
path="$1" expect="$2" git_opts="$3" &&
git $git_opts check-attr test -- "$path" >actual 2>err &&
echo "$path: test: $expect" >expect &&
- test_cmp expect actual &&
+ test_cmp expect actual
+}
+
+attr_check () {
+ attr_check_basic "$@" &&
test_must_be_empty err
}
@@ -331,7 +335,6 @@ test_expect_success 'binary macro expanded by -a' '
test_cmp expect actual
'
-
test_expect_success 'query binary macro directly' '
echo "file binary" >.gitattributes &&
echo file: binary: set >expect &&
@@ -339,4 +342,31 @@ test_expect_success 'query binary macro directly' '
test_cmp expect actual
'
+test_expect_success SYMLINKS 'set up symlink tests' '
+ echo "* test" >attr &&
+ rm -f .gitattributes
+'
+
+test_expect_success SYMLINKS 'symlinks respected in core.attributesFile' '
+ test_when_finished "rm symlink" &&
+ ln -s attr symlink &&
+ test_config core.attributesFile "$(pwd)/symlink" &&
+ attr_check file set
+'
+
+test_expect_success SYMLINKS 'symlinks respected in info/attributes' '
+ test_when_finished "rm .git/info/attributes" &&
+ ln -s ../../attr .git/info/attributes &&
+ attr_check file set
+'
+
+test_expect_success SYMLINKS 'symlinks not respected in-tree' '
+ test_when_finished "rm -rf .gitattributes subdir" &&
+ ln -s attr .gitattributes &&
+ mkdir subdir &&
+ ln -s ../attr subdir/.gitattributes &&
+ attr_check_basic subdir/file unspecified &&
+ test_i18ngrep "unable to access.*gitattributes" err
+'
+
test_done
diff --git a/t/t0008-ignores.sh b/t/t0008-ignores.sh
index f7abde62f6..a594b4aa7d 100755
--- a/t/t0008-ignores.sh
+++ b/t/t0008-ignores.sh
@@ -865,4 +865,38 @@ test_expect_success 'info/exclude trumps core.excludesfile' '
test_cmp expect actual
'
+test_expect_success SYMLINKS 'set up ignore file for symlink tests' '
+ echo "*" >ignore &&
+ rm -f .gitignore .git/info/exclude
+'
+
+test_expect_success SYMLINKS 'symlinks respected in core.excludesFile' '
+ test_when_finished "rm symlink" &&
+ ln -s ignore symlink &&
+ test_config core.excludesFile "$(pwd)/symlink" &&
+ echo file >expect &&
+ git check-ignore file >actual 2>err &&
+ test_cmp expect actual &&
+ test_must_be_empty err
+'
+
+test_expect_success SYMLINKS 'symlinks respected in info/exclude' '
+ test_when_finished "rm .git/info/exclude" &&
+ ln -s ../../ignore .git/info/exclude &&
+ echo file >expect &&
+ git check-ignore file >actual 2>err &&
+ test_cmp expect actual &&
+ test_must_be_empty err
+'
+
+test_expect_success SYMLINKS 'symlinks not respected in-tree' '
+ test_when_finished "rm .gitignore" &&
+ ln -s ignore .gitignore &&
+ mkdir subdir &&
+ ln -s ignore subdir/.gitignore &&
+ test_must_fail git check-ignore subdir/file >actual 2>err &&
+ test_must_be_empty actual &&
+ test_i18ngrep "unable to access.*gitignore" err
+'
+
test_done
diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh
index a9e10a0c21..b5749f327d 100755
--- a/t/t0021-conversion.sh
+++ b/t/t0021-conversion.sh
@@ -257,6 +257,30 @@ test_expect_success 'required filter clean failure' '
test_must_fail git add test.fc
'
+test_expect_success 'required filter with absent clean field' '
+ test_config filter.absentclean.smudge cat &&
+ test_config filter.absentclean.required true &&
+
+ echo "*.ac filter=absentclean" >.gitattributes &&
+
+ echo test >test.ac &&
+ test_must_fail git add test.ac 2>stderr &&
+ test_i18ngrep "fatal: test.ac: clean filter .absentclean. failed" stderr
+'
+
+test_expect_success 'required filter with absent smudge field' '
+ test_config filter.absentsmudge.clean cat &&
+ test_config filter.absentsmudge.required true &&
+
+ echo "*.as filter=absentsmudge" >.gitattributes &&
+
+ echo test >test.as &&
+ git add test.as &&
+ rm -f test.as &&
+ test_must_fail git checkout -- test.as 2>stderr &&
+ test_i18ngrep "fatal: test.as: smudge filter absentsmudge failed" stderr
+'
+
test_expect_success 'filtering large input to small output should use little memory' '
test_config filter.devnull.clean "cat >/dev/null" &&
test_config filter.devnull.required true &&
diff --git a/t/t0027-auto-crlf.sh b/t/t0027-auto-crlf.sh
index d24d5acfbc..4a5c5c602c 100755
--- a/t/t0027-auto-crlf.sh
+++ b/t/t0027-auto-crlf.sh
@@ -386,7 +386,9 @@ test_expect_success 'setup main' '
test_tick
'
-
+# Disable extra chain-linting for the next set of tests. There are many
+# auto-generated ones that are not worth checking over and over.
+GIT_TEST_CHAIN_LINT_HARDER_DEFAULT=0
warn_LF_CRLF="LF will be replaced by CRLF"
warn_CRLF_LF="CRLF will be replaced by LF"
@@ -597,6 +599,9 @@ do
checkout_files auto "$id" "" false native $NL CRLF CRLF_mix_LF LF_mix_CR LF_nul
done
+# The rest of the tests are unique; do the usual linting.
+unset GIT_TEST_CHAIN_LINT_HARDER_DEFAULT
+
# Should be the last test case: remove some files from the worktree
test_expect_success 'ls-files --eol -d -z' '
rm crlf_false_attr__CRLF.txt crlf_false_attr__CRLF_mix_LF.txt crlf_false_attr__LF.txt .gitattributes &&
diff --git a/t/t0028-working-tree-encoding.sh b/t/t0028-working-tree-encoding.sh
index f970a9806b..82905a2156 100755
--- a/t/t0028-working-tree-encoding.sh
+++ b/t/t0028-working-tree-encoding.sh
@@ -6,33 +6,10 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-encoding.sh"
GIT_TRACE_WORKING_TREE_ENCODING=1 && export GIT_TRACE_WORKING_TREE_ENCODING
-test_lazy_prereq NO_UTF16_BOM '
- test $(printf abc | iconv -f UTF-8 -t UTF-16 | wc -c) = 6
-'
-
-test_lazy_prereq NO_UTF32_BOM '
- test $(printf abc | iconv -f UTF-8 -t UTF-32 | wc -c) = 12
-'
-
-write_utf16 () {
- if test_have_prereq NO_UTF16_BOM
- then
- printf '\376\377'
- fi &&
- iconv -f UTF-8 -t UTF-16
-}
-
-write_utf32 () {
- if test_have_prereq NO_UTF32_BOM
- then
- printf '\0\0\376\377'
- fi &&
- iconv -f UTF-8 -t UTF-32
-}
-
test_expect_success 'setup test files' '
git config core.eol lf &&
diff --git a/t/t0052-simple-ipc.sh b/t/t0052-simple-ipc.sh
new file mode 100755
index 0000000000..ff98be31a5
--- /dev/null
+++ b/t/t0052-simple-ipc.sh
@@ -0,0 +1,122 @@
+#!/bin/sh
+
+test_description='simple command server'
+
+. ./test-lib.sh
+
+test-tool simple-ipc SUPPORTS_SIMPLE_IPC || {
+ skip_all='simple IPC not supported on this platform'
+ test_done
+}
+
+stop_simple_IPC_server () {
+ test-tool simple-ipc stop-daemon
+}
+
+test_expect_success 'start simple command server' '
+ test_atexit stop_simple_IPC_server &&
+ test-tool simple-ipc start-daemon --threads=8 &&
+ test-tool simple-ipc is-active
+'
+
+test_expect_success 'simple command server' '
+ test-tool simple-ipc send --token=ping >actual &&
+ echo pong >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'servers cannot share the same path' '
+ test_must_fail test-tool simple-ipc run-daemon &&
+ test-tool simple-ipc is-active
+'
+
+test_expect_success 'big response' '
+ test-tool simple-ipc send --token=big >actual &&
+ test_line_count -ge 10000 actual &&
+ grep -q "big: [0]*9999\$" actual
+'
+
+test_expect_success 'chunk response' '
+ test-tool simple-ipc send --token=chunk >actual &&
+ test_line_count -ge 10000 actual &&
+ grep -q "big: [0]*9999\$" actual
+'
+
+test_expect_success 'slow response' '
+ test-tool simple-ipc send --token=slow >actual &&
+ test_line_count -ge 100 actual &&
+ grep -q "big: [0]*99\$" actual
+'
+
+# Send an IPC with n=100,000 bytes of ballast. This should be large enough
+# to force both the kernel and the pkt-line layer to chunk the message to the
+# daemon and for the daemon to receive it in chunks.
+#
+test_expect_success 'sendbytes' '
+ test-tool simple-ipc sendbytes --bytecount=100000 --byte=A >actual &&
+ grep "sent:A00100000 rcvd:A00100000" actual
+'
+
+# Start a series of <threads> client threads that each make <batchsize>
+# IPC requests to the server. Each (<threads> * <batchsize>) request
+# will open a new connection to the server and randomly bind to a server
+# thread. Each client thread exits after completing its batch. So the
+# total number of live client threads will be smaller than the total.
+# Each request will send a message containing at least <bytecount> bytes
+# of ballast. (Responses are small.)
+#
+# The purpose here is to test threading in the server and responding to
+# many concurrent client requests (regardless of whether they come from
+# 1 client process or many). And to test that the server side of the
+# named pipe/socket is stable. (On Windows this means that the server
+# pipe is properly recycled.)
+#
+# On Windows it also lets us adjust the connection timeout in the
+# `ipc_client_send_command()`.
+#
+# Note it is easy to drive the system into failure by requesting an
+# insane number of threads on client or server and/or increasing the
+# per-thread batchsize or the per-request bytecount (ballast).
+# On Windows these failures look like "pipe is busy" errors.
+# So I've chosen fairly conservative values for now.
+#
+# We expect output of the form "sent:<letter><length> ..."
+# With terms (7, 19, 13) we expect:
+# <letter> in [A-G]
+# <length> in [19+0 .. 19+(13-1)]
+# and (7 * 13) successful responses.
+#
+test_expect_success 'stress test threads' '
+ test-tool simple-ipc multiple \
+ --threads=7 \
+ --bytecount=19 \
+ --batchsize=13 \
+ >actual &&
+ test_line_count = 92 actual &&
+ grep "good 91" actual &&
+ grep "sent:A" <actual >actual_a &&
+ cat >expect_a <<-EOF &&
+ sent:A00000019 rcvd:A00000019
+ sent:A00000020 rcvd:A00000020
+ sent:A00000021 rcvd:A00000021
+ sent:A00000022 rcvd:A00000022
+ sent:A00000023 rcvd:A00000023
+ sent:A00000024 rcvd:A00000024
+ sent:A00000025 rcvd:A00000025
+ sent:A00000026 rcvd:A00000026
+ sent:A00000027 rcvd:A00000027
+ sent:A00000028 rcvd:A00000028
+ sent:A00000029 rcvd:A00000029
+ sent:A00000030 rcvd:A00000030
+ sent:A00000031 rcvd:A00000031
+ EOF
+ test_cmp expect_a actual_a
+'
+
+test_expect_success 'stop-daemon works' '
+ test-tool simple-ipc stop-daemon &&
+ test_must_fail test-tool simple-ipc is-active &&
+ test_must_fail test-tool simple-ipc send --token=ping
+'
+
+test_done
diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
index 0ff06b5d1b..de4960783f 100755
--- a/t/t0060-path-utils.sh
+++ b/t/t0060-path-utils.sh
@@ -468,6 +468,36 @@ test_expect_success 'match .gitmodules' '
.gitmodules,:\$DATA
'
+test_expect_success 'match .gitattributes' '
+ test-tool path-utils is_dotgitattributes \
+ .gitattributes \
+ .git${u200c}attributes \
+ .Gitattributes \
+ .gitattributeS \
+ GITATT~1 \
+ GI7D29~1
+'
+
+test_expect_success 'match .gitignore' '
+ test-tool path-utils is_dotgitignore \
+ .gitignore \
+ .git${u200c}ignore \
+ .Gitignore \
+ .gitignorE \
+ GITIGN~1 \
+ GI250A~1
+'
+
+test_expect_success 'match .mailmap' '
+ test-tool path-utils is_dotmailmap \
+ .mailmap \
+ .mail${u200c}map \
+ .Mailmap \
+ .mailmaP \
+ MAILMA~1 \
+ MABA30~1
+'
+
test_expect_success MINGW 'is_valid_path() on Windows' '
test-tool path-utils is_valid_path \
win32 \
diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh
index fc64e9ed99..38fc8340f5 100755
--- a/t/t1091-sparse-checkout-builtin.sh
+++ b/t/t1091-sparse-checkout-builtin.sh
@@ -205,6 +205,19 @@ test_expect_success 'sparse-checkout disable' '
check_files repo a deep folder1 folder2
'
+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
+'
+
test_expect_success 'cone mode: init and set' '
git -C repo sparse-checkout init --cone &&
git -C repo config --list >config &&
diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh
index 8cd3e5a8d2..e9a815ca7a 100755
--- a/t/t1092-sparse-checkout-compatibility.sh
+++ b/t/t1092-sparse-checkout-compatibility.sh
@@ -2,11 +2,15 @@
test_description='compare full workdir to sparse workdir'
+GIT_TEST_SPLIT_INDEX=0
+GIT_TEST_SPARSE_INDEX=
+
. ./test-lib.sh
test_expect_success 'setup' '
git init initial-repo &&
(
+ GIT_TEST_SPARSE_INDEX=0 &&
cd initial-repo &&
echo a >a &&
echo "after deep" >e &&
@@ -87,39 +91,102 @@ init_repos () {
cp -r initial-repo sparse-checkout &&
git -C sparse-checkout reset --hard &&
- git -C sparse-checkout sparse-checkout init --cone &&
+
+ cp -r initial-repo sparse-index &&
+ git -C sparse-index reset --hard &&
# initialize sparse-checkout definitions
- git -C sparse-checkout sparse-checkout set deep
+ git -C sparse-checkout sparse-checkout init --cone &&
+ git -C sparse-checkout sparse-checkout set deep &&
+ git -C sparse-index sparse-checkout init --cone --sparse-index &&
+ test_cmp_config -C sparse-index true index.sparse &&
+ git -C sparse-index sparse-checkout set deep
}
run_on_sparse () {
(
cd sparse-checkout &&
- $* >../sparse-checkout-out 2>../sparse-checkout-err
+ GIT_PROGRESS_DELAY=100000 "$@" >../sparse-checkout-out 2>../sparse-checkout-err
+ ) &&
+ (
+ cd sparse-index &&
+ GIT_PROGRESS_DELAY=100000 "$@" >../sparse-index-out 2>../sparse-index-err
)
}
run_on_all () {
(
cd full-checkout &&
- $* >../full-checkout-out 2>../full-checkout-err
+ GIT_PROGRESS_DELAY=100000 "$@" >../full-checkout-out 2>../full-checkout-err
) &&
- run_on_sparse $*
+ run_on_sparse "$@"
}
test_all_match () {
- run_on_all $* &&
+ run_on_all "$@" &&
test_cmp full-checkout-out sparse-checkout-out &&
- test_cmp full-checkout-err sparse-checkout-err
+ test_cmp full-checkout-out sparse-index-out &&
+ test_cmp full-checkout-err sparse-checkout-err &&
+ test_cmp full-checkout-err sparse-index-err
+}
+
+test_sparse_match () {
+ run_on_sparse "$@" &&
+ test_cmp sparse-checkout-out sparse-index-out &&
+ test_cmp sparse-checkout-err sparse-index-err
}
+test_expect_success 'sparse-index contents' '
+ init_repos &&
+
+ test-tool -C sparse-index read-cache --table >cache &&
+ for dir in folder1 folder2 x
+ do
+ TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
+ grep "040000 tree $TREE $dir/" cache \
+ || return 1
+ done &&
+
+ git -C sparse-index sparse-checkout set folder1 &&
+
+ test-tool -C sparse-index read-cache --table >cache &&
+ for dir in deep folder2 x
+ do
+ TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
+ grep "040000 tree $TREE $dir/" cache \
+ || return 1
+ done &&
+
+ git -C sparse-index sparse-checkout set deep/deeper1 &&
+
+ test-tool -C sparse-index read-cache --table >cache &&
+ for dir in deep/deeper2 folder1 folder2 x
+ do
+ TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
+ grep "040000 tree $TREE $dir/" cache \
+ || return 1
+ done &&
+
+ # Disabling the sparse-index removes tree entries with full ones
+ git -C sparse-index sparse-checkout init --no-sparse-index &&
+
+ test-tool -C sparse-index read-cache --table >cache &&
+ ! grep "040000 tree" cache &&
+ test_sparse_match test-tool read-cache --table
+'
+
+test_expect_success 'expanded in-memory index matches full index' '
+ init_repos &&
+ test_sparse_match test-tool read-cache --expand --table
+'
+
test_expect_success 'status with options' '
init_repos &&
+ test_sparse_match ls &&
test_all_match git status --porcelain=v2 &&
test_all_match git status --porcelain=v2 -z -u &&
test_all_match git status --porcelain=v2 -uno &&
- run_on_all "touch README.md" &&
+ run_on_all touch README.md &&
test_all_match git status --porcelain=v2 &&
test_all_match git status --porcelain=v2 -z -u &&
test_all_match git status --porcelain=v2 -uno &&
@@ -135,7 +202,7 @@ test_expect_success 'add, commit, checkout' '
write_script edit-contents <<-\EOF &&
echo text >>$1
EOF
- run_on_all "../edit-contents README.md" &&
+ run_on_all ../edit-contents README.md &&
test_all_match git add README.md &&
test_all_match git status --porcelain=v2 &&
@@ -144,7 +211,7 @@ test_expect_success 'add, commit, checkout' '
test_all_match git checkout HEAD~1 &&
test_all_match git checkout - &&
- run_on_all "../edit-contents README.md" &&
+ run_on_all ../edit-contents README.md &&
test_all_match git add -A &&
test_all_match git status --porcelain=v2 &&
@@ -153,7 +220,7 @@ test_expect_success 'add, commit, checkout' '
test_all_match git checkout HEAD~1 &&
test_all_match git checkout - &&
- run_on_all "../edit-contents deep/newfile" &&
+ run_on_all ../edit-contents deep/newfile &&
test_all_match git status --porcelain=v2 -uno &&
test_all_match git status --porcelain=v2 &&
@@ -186,7 +253,7 @@ test_expect_success 'diff --staged' '
write_script edit-contents <<-\EOF &&
echo text >>README.md
EOF
- run_on_all "../edit-contents" &&
+ run_on_all ../edit-contents &&
test_all_match git diff &&
test_all_match git diff --staged &&
@@ -252,6 +319,17 @@ test_expect_failure 'checkout and reset (mixed)' '
test_all_match git reset update-folder2
'
+# Ensure that sparse-index behaves identically to
+# sparse-checkout with a full index.
+test_expect_success 'checkout and reset (mixed) [sparse]' '
+ init_repos &&
+
+ test_sparse_match git checkout -b reset-test update-deep &&
+ test_sparse_match git reset deepest &&
+ test_sparse_match git reset update-folder1 &&
+ test_sparse_match git reset update-folder2
+'
+
test_expect_success 'merge' '
init_repos &&
@@ -280,7 +358,7 @@ test_expect_success 'clean' '
echo bogus >>.gitignore &&
run_on_all cp ../.gitignore . &&
test_all_match git add .gitignore &&
- test_all_match git commit -m ignore-bogus-files &&
+ test_all_match git commit -m "ignore bogus files" &&
run_on_sparse mkdir folder1 &&
run_on_all touch folder1/bogus &&
@@ -288,14 +366,51 @@ test_expect_success 'clean' '
test_all_match git status --porcelain=v2 &&
test_all_match git clean -f &&
test_all_match git status --porcelain=v2 &&
+ test_sparse_match ls &&
+ test_sparse_match ls folder1 &&
test_all_match git clean -xf &&
test_all_match git status --porcelain=v2 &&
+ test_sparse_match ls &&
+ test_sparse_match ls folder1 &&
test_all_match git clean -xdf &&
test_all_match git status --porcelain=v2 &&
+ test_sparse_match ls &&
+ test_sparse_match ls folder1 &&
+
+ test_sparse_match test_path_is_dir folder1
+'
+
+test_expect_success 'submodule handling' '
+ init_repos &&
+
+ test_all_match mkdir modules &&
+ test_all_match touch modules/a &&
+ test_all_match git add modules &&
+ test_all_match git commit -m "add modules directory" &&
+
+ run_on_all git submodule add "$(pwd)/initial-repo" modules/sub &&
+ test_all_match git commit -m "add submodule" &&
+
+ # having a submodule prevents "modules" from collapse
+ 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
+'
+
+test_expect_success 'sparse-index is expanded and converted back' '
+ init_repos &&
+
+ GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
+ git -C sparse-index -c core.fsmonitor="" reset --hard &&
+ test_region index convert_to_sparse trace2.txt &&
+ test_region index ensure_full_index trace2.txt &&
- test_path_is_dir sparse-checkout/folder1
+ rm trace2.txt &&
+ GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
+ git -C sparse-index -c core.fsmonitor="" status -uno &&
+ test_region index ensure_full_index trace2.txt
'
test_done
diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index e0dd5d65ce..9ff46f3b04 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -1374,16 +1374,29 @@ test_expect_success 'git --config-env=key=envvar support' '
cat >expect <<-\EOF &&
value
value
+ value
+ value
+ false
false
EOF
{
ENVVAR=value git --config-env=core.name=ENVVAR config core.name &&
+ ENVVAR=value git --config-env core.name=ENVVAR config core.name &&
ENVVAR=value git --config-env=foo.CamelCase=ENVVAR config foo.camelcase &&
- ENVVAR= git --config-env=foo.flag=ENVVAR config --bool foo.flag
+ ENVVAR=value git --config-env foo.CamelCase=ENVVAR config foo.camelcase &&
+ ENVVAR= git --config-env=foo.flag=ENVVAR config --bool foo.flag &&
+ ENVVAR= git --config-env foo.flag=ENVVAR config --bool foo.flag
} >actual &&
test_cmp expect actual
'
+test_expect_success 'git --config-env with missing value' '
+ test_must_fail env ENVVAR=value git --config-env 2>error &&
+ grep "no config key given for --config-env" error &&
+ test_must_fail env ENVVAR=value git --config-env config core.name 2>error &&
+ grep "invalid config format: config" error
+'
+
test_expect_success 'git --config-env fails with invalid parameters' '
test_must_fail git --config-env=foo.flag config --bool foo.flag 2>error &&
test_i18ngrep "invalid config format: foo.flag" error &&
@@ -2059,6 +2072,91 @@ test_expect_success '--show-scope with --show-origin' '
test_cmp expect output
'
+test_expect_success 'override global and system config' '
+ test_when_finished rm -f "$HOME"/.config/git &&
+
+ cat >"$HOME"/.gitconfig <<-EOF &&
+ [home]
+ config = true
+ EOF
+ mkdir -p "$HOME"/.config/git &&
+ cat >"$HOME"/.config/git/config <<-EOF &&
+ [xdg]
+ config = true
+ EOF
+ cat >.git/config <<-EOF &&
+ [local]
+ config = true
+ EOF
+ cat >custom-global-config <<-EOF &&
+ [global]
+ config = true
+ EOF
+ cat >custom-system-config <<-EOF &&
+ [system]
+ config = true
+ EOF
+
+ cat >expect <<-EOF &&
+ global xdg.config=true
+ global home.config=true
+ local local.config=true
+ EOF
+ git config --show-scope --list >output &&
+ test_cmp expect output &&
+
+ cat >expect <<-EOF &&
+ system system.config=true
+ global global.config=true
+ local local.config=true
+ EOF
+ GIT_CONFIG_NOSYSTEM=false GIT_CONFIG_SYSTEM=custom-system-config GIT_CONFIG_GLOBAL=custom-global-config \
+ git config --show-scope --list >output &&
+ test_cmp expect output &&
+
+ cat >expect <<-EOF &&
+ local local.config=true
+ EOF
+ GIT_CONFIG_NOSYSTEM=false GIT_CONFIG_SYSTEM=/dev/null GIT_CONFIG_GLOBAL=/dev/null \
+ git config --show-scope --list >output &&
+ test_cmp expect output
+'
+
+test_expect_success 'override global and system config with missing file' '
+ test_must_fail env GIT_CONFIG_GLOBAL=does-not-exist GIT_CONFIG_SYSTEM=/dev/null git config --global --list &&
+ test_must_fail env GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=does-not-exist git config --system --list &&
+ GIT_CONFIG_GLOBAL=does-not-exist GIT_CONFIG_SYSTEM=does-not-exist git version
+'
+
+test_expect_success 'system override has no effect with GIT_CONFIG_NOSYSTEM' '
+ # `git config --system` has different semantics compared to other
+ # commands as it ignores GIT_CONFIG_NOSYSTEM. We thus test whether the
+ # variable has an effect via a different proxy.
+ cat >alias-config <<-EOF &&
+ [alias]
+ hello-world = !echo "hello world"
+ EOF
+ test_must_fail env GIT_CONFIG_NOSYSTEM=true GIT_CONFIG_SYSTEM=alias-config \
+ git hello-world &&
+ GIT_CONFIG_NOSYSTEM=false GIT_CONFIG_SYSTEM=alias-config \
+ git hello-world >actual &&
+ echo "hello world" >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'write to overridden global and system config' '
+ cat >expect <<EOF &&
+[config]
+ key = value
+EOF
+
+ GIT_CONFIG_GLOBAL=write-to-global git config --global config.key value &&
+ test_cmp expect write-to-global &&
+
+ GIT_CONFIG_SYSTEM=write-to-system git config --system config.key value &&
+ test_cmp expect write-to-system
+'
+
for opt in --local --worktree
do
test_expect_success "$opt requires a repo" '
diff --git a/t/t1306-xdg-files.sh b/t/t1306-xdg-files.sh
index dd87b43be1..40d3c42618 100755
--- a/t/t1306-xdg-files.sh
+++ b/t/t1306-xdg-files.sh
@@ -116,7 +116,7 @@ test_expect_success 'Exclusion in a non-XDG global ignore file' '
test_expect_success 'Checking XDG ignore file when HOME is unset' '
(sane_unset HOME &&
git config --unset core.excludesfile &&
- git ls-files --exclude-standard --ignored >actual) &&
+ git ls-files --exclude-standard --ignored --others >actual) &&
test_must_be_empty actual
'
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index e31f65f381..4506cd435b 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -1598,4 +1598,13 @@ test_expect_success 'transaction cannot restart ongoing transaction' '
test_must_fail git show-ref --verify refs/heads/restart
'
+test_expect_success 'directory not created deleting packed ref' '
+ git branch d1/d2/r1 HEAD &&
+ git pack-refs --all &&
+ test_path_is_missing .git/refs/heads/d1/d2 &&
+ git update-ref -d refs/heads/d1/d2/r1 &&
+ test_path_is_missing .git/refs/heads/d1/d2 &&
+ test_path_is_missing .git/refs/heads/d1
+'
+
test_done
diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh
index deae916707..1c2df08333 100755
--- a/t/t1500-rev-parse.sh
+++ b/t/t1500-rev-parse.sh
@@ -146,6 +146,10 @@ test_expect_success '--path-format can change in the middle of the command line'
test_cmp expect actual
'
+test_expect_success '--path-format does not segfault without an argument' '
+ test_must_fail git rev-parse --path-format
+'
+
test_expect_success 'git-common-dir from worktree root' '
echo .git >expect &&
git rev-parse --git-common-dir >actual &&
diff --git a/t/t2021-checkout-overwrite.sh b/t/t2021-checkout-overwrite.sh
index c2ada7de37..70d69263e6 100755
--- a/t/t2021-checkout-overwrite.sh
+++ b/t/t2021-checkout-overwrite.sh
@@ -51,4 +51,16 @@ test_expect_success SYMLINKS 'the symlink remained' '
test -h a/b
'
+test_expect_success SYMLINKS 'checkout -f must not follow symlinks when removing entries' '
+ git checkout -f start &&
+ mkdir dir &&
+ >dir/f &&
+ git add dir/f &&
+ git commit -m "add dir/f" &&
+ mv dir untracked &&
+ ln -s untracked dir &&
+ git checkout -f HEAD~ &&
+ test_path_is_file untracked/f
+'
+
test_done
diff --git a/t/t2080-parallel-checkout-basics.sh b/t/t2080-parallel-checkout-basics.sh
new file mode 100755
index 0000000000..3e0f8c675f
--- /dev/null
+++ b/t/t2080-parallel-checkout-basics.sh
@@ -0,0 +1,229 @@
+#!/bin/sh
+
+test_description='parallel-checkout basics
+
+Ensure that parallel-checkout basically works on clone and checkout, spawning
+the required number of workers and correctly populating both the index and the
+working tree.
+'
+
+TEST_NO_CREATE_REPO=1
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-parallel-checkout.sh"
+
+# Test parallel-checkout with a branch switch containing a variety of file
+# creations, deletions, and modifications, involving different entry types.
+# The branches B1 and B2 have the following paths:
+#
+# B1 B2
+# a/a (file) a (file)
+# b (file) b/b (file)
+#
+# c/c (file) c (symlink)
+# d (symlink) d/d (file)
+#
+# e/e (file) e (submodule)
+# f (submodule) f/f (file)
+#
+# g (submodule) g (symlink)
+# h (symlink) h (submodule)
+#
+# Additionally, the following paths are present on both branches, but with
+# different contents:
+#
+# i (file) i (file)
+# j (symlink) j (symlink)
+# k (submodule) k (submodule)
+#
+# And the following paths are only present in one of the branches:
+#
+# l/l (file) -
+# - m/m (file)
+#
+test_expect_success 'setup repo for checkout with various types of changes' '
+ git init sub &&
+ (
+ cd sub &&
+ git checkout -b B2 &&
+ echo B2 >file &&
+ git add file &&
+ git commit -m file &&
+
+ git checkout -b B1 &&
+ echo B1 >file &&
+ git add file &&
+ git commit -m file
+ ) &&
+
+ git init various &&
+ (
+ cd various &&
+
+ git checkout -b B1 &&
+ mkdir a c e &&
+ echo a/a >a/a &&
+ echo b >b &&
+ echo c/c >c/c &&
+ test_ln_s_add c d &&
+ echo e/e >e/e &&
+ git submodule add ../sub f &&
+ git submodule add ../sub g &&
+ test_ln_s_add c h &&
+
+ echo "B1 i" >i &&
+ test_ln_s_add c j &&
+ git submodule add -b B1 ../sub k &&
+ mkdir l &&
+ echo l/l >l/l &&
+
+ git add . &&
+ git commit -m B1 &&
+
+ git checkout -b B2 &&
+ git rm -rf :^.gitmodules :^k &&
+ mkdir b d f &&
+ echo a >a &&
+ echo b/b >b/b &&
+ test_ln_s_add b c &&
+ echo d/d >d/d &&
+ git submodule add ../sub e &&
+ echo f/f >f/f &&
+ test_ln_s_add b g &&
+ git submodule add ../sub h &&
+
+ echo "B2 i" >i &&
+ test_ln_s_add b j &&
+ git -C k checkout B2 &&
+ mkdir m &&
+ echo m/m >m/m &&
+
+ git add . &&
+ git commit -m B2 &&
+
+ git checkout --recurse-submodules B1
+ )
+'
+
+for mode in sequential parallel sequential-fallback
+do
+ case $mode in
+ sequential) workers=1 threshold=0 expected_workers=0 ;;
+ parallel) workers=2 threshold=0 expected_workers=2 ;;
+ sequential-fallback) workers=2 threshold=100 expected_workers=0 ;;
+ esac
+
+ test_expect_success "$mode checkout" '
+ repo=various_$mode &&
+ cp -R -P various $repo &&
+
+ # The just copied files have more recent timestamps than their
+ # associated index entries. So refresh the cached timestamps
+ # to avoid an "entry not up-to-date" error from `git checkout`.
+ # We only have to do this for the submodules as `git checkout`
+ # will already refresh the superproject index before performing
+ # the up-to-date check.
+ #
+ git -C $repo submodule foreach "git update-index --refresh" &&
+
+ set_checkout_config $workers $threshold &&
+ test_checkout_workers $expected_workers \
+ git -C $repo checkout --recurse-submodules B2 &&
+ verify_checkout $repo
+ '
+done
+
+for mode in parallel sequential-fallback
+do
+ case $mode in
+ parallel) workers=2 threshold=0 expected_workers=2 ;;
+ sequential-fallback) workers=2 threshold=100 expected_workers=0 ;;
+ esac
+
+ test_expect_success "$mode checkout on clone" '
+ repo=various_${mode}_clone &&
+ set_checkout_config $workers $threshold &&
+ test_checkout_workers $expected_workers \
+ git clone --recurse-submodules --branch B2 various $repo &&
+ verify_checkout $repo
+ '
+done
+
+# Just to be paranoid, actually compare the working trees' contents directly.
+test_expect_success 'compare the working trees' '
+ rm -rf various_*/.git &&
+ rm -rf various_*/*/.git &&
+
+ # We use `git diff` instead of `diff -r` because the latter would
+ # follow symlinks, and not all `diff` implementations support the
+ # `--no-dereference` option.
+ #
+ git diff --no-index various_sequential various_parallel &&
+ git diff --no-index various_sequential various_parallel_clone &&
+ git diff --no-index various_sequential various_sequential-fallback &&
+ git diff --no-index various_sequential various_sequential-fallback_clone
+'
+
+# Currently, each submodule is checked out in a separated child process, but
+# these subprocesses must also be able to use parallel checkout workers to
+# write the submodules' entries.
+test_expect_success 'submodules can use parallel checkout' '
+ set_checkout_config 2 0 &&
+ git init super &&
+ (
+ cd super &&
+ git init sub &&
+ test_commit -C sub A &&
+ test_commit -C sub B &&
+ git submodule add ./sub &&
+ git commit -m sub &&
+ rm sub/* &&
+ test_checkout_workers 2 git checkout --recurse-submodules .
+ )
+'
+
+test_expect_success 'parallel checkout respects --[no]-force' '
+ set_checkout_config 2 0 &&
+ git init dirty &&
+ (
+ cd dirty &&
+ mkdir D &&
+ test_commit D/F &&
+ test_commit F &&
+
+ rm -rf D &&
+ echo changed >D &&
+ echo changed >F.t &&
+
+ # We expect 0 workers because there is nothing to be done
+ test_checkout_workers 0 git checkout HEAD &&
+ test_path_is_file D &&
+ grep changed D &&
+ grep changed F.t &&
+
+ test_checkout_workers 2 git checkout --force HEAD &&
+ test_path_is_dir D &&
+ grep D/F D/F.t &&
+ grep F F.t
+ )
+'
+
+test_expect_success SYMLINKS 'parallel checkout checks for symlinks in leading dirs' '
+ set_checkout_config 2 0 &&
+ git init symlinks &&
+ (
+ cd symlinks &&
+ mkdir D untracked &&
+ # Commit 2 files to have enough work for 2 parallel workers
+ test_commit D/A &&
+ test_commit D/B &&
+ rm -rf D &&
+ ln -s untracked D &&
+
+ test_checkout_workers 2 git checkout --force HEAD &&
+ ! test -h D &&
+ grep D/A D/A.t &&
+ grep D/B D/B.t
+ )
+'
+
+test_done
diff --git a/t/t2081-parallel-checkout-collisions.sh b/t/t2081-parallel-checkout-collisions.sh
new file mode 100755
index 0000000000..f6fcfc0c1e
--- /dev/null
+++ b/t/t2081-parallel-checkout-collisions.sh
@@ -0,0 +1,162 @@
+#!/bin/sh
+
+test_description="path collisions during parallel checkout
+
+Parallel checkout must detect path collisions to:
+
+1) Avoid racily writing to different paths that represent the same file on disk.
+2) Report the colliding entries on clone.
+
+The tests in this file exercise parallel checkout's collision detection code in
+both these mechanics.
+"
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-parallel-checkout.sh"
+
+TEST_ROOT="$PWD"
+
+test_expect_success CASE_INSENSITIVE_FS 'setup' '
+ empty_oid=$(git hash-object -w --stdin </dev/null) &&
+ cat >objs <<-EOF &&
+ 100644 $empty_oid FILE_X
+ 100644 $empty_oid FILE_x
+ 100644 $empty_oid file_X
+ 100644 $empty_oid file_x
+ EOF
+ git update-index --index-info <objs &&
+ git commit -m "colliding files" &&
+ git tag basename_collision &&
+
+ write_script "$TEST_ROOT"/logger_script <<-\EOF
+ echo "$@" >>filter.log
+ EOF
+'
+
+test_workers_in_event_trace ()
+{
+ test $1 -eq $(grep ".event.:.child_start..*checkout--worker" $2 | wc -l)
+}
+
+test_expect_success CASE_INSENSITIVE_FS 'worker detects basename collision' '
+ GIT_TRACE2_EVENT="$(pwd)/trace" git \
+ -c checkout.workers=2 -c checkout.thresholdForParallelism=0 \
+ checkout . &&
+
+ test_workers_in_event_trace 2 trace &&
+ collisions=$(grep -i "category.:.pcheckout.,.key.:.collision/basename.,.value.:.file_x.}" trace | wc -l) &&
+ test $collisions -eq 3
+'
+
+test_expect_success CASE_INSENSITIVE_FS 'worker detects dirname collision' '
+ test_config filter.logger.smudge "\"$TEST_ROOT/logger_script\" %f" &&
+ empty_oid=$(git hash-object -w --stdin </dev/null) &&
+
+ # By setting a filter command to "a", we make it ineligible for parallel
+ # checkout, and thus it is checked out *first*. This way we can ensure
+ # that "A/B" and "A/C" will both collide with the regular file "a".
+ #
+ attr_oid=$(echo "a filter=logger" | git hash-object -w --stdin) &&
+
+ cat >objs <<-EOF &&
+ 100644 $empty_oid A/B
+ 100644 $empty_oid A/C
+ 100644 $empty_oid a
+ 100644 $attr_oid .gitattributes
+ EOF
+ git rm -rf . &&
+ git update-index --index-info <objs &&
+
+ rm -f trace filter.log &&
+ GIT_TRACE2_EVENT="$(pwd)/trace" git \
+ -c checkout.workers=2 -c checkout.thresholdForParallelism=0 \
+ checkout . &&
+
+ # Check that "a" (and only "a") was filtered
+ echo a >expected.log &&
+ test_cmp filter.log expected.log &&
+
+ # Check that it used the right number of workers and detected the collisions
+ test_workers_in_event_trace 2 trace &&
+ grep "category.:.pcheckout.,.key.:.collision/dirname.,.value.:.A/B.}" trace &&
+ grep "category.:.pcheckout.,.key.:.collision/dirname.,.value.:.A/C.}" trace
+'
+
+test_expect_success SYMLINKS,CASE_INSENSITIVE_FS 'do not follow symlinks colliding with leading dir' '
+ empty_oid=$(git hash-object -w --stdin </dev/null) &&
+ symlink_oid=$(echo "./e" | git hash-object -w --stdin) &&
+ mkdir e &&
+
+ cat >objs <<-EOF &&
+ 120000 $symlink_oid D
+ 100644 $empty_oid d/x
+ 100644 $empty_oid e/y
+ EOF
+ git rm -rf . &&
+ git update-index --index-info <objs &&
+
+ set_checkout_config 2 0 &&
+ test_checkout_workers 2 git checkout . &&
+ test_path_is_dir e &&
+ test_path_is_missing e/x
+'
+
+# The two following tests check that parallel checkout correctly reports
+# colliding entries on clone. The sequential code detects a collision by
+# calling lstat() before trying to open(O_CREAT) a file. (Note that this only
+# works for clone.) Then, to find the pair of a colliding item k, it searches
+# cache_entry[0, k-1]. This is not sufficient in parallel checkout because:
+#
+# - A colliding file may be created between the lstat() and open() calls;
+# - A colliding entry might appear in the second half of the cache_entry array.
+#
+test_expect_success CASE_INSENSITIVE_FS 'collision report on clone (w/ racy file creation)' '
+ git reset --hard basename_collision &&
+ set_checkout_config 2 0 &&
+ test_checkout_workers 2 git clone . clone-repo 2>stderr &&
+
+ grep FILE_X stderr &&
+ grep FILE_x stderr &&
+ grep file_X stderr &&
+ grep file_x stderr &&
+ grep "the following paths have collided" stderr
+'
+
+# This test ensures that the collision report code is correctly looking for
+# colliding peers in the second half of the cache_entry array. This is done by
+# defining a smudge command for the *last* array entry, which makes it
+# non-eligible for parallel-checkout. Thus, it is checked out *first*, before
+# spawning the workers.
+#
+# Note: this test doesn't work on Windows because, on this system, the
+# collision report code uses strcmp() to find the colliding pairs when
+# core.ignoreCase is false. And we need this setting for this test so that only
+# 'file_x' matches the pattern of the filter attribute. But the test works on
+# OSX, where the colliding pairs are found using inode.
+#
+test_expect_success CASE_INSENSITIVE_FS,!MINGW,!CYGWIN \
+ 'collision report on clone (w/ colliding peer after the detected entry)' '
+
+ test_config_global filter.logger.smudge "\"$TEST_ROOT/logger_script\" %f" &&
+ git reset --hard basename_collision &&
+ echo "file_x filter=logger" >.gitattributes &&
+ git add .gitattributes &&
+ git commit -m "filter for file_x" &&
+
+ rm -rf clone-repo &&
+ set_checkout_config 2 0 &&
+ test_checkout_workers 2 \
+ git -c core.ignoreCase=false clone . clone-repo 2>stderr &&
+
+ grep FILE_X stderr &&
+ grep FILE_x stderr &&
+ grep file_X stderr &&
+ grep file_x stderr &&
+ grep "the following paths have collided" stderr &&
+
+ # Check that only "file_x" was filtered
+ echo file_x >expected.log &&
+ test_cmp clone-repo/filter.log expected.log
+'
+
+test_done
diff --git a/t/t2082-parallel-checkout-attributes.sh b/t/t2082-parallel-checkout-attributes.sh
new file mode 100755
index 0000000000..2525457961
--- /dev/null
+++ b/t/t2082-parallel-checkout-attributes.sh
@@ -0,0 +1,194 @@
+#!/bin/sh
+
+test_description='parallel-checkout: attributes
+
+Verify that parallel-checkout correctly creates files that require
+conversions, as specified in .gitattributes. The main point here is
+to check that the conv_attr data is correctly sent to the workers
+and that it contains sufficient information to smudge files
+properly (without access to the index or attribute stack).
+'
+
+TEST_NO_CREATE_REPO=1
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-parallel-checkout.sh"
+. "$TEST_DIRECTORY/lib-encoding.sh"
+
+test_expect_success 'parallel-checkout with ident' '
+ set_checkout_config 2 0 &&
+ git init ident &&
+ (
+ cd ident &&
+ echo "A ident" >.gitattributes &&
+ echo "\$Id\$" >A &&
+ echo "\$Id\$" >B &&
+ git add -A &&
+ git commit -m id &&
+
+ rm A B &&
+ test_checkout_workers 2 git reset --hard &&
+ hexsz=$(test_oid hexsz) &&
+ grep -E "\\\$Id: [0-9a-f]{$hexsz} \\\$" A &&
+ grep "\\\$Id\\\$" B
+ )
+'
+
+test_expect_success 'parallel-checkout with re-encoding' '
+ set_checkout_config 2 0 &&
+ git init encoding &&
+ (
+ cd encoding &&
+ echo text >utf8-text &&
+ write_utf16 <utf8-text >utf16-text &&
+
+ echo "A working-tree-encoding=UTF-16" >.gitattributes &&
+ cp utf16-text A &&
+ cp utf8-text B &&
+ git add A B .gitattributes &&
+ git commit -m encoding &&
+
+ # Check that A is stored in UTF-8
+ git cat-file -p :A >A.internal &&
+ test_cmp_bin utf8-text A.internal &&
+
+ rm A B &&
+ test_checkout_workers 2 git checkout A B &&
+
+ # Check that A (and only A) is re-encoded during checkout
+ test_cmp_bin utf16-text A &&
+ test_cmp_bin utf8-text B
+ )
+'
+
+test_expect_success 'parallel-checkout with eol conversions' '
+ set_checkout_config 2 0 &&
+ git init eol &&
+ (
+ cd eol &&
+ printf "multi\r\nline\r\ntext" >crlf-text &&
+ printf "multi\nline\ntext" >lf-text &&
+
+ git config core.autocrlf false &&
+ echo "A eol=crlf" >.gitattributes &&
+ cp crlf-text A &&
+ cp lf-text B &&
+ git add A B .gitattributes &&
+ git commit -m eol &&
+
+ # Check that A is stored with LF format
+ git cat-file -p :A >A.internal &&
+ test_cmp_bin lf-text A.internal &&
+
+ rm A B &&
+ test_checkout_workers 2 git checkout A B &&
+
+ # Check that A (and only A) is converted to CRLF during checkout
+ test_cmp_bin crlf-text A &&
+ test_cmp_bin lf-text B
+ )
+'
+
+# Entries that require an external filter are not eligible for parallel
+# checkout. Check that both the parallel-eligible and non-eligible entries are
+# properly writen in a single checkout operation.
+#
+test_expect_success 'parallel-checkout and external filter' '
+ set_checkout_config 2 0 &&
+ git init filter &&
+ (
+ cd filter &&
+ write_script <<-\EOF rot13.sh &&
+ tr \
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" \
+ "nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM"
+ EOF
+
+ git config filter.rot13.clean "\"$(pwd)/rot13.sh\"" &&
+ git config filter.rot13.smudge "\"$(pwd)/rot13.sh\"" &&
+ git config filter.rot13.required true &&
+
+ echo abcd >original &&
+ echo nopq >rot13 &&
+
+ echo "A filter=rot13" >.gitattributes &&
+ cp original A &&
+ cp original B &&
+ cp original C &&
+ git add A B C .gitattributes &&
+ git commit -m filter &&
+
+ # Check that A (and only A) was cleaned
+ git cat-file -p :A >A.internal &&
+ test_cmp rot13 A.internal &&
+ git cat-file -p :B >B.internal &&
+ test_cmp original B.internal &&
+ git cat-file -p :C >C.internal &&
+ test_cmp original C.internal &&
+
+ rm A B C *.internal &&
+ test_checkout_workers 2 git checkout A B C &&
+
+ # Check that A (and only A) was smudged during checkout
+ test_cmp original A &&
+ test_cmp original B &&
+ test_cmp original C
+ )
+'
+
+# The delayed queue is independent from the parallel queue, and they should be
+# able to work together in the same checkout process.
+#
+test_expect_success PERL 'parallel-checkout and delayed checkout' '
+ write_script rot13-filter.pl "$PERL_PATH" \
+ <"$TEST_DIRECTORY"/t0021/rot13-filter.pl &&
+
+ test_config_global filter.delay.process \
+ "\"$(pwd)/rot13-filter.pl\" --always-delay \"$(pwd)/delayed.log\" clean smudge delay" &&
+ test_config_global filter.delay.required true &&
+
+ echo "abcd" >original &&
+ echo "nopq" >rot13 &&
+
+ git init delayed &&
+ (
+ cd delayed &&
+ echo "*.d filter=delay" >.gitattributes &&
+ cp ../original W.d &&
+ cp ../original X.d &&
+ cp ../original Y &&
+ cp ../original Z &&
+ git add -A &&
+ git commit -m delayed &&
+
+ # Check that *.d files were cleaned
+ git cat-file -p :W.d >W.d.internal &&
+ test_cmp W.d.internal ../rot13 &&
+ git cat-file -p :X.d >X.d.internal &&
+ test_cmp X.d.internal ../rot13 &&
+ git cat-file -p :Y >Y.internal &&
+ test_cmp Y.internal ../original &&
+ git cat-file -p :Z >Z.internal &&
+ test_cmp Z.internal ../original &&
+
+ rm *
+ ) &&
+
+ set_checkout_config 2 0 &&
+ test_checkout_workers 2 git -C delayed checkout -f &&
+ verify_checkout delayed &&
+
+ # Check that the *.d files got to the delay queue and were filtered
+ grep "smudge W.d .* \[DELAYED\]" delayed.log &&
+ grep "smudge X.d .* \[DELAYED\]" delayed.log &&
+ test_cmp delayed/W.d original &&
+ test_cmp delayed/X.d original &&
+
+ # Check that the parallel-eligible entries went to the right queue and
+ # were not filtered
+ ! grep "smudge Y .* \[DELAYED\]" delayed.log &&
+ ! grep "smudge Z .* \[DELAYED\]" delayed.log &&
+ test_cmp delayed/Y original &&
+ test_cmp delayed/Z original
+'
+
+test_done
diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh
index 1ec7cb57c7..516c95ea0e 100755
--- a/t/t3001-ls-files-others-exclude.sh
+++ b/t/t3001-ls-files-others-exclude.sh
@@ -292,6 +292,11 @@ EOF
test_cmp expect actual
'
+test_expect_success 'ls-files with "**" patterns and --directory' '
+ # Expectation same as previous test
+ git ls-files --directory -o -i --exclude "**/a.1" >actual &&
+ test_cmp expect actual
+'
test_expect_success 'ls-files with "**" patterns and no slashes' '
git ls-files -o -i --exclude "one**a.1" >actual &&
diff --git a/t/t3003-ls-files-exclude.sh b/t/t3003-ls-files-exclude.sh
index d5ec333131..c41c4f046a 100755
--- a/t/t3003-ls-files-exclude.sh
+++ b/t/t3003-ls-files-exclude.sh
@@ -29,11 +29,11 @@ test_expect_success 'add file to gitignore' '
'
check_all_output
-test_expect_success 'ls-files -i lists only tracked-but-ignored files' '
+test_expect_success 'ls-files -i -c lists only tracked-but-ignored files' '
echo content >other-file &&
git add other-file &&
echo file >expect &&
- git ls-files -i --exclude-standard >output &&
+ git ls-files -i -c --exclude-standard >output &&
test_cmp expect output
'
diff --git a/t/t3060-ls-files-with-tree.sh b/t/t3060-ls-files-with-tree.sh
index 52ed665fcd..b257c792a4 100755
--- a/t/t3060-ls-files-with-tree.sh
+++ b/t/t3060-ls-files-with-tree.sh
@@ -47,6 +47,12 @@ test_expect_success setup '
git add .
'
+test_expect_success 'usage' '
+ test_expect_code 128 git ls-files --with-tree=HEAD -u &&
+ test_expect_code 128 git ls-files --with-tree=HEAD -s &&
+ test_expect_code 128 git ls-files --recurse-submodules --with-tree=HEAD
+'
+
test_expect_success 'git ls-files --with-tree should succeed from subdir' '
# We have to run from a sub-directory to trigger prune_path
# Then we finally get to run our --with-tree test
@@ -60,4 +66,39 @@ test_expect_success \
'git ls-files --with-tree should add entries from named tree.' \
'test_cmp expected output'
+test_expect_success 'no duplicates in --with-tree output' '
+ git ls-files --with-tree=HEAD >actual &&
+ sort -u actual >expected &&
+ test_cmp expected actual
+'
+
+test_expect_success 'setup: output in a conflict' '
+ test_create_repo conflict &&
+ test_commit -C conflict BASE file &&
+ test_commit -C conflict A file foo &&
+ git -C conflict reset --hard BASE &&
+ test_commit -C conflict B file bar
+'
+
+test_expect_success 'output in a conflict' '
+ test_must_fail git -C conflict merge A B &&
+ cat >expected <<-\EOF &&
+ file
+ file
+ file
+ file
+ EOF
+ git -C conflict ls-files --with-tree=HEAD >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'output with removed .git/index' '
+ cat >expected <<-\EOF &&
+ file
+ EOF
+ rm conflict/.git/index &&
+ git -C conflict ls-files --with-tree=HEAD >actual &&
+ test_cmp expected actual
+'
+
test_done
diff --git a/t/t3070-wildmatch.sh b/t/t3070-wildmatch.sh
index 891d4d7cb9..56ea4bda13 100755
--- a/t/t3070-wildmatch.sh
+++ b/t/t3070-wildmatch.sh
@@ -4,6 +4,11 @@ test_description='wildmatch tests'
. ./test-lib.sh
+# Disable expensive chain-lint tests; all of the tests in this script
+# are variants of a few trivial test-tool invocations, and there are a lot of
+# them.
+GIT_TEST_CHAIN_LINT_HARDER_DEFAULT=0
+
should_create_test_file() {
file=$1
diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh
index 1b26c4c2ef..e30bc48a29 100755
--- a/t/t3206-range-diff.sh
+++ b/t/t3206-range-diff.sh
@@ -521,6 +521,30 @@ test_expect_success 'format-patch --range-diff as commentary' '
grep "> 1: .* new message" 0001-*
'
+test_expect_success 'format-patch --range-diff reroll-count with a non-integer' '
+ git format-patch --range-diff=HEAD~1 -v2.9 HEAD~1 >actual &&
+ test_when_finished "rm v2.9-0001-*" &&
+ test_line_count = 1 actual &&
+ test_i18ngrep "^Range-diff:$" v2.9-0001-* &&
+ grep "> 1: .* new message" v2.9-0001-*
+'
+
+test_expect_success 'format-patch --range-diff reroll-count with a integer' '
+ git format-patch --range-diff=HEAD~1 -v2 HEAD~1 >actual &&
+ test_when_finished "rm v2-0001-*" &&
+ test_line_count = 1 actual &&
+ test_i18ngrep "^Range-diff ..* v1:$" v2-0001-* &&
+ grep "> 1: .* new message" v2-0001-*
+'
+
+test_expect_success 'format-patch --range-diff with v0' '
+ git format-patch --range-diff=HEAD~1 -v0 HEAD~1 >actual &&
+ test_when_finished "rm v0-0001-*" &&
+ test_line_count = 1 actual &&
+ test_i18ngrep "^Range-diff:$" v0-0001-* &&
+ grep "> 1: .* new message" v0-0001-*
+'
+
test_expect_success 'range-diff overrides diff.noprefix internally' '
git -c diff.noprefix=true range-diff HEAD^...
'
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 587b408063..0bb88aa982 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -388,22 +388,6 @@ test_expect_success 'rebase--merge.sh and --show-current-patch' '
)
'
-test_expect_success 'rebase -c rebase.useBuiltin=false warning' '
- expected="rebase.useBuiltin support has been removed" &&
-
- # Only warn when the legacy rebase is requested...
- test_must_fail git -c rebase.useBuiltin=false rebase 2>err &&
- test_i18ngrep "$expected" err &&
- test_must_fail env GIT_TEST_REBASE_USE_BUILTIN=false git rebase 2>err &&
- test_i18ngrep "$expected" err &&
-
- # ...not when we would have used the built-in anyway
- test_must_fail git -c rebase.useBuiltin=true rebase 2>err &&
- test_must_be_empty err &&
- test_must_fail env GIT_TEST_REBASE_USE_BUILTIN=true git rebase 2>err &&
- test_must_be_empty err
-'
-
test_expect_success 'switch to branch checked out here' '
git checkout main &&
git rebase main main
diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh
index 908016c2f8..78c27496d6 100755
--- a/t/t3415-rebase-autosquash.sh
+++ b/t/t3415-rebase-autosquash.sh
@@ -84,8 +84,7 @@ test_auto_squash () {
echo 1 >file1 &&
git add -u &&
test_tick &&
- git commit -m "squash! first" &&
-
+ git commit -m "squash! first" -m "extra para for first" &&
git tag $1 &&
test_tick &&
git rebase $2 -i HEAD^^^ &&
@@ -142,7 +141,7 @@ test_expect_success 'auto squash that matches 2 commits' '
echo 1 >file1 &&
git add -u &&
test_tick &&
- git commit -m "squash! first" &&
+ git commit -m "squash! first" -m "extra para for first" &&
git tag final-multisquash &&
test_tick &&
git rebase --autosquash -i HEAD~4 &&
@@ -195,7 +194,7 @@ test_expect_success 'auto squash that matches a sha1' '
git add -u &&
test_tick &&
oid=$(git rev-parse --short HEAD^) &&
- git commit -m "squash! $oid" &&
+ git commit -m "squash! $oid" -m "extra para" &&
git tag final-shasquash &&
test_tick &&
git rebase --autosquash -i HEAD^^^ &&
@@ -206,7 +205,8 @@ test_expect_success 'auto squash that matches a sha1' '
git cat-file blob HEAD^:file1 >actual &&
test_cmp expect actual &&
git cat-file commit HEAD^ >commit &&
- grep squash commit >actual &&
+ ! grep "squash" commit &&
+ grep "^extra para" commit >actual &&
test_line_count = 1 actual
'
@@ -216,7 +216,7 @@ test_expect_success 'auto squash that matches longer sha1' '
git add -u &&
test_tick &&
oid=$(git rev-parse --short=11 HEAD^) &&
- git commit -m "squash! $oid" &&
+ git commit -m "squash! $oid" -m "extra para" &&
git tag final-longshasquash &&
test_tick &&
git rebase --autosquash -i HEAD^^^ &&
@@ -227,7 +227,8 @@ test_expect_success 'auto squash that matches longer sha1' '
git cat-file blob HEAD^:file1 >actual &&
test_cmp expect actual &&
git cat-file commit HEAD^ >commit &&
- grep squash commit >actual &&
+ ! grep "squash" commit &&
+ grep "^extra para" commit >actual &&
test_line_count = 1 actual
'
@@ -236,7 +237,7 @@ test_auto_commit_flags () {
echo 1 >file1 &&
git add -u &&
test_tick &&
- git commit --$1 first-commit &&
+ git commit --$1 first-commit -m "extra para for first" &&
git tag final-commit-$1 &&
test_tick &&
git rebase --autosquash -i HEAD^^^ &&
@@ -264,11 +265,11 @@ test_auto_fixup_fixup () {
echo 1 >file1 &&
git add -u &&
test_tick &&
- git commit -m "$1! first" &&
+ git commit -m "$1! first" -m "extra para for first" &&
echo 2 >file1 &&
git add -u &&
test_tick &&
- git commit -m "$1! $2! first" &&
+ git commit -m "$1! $2! first" -m "second extra para for first" &&
git tag "final-$1-$2" &&
test_tick &&
(
@@ -329,12 +330,12 @@ test_expect_success 'autosquash with custom inst format' '
git add -u &&
test_tick &&
oid=$(git rev-parse --short HEAD^) &&
- git commit -m "squash! $oid" &&
+ git commit -m "squash! $oid" -m "extra para for first" &&
echo 1 >file1 &&
git add -u &&
test_tick &&
subject=$(git log -n 1 --format=%s HEAD~2) &&
- git commit -m "squash! $subject" &&
+ git commit -m "squash! $subject" -m "second extra para for first" &&
git tag final-squash-instFmt &&
test_tick &&
git rebase --autosquash -i HEAD~4 &&
@@ -345,8 +346,9 @@ test_expect_success 'autosquash with custom inst format' '
git cat-file blob HEAD^:file1 >actual &&
test_cmp expect actual &&
git cat-file commit HEAD^ >commit &&
- grep squash commit >actual &&
- test_line_count = 2 actual
+ ! grep "squash" commit &&
+ grep first commit >actual &&
+ test_line_count = 3 actual
'
test_expect_success 'autosquash with empty custom instructionFormat' '
diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh
index 0838f4e798..f4c2ee02bc 100755
--- a/t/t3418-rebase-continue.sh
+++ b/t/t3418-rebase-continue.sh
@@ -282,12 +282,35 @@ test_expect_success '--reschedule-failed-exec' '
test_i18ngrep "has been rescheduled" err
'
-test_expect_success 'rebase.reschedulefailedexec only affects `rebase -i`' '
- test_config rebase.reschedulefailedexec true &&
+test_expect_success 'rebase.rescheduleFailedExec only affects `rebase -i`' '
+ test_config rebase.rescheduleFailedExec true &&
test_must_fail git rebase -x false HEAD^ &&
grep "^exec false" .git/rebase-merge/git-rebase-todo &&
git rebase --abort &&
git rebase HEAD^
'
+test_expect_success 'rebase.rescheduleFailedExec=true & --no-reschedule-failed-exec' '
+ test_when_finished "git rebase --abort" &&
+ test_config rebase.rescheduleFailedExec true &&
+ test_must_fail git rebase -x false --no-reschedule-failed-exec HEAD~2 &&
+ test_must_fail git rebase --continue 2>err &&
+ ! grep "has been rescheduled" err
+'
+
+test_expect_success 'new rebase.rescheduleFailedExec=true setting in an ongoing rebase is ignored' '
+ test_when_finished "git rebase --abort" &&
+ test_must_fail git rebase -x false HEAD~2 &&
+ test_config rebase.rescheduleFailedExec true &&
+ test_must_fail git rebase --continue 2>err &&
+ ! grep "has been rescheduled" err
+'
+
+test_expect_success 'there is no --no-reschedule-failed-exec in an ongoing rebase' '
+ test_when_finished "git rebase --abort" &&
+ test_must_fail git rebase -x false HEAD~2 &&
+ test_expect_code 129 git rebase --continue --no-reschedule-failed-exec &&
+ test_expect_code 129 git rebase --edit-todo --no-reschedule-failed-exec
+'
+
test_done
diff --git a/t/t3437-rebase-fixup-options.sh b/t/t3437-rebase-fixup-options.sh
new file mode 100755
index 0000000000..c023fefd68
--- /dev/null
+++ b/t/t3437-rebase-fixup-options.sh
@@ -0,0 +1,211 @@
+#!/bin/sh
+#
+# Copyright (c) 2018 Phillip Wood
+#
+
+test_description='git rebase interactive fixup options
+
+This test checks the "fixup [-C|-c]" command of rebase interactive.
+In addition to amending the contents of the commit, "fixup -C"
+replaces the original commit message with the message of the fixup
+commit. "fixup -c" also replaces the original message, but opens the
+editor to allow the user to edit the message before committing. Similar
+to the "fixup" command that works with "fixup!", "fixup -C" works with
+"amend!" upon --autosquash.
+'
+
+. ./test-lib.sh
+
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+EMPTY=""
+
+# test_commit_message <rev> -m <msg>
+# test_commit_message <rev> <path>
+# Verify that the commit message of <rev> matches
+# <msg> or the content of <path>.
+test_commit_message () {
+ git show --no-patch --pretty=format:%B "$1" >actual &&
+ case "$2" in
+ -m)
+ echo "$3" >expect &&
+ test_cmp expect actual ;;
+ *)
+ test_cmp "$2" actual ;;
+ esac
+}
+
+get_author () {
+ rev="$1" &&
+ git log -1 --pretty=format:"%an %ae %at" "$rev"
+}
+
+test_expect_success 'setup' '
+ cat >message <<-EOF &&
+ amend! B
+ $EMPTY
+ new subject
+ $EMPTY
+ new
+ body
+ EOF
+
+ test_commit A A &&
+ test_commit B B &&
+ get_author HEAD >expected-author &&
+ ORIG_AUTHOR_NAME="$GIT_AUTHOR_NAME" &&
+ ORIG_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" &&
+ GIT_AUTHOR_NAME="Amend Author" &&
+ GIT_AUTHOR_EMAIL="amend@example.com" &&
+ test_commit "$(cat message)" A A1 A1 &&
+ test_commit A2 A &&
+ test_commit A3 A &&
+ GIT_AUTHOR_NAME="$ORIG_AUTHOR_NAME" &&
+ GIT_AUTHOR_EMAIL="$ORIG_AUTHOR_EMAIL" &&
+ git checkout -b conflicts-branch A &&
+ test_commit conflicts A &&
+
+ set_fake_editor &&
+ git checkout -b branch B &&
+ echo B1 >B &&
+ test_tick &&
+ git commit --fixup=HEAD -a &&
+ git tag B1 &&
+ test_tick &&
+ FAKE_COMMIT_AMEND="edited 1" git commit --fixup=reword:B &&
+ test_tick &&
+ FAKE_COMMIT_AMEND="edited 2" git commit --fixup=reword:HEAD &&
+ echo B2 >B &&
+ test_tick &&
+ FAKE_COMMIT_AMEND="edited squash" git commit --squash=HEAD -a &&
+ git tag B2 &&
+ echo B3 >B &&
+ test_tick &&
+ FAKE_COMMIT_AMEND="edited 3" git commit -a --fixup=amend:HEAD^ &&
+ git tag B3 &&
+
+ GIT_AUTHOR_NAME="Rebase Author" &&
+ GIT_AUTHOR_EMAIL="rebase.author@example.com" &&
+ GIT_COMMITTER_NAME="Rebase Committer" &&
+ GIT_COMMITTER_EMAIL="rebase.committer@example.com"
+'
+
+test_expect_success 'simple fixup -C works' '
+ test_when_finished "test_might_fail git rebase --abort" &&
+ git checkout --detach A2 &&
+ FAKE_LINES="1 fixup_-C 2" git rebase -i B &&
+ test_cmp_rev HEAD^ B &&
+ test_cmp_rev HEAD^{tree} A2^{tree} &&
+ test_commit_message HEAD -m "A2"
+'
+
+test_expect_success 'simple fixup -c works' '
+ test_when_finished "test_might_fail git rebase --abort" &&
+ git checkout --detach A2 &&
+ git log -1 --pretty=format:%B >expected-fixup-message &&
+ test_write_lines "" "Modified A2" >>expected-fixup-message &&
+ FAKE_LINES="1 fixup_-c 2" \
+ FAKE_COMMIT_AMEND="Modified A2" \
+ git rebase -i B &&
+ test_cmp_rev HEAD^ B &&
+ test_cmp_rev HEAD^{tree} A2^{tree} &&
+ test_commit_message HEAD expected-fixup-message
+'
+
+test_expect_success 'fixup -C removes amend! from message' '
+ test_when_finished "test_might_fail git rebase --abort" &&
+ git checkout --detach A1 &&
+ git log -1 --pretty=format:%b >expected-message &&
+ FAKE_LINES="1 fixup_-C 2" git rebase -i A &&
+ test_cmp_rev HEAD^ A &&
+ test_cmp_rev HEAD^{tree} A1^{tree} &&
+ test_commit_message HEAD expected-message &&
+ get_author HEAD >actual-author &&
+ test_cmp expected-author actual-author
+'
+
+test_expect_success 'fixup -C with conflicts gives correct message' '
+ test_when_finished "test_might_fail git rebase --abort" &&
+ git checkout --detach A1 &&
+ git log -1 --pretty=format:%b >expected-message &&
+ test_write_lines "" "edited" >>expected-message &&
+ test_must_fail env FAKE_LINES="1 fixup_-C 2" git rebase -i conflicts &&
+ git checkout --theirs -- A &&
+ git add A &&
+ FAKE_COMMIT_AMEND=edited git rebase --continue &&
+ test_cmp_rev HEAD^ conflicts &&
+ test_cmp_rev HEAD^{tree} A1^{tree} &&
+ test_commit_message HEAD expected-message &&
+ get_author HEAD >actual-author &&
+ test_cmp expected-author actual-author
+'
+
+test_expect_success 'skipping fixup -C after fixup gives correct message' '
+ test_when_finished "test_might_fail git rebase --abort" &&
+ git checkout --detach A3 &&
+ test_must_fail env FAKE_LINES="1 fixup 2 fixup_-C 4" git rebase -i A &&
+ git reset --hard &&
+ FAKE_COMMIT_AMEND=edited git rebase --continue &&
+ test_commit_message HEAD -m "B"
+'
+
+test_expect_success 'sequence of fixup, fixup -C & squash --signoff works' '
+ git checkout --detach B3 &&
+ FAKE_LINES="1 fixup 2 fixup_-C 3 fixup_-C 4 squash 5 fixup_-C 6" \
+ FAKE_COMMIT_AMEND=squashed \
+ FAKE_MESSAGE_COPY=actual-squash-message \
+ git -c commit.status=false rebase -ik --signoff A &&
+ git diff-tree --exit-code --patch HEAD B3 -- &&
+ test_cmp_rev HEAD^ A &&
+ test_cmp "$TEST_DIRECTORY/t3437/expected-squash-message" \
+ actual-squash-message
+'
+
+test_expect_success 'first fixup -C commented out in sequence fixup fixup -C fixup -C' '
+ test_when_finished "test_might_fail git rebase --abort" &&
+ git checkout --detach B2~ &&
+ git log -1 --pretty=format:%b >expected-message &&
+ FAKE_LINES="1 fixup 2 fixup_-C 3 fixup_-C 4" git rebase -i A &&
+ test_cmp_rev HEAD^ A &&
+ test_commit_message HEAD expected-message
+'
+
+test_expect_success 'multiple fixup -c opens editor once' '
+ test_when_finished "test_might_fail git rebase --abort" &&
+ git checkout --detach A3 &&
+ git log -1 --pretty=format:%B >expected-message &&
+ test_write_lines "" "Modified-A3" >>expected-message &&
+ FAKE_COMMIT_AMEND="Modified-A3" \
+ FAKE_LINES="1 fixup_-C 2 fixup_-c 3 fixup_-c 4" \
+ EXPECT_HEADER_COUNT=4 \
+ git rebase -i A &&
+ test_cmp_rev HEAD^ A &&
+ get_author HEAD >actual-author &&
+ test_cmp expected-author actual-author &&
+ test_commit_message HEAD expected-message
+'
+
+test_expect_success 'sequence squash, fixup & fixup -c gives combined message' '
+ test_when_finished "test_might_fail git rebase --abort" &&
+ git checkout --detach A3 &&
+ FAKE_LINES="1 squash 2 fixup 3 fixup_-c 4" \
+ FAKE_MESSAGE_COPY=actual-combined-message \
+ git -c commit.status=false rebase -i A &&
+ test_cmp "$TEST_DIRECTORY/t3437/expected-combined-message" \
+ actual-combined-message &&
+ test_cmp_rev HEAD^ A
+'
+
+test_expect_success 'fixup -C works upon --autosquash with amend!' '
+ git checkout --detach B3 &&
+ FAKE_COMMIT_AMEND=squashed \
+ FAKE_MESSAGE_COPY=actual-squash-message \
+ git -c commit.status=false rebase -ik --autosquash \
+ --signoff A &&
+ git diff-tree --exit-code --patch HEAD B3 -- &&
+ test_cmp_rev HEAD^ A &&
+ test_cmp "$TEST_DIRECTORY/t3437/expected-squash-message" \
+ actual-squash-message
+'
+
+test_done
diff --git a/t/t3437/expected-combined-message b/t/t3437/expected-combined-message
new file mode 100644
index 0000000000..a26cfb2fa9
--- /dev/null
+++ b/t/t3437/expected-combined-message
@@ -0,0 +1,21 @@
+# This is a combination of 4 commits.
+# This is the 1st commit message:
+
+B
+
+# This is the commit message #2:
+
+# amend! B
+
+new subject
+
+new
+body
+
+# The commit message #3 will be skipped:
+
+# A2
+
+# This is the commit message #4:
+
+A3
diff --git a/t/t3437/expected-squash-message b/t/t3437/expected-squash-message
new file mode 100644
index 0000000000..ab2434f90e
--- /dev/null
+++ b/t/t3437/expected-squash-message
@@ -0,0 +1,51 @@
+# This is a combination of 6 commits.
+# The 1st commit message will be skipped:
+
+# B
+#
+# Signed-off-by: Rebase Committer <rebase.committer@example.com>
+
+# The commit message #2 will be skipped:
+
+# fixup! B
+
+# The commit message #3 will be skipped:
+
+# amend! B
+#
+# B
+#
+# edited 1
+#
+# Signed-off-by: Rebase Committer <rebase.committer@example.com>
+
+# This is the commit message #4:
+
+# amend! amend! B
+
+B
+
+edited 1
+
+edited 2
+
+Signed-off-by: Rebase Committer <rebase.committer@example.com>
+
+# This is the commit message #5:
+
+# squash! amend! amend! B
+
+edited squash
+
+# This is the commit message #6:
+
+# amend! amend! amend! B
+
+B
+
+edited 1
+
+edited 2
+
+edited 3
+squashed
diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh
index b76cb6de91..49010aa946 100755
--- a/t/t3510-cherry-pick-sequence.sh
+++ b/t/t3510-cherry-pick-sequence.sh
@@ -65,7 +65,7 @@ test_expect_success 'cherry-pick persists opts correctly' '
# gets interrupted, use a high-enough number that is larger
# than the number of parents of any commit we have created
mainline=4 &&
- test_expect_code 128 git cherry-pick -s -m $mainline --strategy=recursive -X patience -X ours initial..anotherpick &&
+ test_expect_code 128 git cherry-pick -s -m $mainline --strategy=recursive -X patience -X ours --edit initial..anotherpick &&
test_path_is_dir .git/sequencer &&
test_path_is_file .git/sequencer/head &&
test_path_is_file .git/sequencer/todo &&
@@ -84,6 +84,36 @@ test_expect_success 'cherry-pick persists opts correctly' '
ours
EOF
git config --file=.git/sequencer/opts --get-all options.strategy-option >actual &&
+ test_cmp expect actual &&
+ echo "true" >expect &&
+ git config --file=.git/sequencer/opts --get-all options.edit >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'revert persists opts correctly' '
+ pristine_detach initial &&
+ # to make sure that the session to revert a sequence
+ # gets interrupted, revert commits that are not in the history
+ # of HEAD.
+ test_expect_code 1 git revert -s --strategy=recursive -X patience -X ours --no-edit picked yetanotherpick &&
+ test_path_is_dir .git/sequencer &&
+ test_path_is_file .git/sequencer/head &&
+ test_path_is_file .git/sequencer/todo &&
+ test_path_is_file .git/sequencer/opts &&
+ echo "true" >expect &&
+ git config --file=.git/sequencer/opts --get-all options.signoff >actual &&
+ test_cmp expect actual &&
+ echo "recursive" >expect &&
+ git config --file=.git/sequencer/opts --get-all options.strategy >actual &&
+ test_cmp expect actual &&
+ cat >expect <<-\EOF &&
+ patience
+ ours
+ EOF
+ git config --file=.git/sequencer/opts --get-all options.strategy-option >actual &&
+ test_cmp expect actual &&
+ echo "false" >expect &&
+ git config --file=.git/sequencer/opts --get-all options.edit >actual &&
test_cmp expect actual
'
diff --git a/t/t3512-cherry-pick-submodule.sh b/t/t3512-cherry-pick-submodule.sh
index 822f2d4bfb..c657840db3 100755
--- a/t/t3512-cherry-pick-submodule.sh
+++ b/t/t3512-cherry-pick-submodule.sh
@@ -8,8 +8,11 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-submodule-update.sh
-KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
-KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
+if test "$GIT_TEST_MERGE_ALGORITHM" != ort
+then
+ KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
+ KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
+fi
test_submodule_switch "cherry-pick"
test_expect_success 'unrelated submodule/file conflict is ignored' '
diff --git a/t/t3513-revert-submodule.sh b/t/t3513-revert-submodule.sh
index a759f12cbb..74cd96e582 100755
--- a/t/t3513-revert-submodule.sh
+++ b/t/t3513-revert-submodule.sh
@@ -30,7 +30,10 @@ git_revert () {
git revert HEAD
}
-KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
+if test "$GIT_TEST_MERGE_ALGORITHM" != ort
+then
+ KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
+fi
test_submodule_switch_func "git_revert"
test_done
diff --git a/t/t3602-rm-sparse-checkout.sh b/t/t3602-rm-sparse-checkout.sh
new file mode 100755
index 0000000000..e9e9a15c74
--- /dev/null
+++ b/t/t3602-rm-sparse-checkout.sh
@@ -0,0 +1,78 @@
+#!/bin/sh
+
+test_description='git rm in sparse checked out working trees'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' "
+ mkdir -p sub/dir &&
+ touch a b c sub/d sub/dir/e &&
+ git add -A &&
+ 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:
+ EOF
+
+ cat >sparse_hint <<-EOF &&
+ hint: Disable or modify the sparsity rules if you intend to update such entries.
+ hint: Disable this message with \"git config advice.updateSparsePath false\"
+ EOF
+
+ echo b | cat sparse_error_header - >sparse_entry_b_error &&
+ cat sparse_entry_b_error sparse_hint >b_error_and_hint
+"
+
+for opt in "" -f --dry-run
+do
+ test_expect_success "rm${opt:+ $opt} does not remove sparse entries" '
+ git sparse-checkout set a &&
+ test_must_fail git rm $opt b 2>stderr &&
+ test_cmp b_error_and_hint stderr &&
+ git ls-files --error-unmatch b
+ '
+done
+
+test_expect_success 'recursive rm does not remove sparse entries' '
+ git reset --hard &&
+ git sparse-checkout set sub/dir &&
+ git rm -r sub &&
+ git status --porcelain -uno >actual &&
+ echo "D sub/dir/e" >expected &&
+ test_cmp expected actual
+'
+
+test_expect_success 'rm obeys advice.updateSparsePath' '
+ git reset --hard &&
+ git sparse-checkout set a &&
+ test_must_fail git -c advice.updateSparsePath=false rm b 2>stderr &&
+ test_cmp sparse_entry_b_error stderr
+'
+
+test_expect_success 'do not advice about sparse entries when they do not match the pathspec' '
+ git reset --hard &&
+ git sparse-checkout set a &&
+ test_must_fail git rm nonexistent 2>stderr &&
+ grep "fatal: pathspec .nonexistent. did not match any files" stderr &&
+ ! grep -F -f sparse_error_header stderr
+'
+
+test_expect_success 'do not warn about sparse entries when pathspec matches dense entries' '
+ git reset --hard &&
+ git sparse-checkout set a &&
+ git rm "[ba]" 2>stderr &&
+ test_must_be_empty stderr &&
+ git ls-files --error-unmatch b &&
+ test_must_fail git ls-files --error-unmatch a
+'
+
+test_expect_success 'do not warn about sparse entries with --ignore-unmatch' '
+ git reset --hard &&
+ git sparse-checkout set a &&
+ git rm --ignore-unmatch b 2>stderr &&
+ test_must_be_empty stderr &&
+ git ls-files --error-unmatch b
+'
+
+test_done
diff --git a/t/t3700-add.sh b/t/t3700-add.sh
index b3b122ff97..4086e1ebbc 100755
--- a/t/t3700-add.sh
+++ b/t/t3700-add.sh
@@ -196,6 +196,12 @@ test_expect_success 'git add --refresh with pathspec' '
grep baz actual
'
+test_expect_success 'git add --refresh correctly reports no match error' "
+ echo \"fatal: pathspec ':(icase)nonexistent' did not match any files\" >expect &&
+ test_must_fail git add --refresh ':(icase)nonexistent' 2>actual &&
+ test_cmp expect actual
+"
+
test_expect_success POSIXPERM,SANITY 'git add should fail atomically upon an unreadable file' '
git reset --hard &&
date >foo1 &&
@@ -343,6 +349,10 @@ test_expect_success 'git add --dry-run --ignore-missing of non-existing file out
test_cmp expect.err actual.err
'
+test_expect_success 'git add --dry-run --interactive should fail' '
+ test_must_fail git add --dry-run --interactive
+'
+
test_expect_success 'git add empty string should fail' '
test_must_fail git add ""
'
diff --git a/t/t3705-add-sparse-checkout.sh b/t/t3705-add-sparse-checkout.sh
new file mode 100755
index 0000000000..2b1fd0d0ee
--- /dev/null
+++ b/t/t3705-add-sparse-checkout.sh
@@ -0,0 +1,155 @@
+#!/bin/sh
+
+test_description='git add in sparse checked out working trees'
+
+. ./test-lib.sh
+
+SPARSE_ENTRY_BLOB=""
+
+# Optionally take a printf format string to write to the sparse_entry file
+setup_sparse_entry () {
+ # 'sparse_entry' might already be in the index with the skip-worktree
+ # bit set. Remove it so that the subsequent git add can update it.
+ git update-index --force-remove sparse_entry &&
+ if test $# -eq 1
+ then
+ printf "$1" >sparse_entry
+ else
+ >sparse_entry
+ fi &&
+ git add sparse_entry &&
+ git update-index --skip-worktree sparse_entry &&
+ SPARSE_ENTRY_BLOB=$(git rev-parse :sparse_entry)
+}
+
+test_sparse_entry_unchanged () {
+ echo "100644 $SPARSE_ENTRY_BLOB 0 sparse_entry" >expected &&
+ git ls-files --stage sparse_entry >actual &&
+ test_cmp expected actual
+}
+
+setup_gitignore () {
+ test_when_finished rm -f .gitignore &&
+ cat >.gitignore <<-EOF
+ *
+ !/sparse_entry
+ EOF
+}
+
+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:
+ EOF
+
+ cat >sparse_hint <<-EOF &&
+ hint: Disable or modify the sparsity rules if you intend to update such entries.
+ hint: Disable this message with \"git config advice.updateSparsePath false\"
+ EOF
+
+ echo sparse_entry | cat sparse_error_header - >sparse_entry_error &&
+ cat sparse_entry_error sparse_hint >error_and_hint
+"
+
+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_cmp error_and_hint stderr &&
+ test_sparse_entry_unchanged
+'
+
+test_expect_success 'git add -A does not remove sparse entries' '
+ setup_sparse_entry &&
+ rm sparse_entry &&
+ setup_gitignore &&
+ git add -A 2>stderr &&
+ test_must_be_empty stderr &&
+ test_sparse_entry_unchanged
+'
+
+test_expect_success 'git add . does not remove sparse entries' '
+ setup_sparse_entry &&
+ rm sparse_entry &&
+ setup_gitignore &&
+ test_must_fail git add . 2>stderr &&
+
+ cat sparse_error_header >expect &&
+ echo . >>expect &&
+ cat sparse_hint >>expect &&
+
+ test_cmp expect stderr &&
+ test_sparse_entry_unchanged
+'
+
+for opt in "" -f -u --ignore-removal --dry-run
+do
+ test_expect_success "git add${opt:+ $opt} does not update sparse entries" '
+ setup_sparse_entry &&
+ echo modified >sparse_entry &&
+ test_must_fail git add $opt sparse_entry 2>stderr &&
+ test_cmp error_and_hint stderr &&
+ test_sparse_entry_unchanged
+ '
+done
+
+test_expect_success 'git add --refresh does not update sparse entries' '
+ setup_sparse_entry &&
+ 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_cmp error_and_hint stderr &&
+ git ls-files --debug sparse_entry | grep mtime >after &&
+ test_cmp before after
+'
+
+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_cmp error_and_hint stderr &&
+ test_sparse_entry_unchanged &&
+ ! test -x sparse_entry
+'
+
+test_expect_success 'git add --renormalize does not update sparse entries' '
+ test_config core.autocrlf false &&
+ 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_cmp error_and_hint stderr &&
+ test_sparse_entry_unchanged
+'
+
+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_cmp error_and_hint stderr &&
+ test_sparse_entry_unchanged
+'
+
+test_expect_success 'do not advice about sparse entries when they do not match the pathspec' '
+ setup_sparse_entry &&
+ test_must_fail git add nonexistent 2>stderr &&
+ grep "fatal: pathspec .nonexistent. did not match any files" stderr &&
+ ! grep -F -f sparse_error_header stderr
+'
+
+test_expect_success 'do not warn when pathspec matches dense entries' '
+ setup_sparse_entry &&
+ echo modified >sparse_entry &&
+ >dense_entry &&
+ git add "*_entry" 2>stderr &&
+ test_must_be_empty stderr &&
+ test_sparse_entry_unchanged &&
+ git ls-files --error-unmatch dense_entry
+'
+
+test_expect_success 'add obeys advice.updateSparsePath' '
+ setup_sparse_entry &&
+ test_must_fail git -c advice.updateSparsePath=false add sparse_entry 2>stderr &&
+ test_cmp sparse_entry_error stderr
+
+'
+
+test_done
diff --git a/t/t3800-mktag.sh b/t/t3800-mktag.sh
index 60a666da59..6275c98523 100755
--- a/t/t3800-mktag.sh
+++ b/t/t3800-mktag.sh
@@ -17,7 +17,7 @@ check_verify_failure () {
grep '$2' message &&
if test '$3' != '--no-strict'
then
- test_must_fail git mktag --no-strict <tag.sig 2>message.no-strict &&xb
+ test_must_fail git mktag --no-strict <tag.sig 2>message.no-strict &&
grep '$2' message.no-strict
fi
"
diff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh
index d277a9f4b7..bfab245eb3 100755
--- a/t/t3900-i18n-commit.sh
+++ b/t/t3900-i18n-commit.sh
@@ -226,10 +226,6 @@ test_commit_autosquash_multi_encoding () {
git rev-list HEAD >actual &&
test_line_count = 3 actual &&
iconv -f $old -t UTF-8 "$TEST_DIRECTORY"/t3900/$msg >expect &&
- if test $flag = squash; then
- subject="$(head -1 expect)" &&
- printf "\nsquash! %s\n" "$subject" >>expect
- fi &&
git cat-file commit HEAD^ >raw &&
(sed "1,/^$/d" raw | iconv -f $new -t utf-8) >actual &&
test_cmp expect actual
diff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh
index 598b17f6d4..dd2cdcc114 100755
--- a/t/t3905-stash-include-untracked.sh
+++ b/t/t3905-stash-include-untracked.sh
@@ -297,4 +297,129 @@ test_expect_success 'stash -u with globs' '
test_path_is_missing untracked.txt
'
+test_expect_success 'stash show --include-untracked shows untracked files' '
+ git reset --hard &&
+ git clean -xf &&
+ >untracked &&
+ >tracked &&
+ git add tracked &&
+ empty_blob_oid=$(git rev-parse --short :tracked) &&
+ git stash -u &&
+
+ cat >expect <<-EOF &&
+ tracked | 0
+ untracked | 0
+ 2 files changed, 0 insertions(+), 0 deletions(-)
+ EOF
+ git stash show --include-untracked >actual &&
+ test_cmp expect actual &&
+ git stash show -u >actual &&
+ test_cmp expect actual &&
+ git stash show --no-include-untracked --include-untracked >actual &&
+ test_cmp expect actual &&
+ git stash show --only-untracked --include-untracked >actual &&
+ test_cmp expect actual &&
+ git -c stash.showIncludeUntracked=true stash show >actual &&
+ test_cmp expect actual &&
+
+ cat >expect <<-EOF &&
+ diff --git a/tracked b/tracked
+ new file mode 100644
+ index 0000000..$empty_blob_oid
+ diff --git a/untracked b/untracked
+ new file mode 100644
+ index 0000000..$empty_blob_oid
+ EOF
+ git stash show -p --include-untracked >actual &&
+ test_cmp expect actual &&
+ git stash show --include-untracked -p >actual &&
+ test_cmp expect actual &&
+ git -c stash.showIncludeUntracked=true stash show -p >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'stash show --only-untracked only shows untracked files' '
+ git reset --hard &&
+ git clean -xf &&
+ >untracked &&
+ >tracked &&
+ git add tracked &&
+ empty_blob_oid=$(git rev-parse --short :tracked) &&
+ git stash -u &&
+
+ cat >expect <<-EOF &&
+ untracked | 0
+ 1 file changed, 0 insertions(+), 0 deletions(-)
+ EOF
+ git stash show --only-untracked >actual &&
+ test_cmp expect actual &&
+ git stash show --no-include-untracked --only-untracked >actual &&
+ test_cmp expect actual &&
+ git stash show --include-untracked --only-untracked >actual &&
+ test_cmp expect actual &&
+
+ cat >expect <<-EOF &&
+ diff --git a/untracked b/untracked
+ new file mode 100644
+ index 0000000..$empty_blob_oid
+ EOF
+ git stash show -p --only-untracked >actual &&
+ test_cmp expect actual &&
+ git stash show --only-untracked -p >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'stash show --no-include-untracked cancels --{include,only}-untracked' '
+ git reset --hard &&
+ git clean -xf &&
+ >untracked &&
+ >tracked &&
+ git add tracked &&
+ git stash -u &&
+
+ cat >expect <<-EOF &&
+ tracked | 0
+ 1 file changed, 0 insertions(+), 0 deletions(-)
+ EOF
+ git stash show --only-untracked --no-include-untracked >actual &&
+ test_cmp expect actual &&
+ git stash show --include-untracked --no-include-untracked >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'stash show --include-untracked errors on duplicate files' '
+ git reset --hard &&
+ git clean -xf &&
+ >tracked &&
+ git add tracked &&
+ tree=$(git write-tree) &&
+ i_commit=$(git commit-tree -p HEAD -m "index on any-branch" "$tree") &&
+ test_when_finished "rm -f untracked_index" &&
+ u_commit=$(
+ GIT_INDEX_FILE="untracked_index" &&
+ export GIT_INDEX_FILE &&
+ git update-index --add tracked &&
+ u_tree=$(git write-tree) &&
+ git commit-tree -m "untracked files on any-branch" "$u_tree"
+ ) &&
+ w_commit=$(git commit-tree -p HEAD -p "$i_commit" -p "$u_commit" -m "WIP on any-branch" "$tree") &&
+ test_must_fail git stash show --include-untracked "$w_commit" 2>err &&
+ test_i18ngrep "worktree and untracked commit have duplicate entries: tracked" err
+'
+
+test_expect_success 'stash show --{include,only}-untracked on stashes without untracked entries' '
+ git reset --hard &&
+ git clean -xf &&
+ >tracked &&
+ git add tracked &&
+ git stash &&
+
+ git stash show >expect &&
+ git stash show --include-untracked >actual &&
+ test_cmp expect actual &&
+
+ git stash show --only-untracked >actual &&
+ test_must_be_empty actual
+'
+
test_done
diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh
index 6cca8b84a6..87def81699 100755
--- a/t/t4013-diff-various.sh
+++ b/t/t4013-diff-various.sh
@@ -452,6 +452,37 @@ diff-tree --stat --compact-summary initial mode
diff-tree -R --stat --compact-summary initial mode
EOF
+test_expect_success 'log --diff-merges=on matches --diff-merges=separate' '
+ git log -p --diff-merges=separate master >result &&
+ process_diffs result >expected &&
+ git log -p --diff-merges=on master >result &&
+ process_diffs result >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'deny wrong log.diffMerges config' '
+ test_config log.diffMerges wrong-value &&
+ test_expect_code 128 git log
+'
+
+test_expect_success 'git config log.diffMerges first-parent' '
+ git log -p --diff-merges=first-parent master >result &&
+ process_diffs result >expected &&
+ test_config log.diffMerges first-parent &&
+ git log -p --diff-merges=on master >result &&
+ process_diffs result >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'git config log.diffMerges first-parent vs -m' '
+ git log -p --diff-merges=first-parent master >result &&
+ process_diffs result >expected &&
+ test_config log.diffMerges first-parent &&
+ git log -p -m master >result &&
+ process_diffs result >actual &&
+ test_cmp expected actual
+'
+
test_expect_success 'log -S requires an argument' '
test_must_fail git log -S
'
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index cdd3154e70..712d4b5ddf 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -386,6 +386,30 @@ test_expect_success 'reroll count (-v)' '
! grep -v "^Subject: \[PATCH v4 [0-3]/3\] " subjects
'
+test_expect_success 'reroll count (-v) with a fractional number' '
+ rm -fr patches &&
+ git format-patch -o patches --cover-letter -v4.4 main..side >list &&
+ ! grep -v "^patches/v4.4-000[0-3]-" list &&
+ sed -n -e "/^Subject: /p" $(cat list) >subjects &&
+ ! grep -v "^Subject: \[PATCH v4.4 [0-3]/3\] " subjects
+'
+
+test_expect_success 'reroll (-v) count with a non number' '
+ rm -fr patches &&
+ git format-patch -o patches --cover-letter -v4rev2 main..side >list &&
+ ! grep -v "^patches/v4rev2-000[0-3]-" list &&
+ sed -n -e "/^Subject: /p" $(cat list) >subjects &&
+ ! grep -v "^Subject: \[PATCH v4rev2 [0-3]/3\] " subjects
+'
+
+test_expect_success 'reroll (-v) count with a non-pathname character' '
+ rm -fr patches &&
+ git format-patch -o patches --cover-letter -v4---..././../--1/.2// main..side >list &&
+ ! grep -v "patches/v4-\.-\.-\.-1-\.2-000[0-3]-" list &&
+ sed -n -e "/^Subject: /p" $(cat list) >subjects &&
+ ! grep -v "^Subject: \[PATCH v4---\.\.\./\./\.\./--1/\.2// [0-3]/3\] " subjects
+'
+
check_threading () {
expect="$1" &&
shift &&
@@ -2255,6 +2279,16 @@ test_expect_success 'interdiff: reroll-count' '
test_i18ngrep "^Interdiff ..* v1:$" v2-0000-cover-letter.patch
'
+test_expect_success 'interdiff: reroll-count with a non-integer' '
+ git format-patch --cover-letter --interdiff=boop~2 -v2.2 -1 boop &&
+ test_i18ngrep "^Interdiff:$" v2.2-0000-cover-letter.patch
+'
+
+test_expect_success 'interdiff: reroll-count with a integer' '
+ git format-patch --cover-letter --interdiff=boop~2 -v2 -1 boop &&
+ test_i18ngrep "^Interdiff ..* v1:$" v2-0000-cover-letter.patch
+'
+
test_expect_success 'interdiff: solo-patch' '
cat >expect <<-\EOF &&
+fleep
diff --git a/t/t4018-diff-funcname.sh b/t/t4018-diff-funcname.sh
index 9675bc17db..740696c8f7 100755
--- a/t/t4018-diff-funcname.sh
+++ b/t/t4018-diff-funcname.sh
@@ -25,33 +25,26 @@ test_expect_success 'setup' '
echo B >B.java
'
+test_expect_success 'setup: test-tool userdiff' '
+ # Make sure additions to builtin_drivers are sorted
+ test_when_finished "rm builtin-drivers.sorted" &&
+ test-tool userdiff list-builtin-drivers >builtin-drivers &&
+ test_file_not_empty builtin-drivers &&
+ sort <builtin-drivers >builtin-drivers.sorted &&
+ test_cmp builtin-drivers.sorted builtin-drivers &&
+
+ # Ditto, but "custom" requires the .git directory and config
+ # to be setup and read.
+ test_when_finished "rm custom-drivers.sorted" &&
+ test-tool userdiff list-custom-drivers >custom-drivers &&
+ test_file_not_empty custom-drivers &&
+ sort <custom-drivers >custom-drivers.sorted &&
+ test_cmp custom-drivers.sorted custom-drivers
+'
+
diffpatterns="
- ada
- bash
- bibtex
- cpp
- csharp
- css
- dts
- elixir
- fortran
- fountain
- golang
- html
- java
- markdown
- matlab
- objc
- pascal
- perl
- php
- python
- ruby
- rust
- tex
- custom1
- custom2
- custom3
+ $(cat builtin-drivers)
+ $(cat custom-drivers)
"
for p in $diffpatterns
@@ -101,13 +94,7 @@ test_expect_success 'setup hunk header tests' '
# check each individual file
for i in $(git ls-files)
do
- if grep broken "$i" >/dev/null 2>&1
- then
- result=failure
- else
- result=success
- fi
- test_expect_$result "hunk header: $i" "
+ test_expect_success "hunk header: $i" "
git diff -U1 $i >actual &&
grep '@@ .* @@.*RIGHT' actual
"
diff --git a/t/t4018/README b/t/t4018/README
index 283e01cca1..2d25b2b4fc 100644
--- a/t/t4018/README
+++ b/t/t4018/README
@@ -7,9 +7,6 @@ at least two lines from the line that must appear in the hunk header.
The text that must appear in the hunk header must contain the word
"right", but in all upper-case, like in the title above.
-To mark a test case that highlights a malfunction, insert the word
-BROKEN in all lower-case somewhere in the file.
-
This text is a bit twisted and out of order, but it is itself a
test case for the default hunk header pattern. Know what you are doing
if you change it.
diff --git a/t/t4018/scheme-class b/t/t4018/scheme-class
new file mode 100644
index 0000000000..e5e07b43fb
--- /dev/null
+++ b/t/t4018/scheme-class
@@ -0,0 +1,7 @@
+(define book-class%
+ (class* () object% RIGHT
+ (field (pages 5))
+ (field (ChangeMe 5))
+ (define/public (letters)
+ (* pages 500))
+ (super-new)))
diff --git a/t/t4018/scheme-def b/t/t4018/scheme-def
new file mode 100644
index 0000000000..1e2673da96
--- /dev/null
+++ b/t/t4018/scheme-def
@@ -0,0 +1,4 @@
+(def (some-func x y z) RIGHT
+ (let ((a x)
+ (b y))
+ (ChangeMe a b)))
diff --git a/t/t4018/scheme-def-variant b/t/t4018/scheme-def-variant
new file mode 100644
index 0000000000..d857a61d64
--- /dev/null
+++ b/t/t4018/scheme-def-variant
@@ -0,0 +1,4 @@
+(defmethod {print point} RIGHT
+ (lambda (self)
+ (with ((point x y) self)
+ (printf "{ChangeMe x:~a y:~a}~n" x y))))
diff --git a/t/t4018/scheme-define-slash-public b/t/t4018/scheme-define-slash-public
new file mode 100644
index 0000000000..39a93a1600
--- /dev/null
+++ b/t/t4018/scheme-define-slash-public
@@ -0,0 +1,7 @@
+(define bar-class%
+ (class object%
+ (field (info 5))
+ (define/public (foo) RIGHT
+ (+ info 42)
+ (* info ChangeMe))
+ (super-new)))
diff --git a/t/t4018/scheme-define-syntax b/t/t4018/scheme-define-syntax
new file mode 100644
index 0000000000..7d5e99e0fc
--- /dev/null
+++ b/t/t4018/scheme-define-syntax
@@ -0,0 +1,8 @@
+(define-syntax define-test-suite RIGHT
+ (syntax-rules ()
+ ((_ suite-name (name test) ChangeMe ...)
+ (define suite-name
+ (let ((tests
+ `((name . ,test) ...)))
+ (lambda ()
+ (run-suite 'suite-name tests)))))))
diff --git a/t/t4018/scheme-define-variant b/t/t4018/scheme-define-variant
new file mode 100644
index 0000000000..911708854d
--- /dev/null
+++ b/t/t4018/scheme-define-variant
@@ -0,0 +1,4 @@
+(define* (some-func x y z) RIGHT
+ (let ((a x)
+ (b y))
+ (ChangeMe a b)))
diff --git a/t/t4018/scheme-library b/t/t4018/scheme-library
new file mode 100644
index 0000000000..82ea3df510
--- /dev/null
+++ b/t/t4018/scheme-library
@@ -0,0 +1,11 @@
+(library (my-helpers id-stuff) RIGHT
+ (export find-dup)
+ (import (ChangeMe))
+ (define (find-dup l)
+ (and (pair? l)
+ (let loop ((rest (cdr l)))
+ (cond
+ [(null? rest) (find-dup (cdr l))]
+ [(bound-identifier=? (car l) (car rest))
+ (car rest)]
+ [else (loop (cdr rest))])))))
diff --git a/t/t4018/scheme-local-define b/t/t4018/scheme-local-define
new file mode 100644
index 0000000000..bc6d8aebbe
--- /dev/null
+++ b/t/t4018/scheme-local-define
@@ -0,0 +1,4 @@
+(define (higher-order)
+ (define local-function RIGHT
+ (lambda (x)
+ (car "this is" "ChangeMe"))))
diff --git a/t/t4018/scheme-module b/t/t4018/scheme-module
new file mode 100644
index 0000000000..edfae0ebf7
--- /dev/null
+++ b/t/t4018/scheme-module
@@ -0,0 +1,6 @@
+(module A RIGHT
+ (export with-display-exception)
+ (extern (display-exception display-exception ChangeMe))
+ (def (with-display-exception thunk)
+ (with-catch (lambda (e) (display-exception e (current-error-port)) e)
+ thunk)))
diff --git a/t/t4018/scheme-top-level-define b/t/t4018/scheme-top-level-define
new file mode 100644
index 0000000000..624743c22b
--- /dev/null
+++ b/t/t4018/scheme-top-level-define
@@ -0,0 +1,4 @@
+(define (some-func x y z) RIGHT
+ (let ((a x)
+ (b y))
+ (ChangeMe a b)))
diff --git a/t/t4018/scheme-user-defined-define b/t/t4018/scheme-user-defined-define
new file mode 100644
index 0000000000..35fe7cc9bf
--- /dev/null
+++ b/t/t4018/scheme-user-defined-define
@@ -0,0 +1,6 @@
+(define-test-suite record\ case-tests RIGHT
+ (record-case-1 (lambda (fail)
+ (let ((a (make-foo 1 2)))
+ (record-case a
+ ((bar x) (ChangeMe))
+ ((foo a b) (+ a b)))))))
diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh
index 56f1e62a97..561c582d16 100755
--- a/t/t4034-diff-words.sh
+++ b/t/t4034-diff-words.sh
@@ -184,6 +184,11 @@ test_expect_success 'word diff with a regular expression' '
word_diff --color-words="[a-z]+"
'
+test_expect_success 'word diff with zero length matches' '
+ cp expect.letter-runs-are-words expect &&
+ word_diff --color-words="[a-z${LF}]*"
+'
+
test_expect_success 'set up a diff driver' '
git config diff.testdriver.wordRegex "[^[:space:]]" &&
cat <<-\EOF >.gitattributes
@@ -325,6 +330,7 @@ test_language_driver perl
test_language_driver php
test_language_driver python
test_language_driver ruby
+test_language_driver scheme
test_language_driver tex
test_expect_success 'word-diff with diff.sbe' '
diff --git a/t/t4034/scheme/expect b/t/t4034/scheme/expect
new file mode 100644
index 0000000000..496cd5de8c
--- /dev/null
+++ b/t/t4034/scheme/expect
@@ -0,0 +1,11 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index 74b6605..63b6ac4 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,6 +1,6 @@<RESET>
+(define (<RED>myfunc a b<RESET><GREEN>my-func first second<RESET>)
+ ; This is a <RED>really<RESET><GREEN>(moderately)<RESET> cool function.
+ (<RED>this\place<RESET><GREEN>that\place<RESET> (+ 3 4))
+ (define <RED>some-text<RESET><GREEN>|a greeting|<RESET> "hello")
+ (let ((c (<RED>+ a b<RESET><GREEN>add1 first<RESET>)))
+ (format "one more than the total is %d" (<RED>add1<RESET><GREEN>+<RESET> c <GREEN>second<RESET>))))
diff --git a/t/t4034/scheme/post b/t/t4034/scheme/post
new file mode 100644
index 0000000000..63b6ac4f87
--- /dev/null
+++ b/t/t4034/scheme/post
@@ -0,0 +1,6 @@
+(define (my-func first second)
+ ; This is a (moderately) cool function.
+ (that\place (+ 3 4))
+ (define |a greeting| "hello")
+ (let ((c (add1 first)))
+ (format "one more than the total is %d" (+ c second))))
diff --git a/t/t4034/scheme/pre b/t/t4034/scheme/pre
new file mode 100644
index 0000000000..74b6605357
--- /dev/null
+++ b/t/t4034/scheme/pre
@@ -0,0 +1,6 @@
+(define (myfunc a b)
+ ; This is a really cool function.
+ (this\place (+ 3 4))
+ (define some-text "hello")
+ (let ((c (+ a b)))
+ (format "one more than the total is %d" (add1 c))))
diff --git a/t/t4053-diff-no-index.sh b/t/t4053-diff-no-index.sh
index 0168946b63..3feadf0e35 100755
--- a/t/t4053-diff-no-index.sh
+++ b/t/t4053-diff-no-index.sh
@@ -16,6 +16,11 @@ test_expect_success 'setup' '
echo 1 >non/git/b
'
+test_expect_success 'git diff --no-index --exit-code' '
+ git diff --no-index --exit-code a/1 non/git/a &&
+ test_expect_code 1 git diff --no-index --exit-code a/1 a/2
+'
+
test_expect_success 'git diff --no-index directories' '
test_expect_code 1 git diff --no-index a b >cnt &&
test_line_count = 14 cnt
@@ -144,4 +149,59 @@ test_expect_success 'diff --no-index allows external diff' '
test_cmp expect actual
'
+test_expect_success 'diff --no-index normalizes mode: no changes' '
+ echo foo >x &&
+ cp x y &&
+ git diff --no-index x y >out &&
+ test_must_be_empty out
+'
+
+test_expect_success POSIXPERM 'diff --no-index normalizes mode: chmod +x' '
+ chmod +x y &&
+ cat >expected <<-\EOF &&
+ diff --git a/x b/y
+ old mode 100644
+ new mode 100755
+ EOF
+ test_expect_code 1 git diff --no-index x y >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success POSIXPERM 'diff --no-index normalizes: mode not like git mode' '
+ chmod 666 x &&
+ chmod 777 y &&
+ cat >expected <<-\EOF &&
+ diff --git a/x b/y
+ old mode 100644
+ new mode 100755
+ EOF
+ test_expect_code 1 git diff --no-index x y >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success POSIXPERM,SYMLINKS 'diff --no-index normalizes: mode not like git mode (symlink)' '
+ ln -s y z &&
+ X_OID=$(git hash-object --stdin <x) &&
+ Z_OID=$(printf y | git hash-object --stdin) &&
+ cat >expected <<-EOF &&
+ diff --git a/x b/x
+ deleted file mode 100644
+ index $X_OID..$ZERO_OID
+ --- a/x
+ +++ /dev/null
+ @@ -1 +0,0 @@
+ -foo
+ diff --git a/z b/z
+ new file mode 120000
+ index $ZERO_OID..$Z_OID
+ --- /dev/null
+ +++ b/z
+ @@ -0,0 +1 @@
+ +y
+ \ No newline at end of file
+ EOF
+ test_expect_code 1 git -c core.abbrev=no diff --no-index x z >actual &&
+ test_cmp expected actual
+'
+
test_done
diff --git a/t/t4108-apply-threeway.sh b/t/t4108-apply-threeway.sh
index d62db3fbe1..65147efdea 100755
--- a/t/t4108-apply-threeway.sh
+++ b/t/t4108-apply-threeway.sh
@@ -160,4 +160,74 @@ test_expect_success 'apply -3 with add/add conflict (dirty working tree)' '
test_cmp three.save three
'
+test_expect_success 'apply -3 with ambiguous repeating file' '
+ git reset --hard &&
+ test_write_lines 1 2 1 2 1 2 1 2 1 2 1 >one_two_repeat &&
+ git add one_two_repeat &&
+ git commit -m "init one" &&
+ test_write_lines 1 2 1 2 1 2 1 2 one 2 1 >one_two_repeat &&
+ git commit -a -m "change one" &&
+
+ git diff HEAD~ >Repeat.diff &&
+ git reset --hard HEAD~ &&
+
+ test_write_lines 1 2 1 2 1 2 one 2 1 2 one >one_two_repeat &&
+ git commit -a -m "change surrounding one" &&
+
+ git apply --index --3way Repeat.diff &&
+ test_write_lines 1 2 1 2 1 2 one 2 one 2 one >expect &&
+
+ test_cmp expect one_two_repeat
+'
+
+test_expect_success 'apply with --3way --cached clean apply' '
+ # Merging side should be similar to applying this patch
+ git diff ...side >P.diff &&
+
+ # The corresponding cleanly applied merge
+ git reset --hard &&
+ git checkout main~ &&
+ git merge --no-commit side &&
+ git ls-files -s >expect.ls &&
+
+ # should succeed
+ git reset --hard &&
+ git checkout main~ &&
+ git apply --cached --3way P.diff &&
+ git ls-files -s >actual.ls &&
+ print_sanitized_conflicted_diff >actual.diff &&
+
+ # The cache should resemble the corresponding merge
+ # (both files at stage #0)
+ test_cmp expect.ls actual.ls &&
+ # However the working directory should not change
+ >expect.diff &&
+ test_cmp expect.diff actual.diff
+'
+
+test_expect_success 'apply with --3way --cached and conflicts' '
+ # Merging side should be similar to applying this patch
+ git diff ...side >P.diff &&
+
+ # The corresponding conflicted merge
+ git reset --hard &&
+ git checkout main^0 &&
+ test_must_fail git merge --no-commit side &&
+ git ls-files -s >expect.ls &&
+
+ # should fail to apply
+ git reset --hard &&
+ git checkout main^0 &&
+ test_must_fail git apply --cached --3way P.diff &&
+ git ls-files -s >actual.ls &&
+ print_sanitized_conflicted_diff >actual.diff &&
+
+ # The cache should resemble the corresponding merge
+ # (one file at stage #0, one file at stages #1 #2 #3)
+ test_cmp expect.ls actual.ls &&
+ # However the working directory should not change
+ >expect.diff &&
+ test_cmp expect.diff actual.diff
+'
+
test_done
diff --git a/t/t4203-mailmap.sh b/t/t4203-mailmap.sh
index 93caf9a46d..d8e7374234 100755
--- a/t/t4203-mailmap.sh
+++ b/t/t4203-mailmap.sh
@@ -932,4 +932,35 @@ test_expect_success 'find top-level mailmap from subdir' '
test_cmp expect actual
'
+test_expect_success SYMLINKS 'set up symlink tests' '
+ git commit --allow-empty -m foo --author="Orig <orig@example.com>" &&
+ echo "New <new@example.com> <orig@example.com>" >map &&
+ rm -f .mailmap
+'
+
+test_expect_success SYMLINKS 'symlinks respected in mailmap.file' '
+ test_when_finished "rm symlink" &&
+ ln -s map symlink &&
+ git -c mailmap.file="$(pwd)/symlink" log -1 --format=%aE >actual &&
+ echo "new@example.com" >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success SYMLINKS 'symlinks respected in non-repo shortlog' '
+ git log -1 >input &&
+ test_when_finished "nongit rm .mailmap" &&
+ nongit ln -sf "$TRASH_DIRECTORY/map" .mailmap &&
+ nongit git shortlog -s <input >actual &&
+ echo " 1 New" >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success SYMLINKS 'symlinks not respected in-tree' '
+ test_when_finished "rm .mailmap" &&
+ ln -s map .mailmap &&
+ git log -1 --format=%aE >actual &&
+ echo "orig@example.com" >expect&&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh
index 85432b80ff..8272d94ce6 100755
--- a/t/t4205-log-pretty-formats.sh
+++ b/t/t4205-log-pretty-formats.sh
@@ -525,20 +525,25 @@ test_expect_success 'strbuf_utf8_replace() not producing NUL' '
! grep Q actual
'
-# ISO strict date format
-test_expect_success 'ISO and ISO-strict date formats display the same values' '
- git log --format=%ai%n%ci |
- sed -e "s/ /T/; s/ //; s/..\$/:&/" >expected &&
+# --date=[XXX] and corresponding %a[X] %c[X] format equivalency
+test_expect_success '--date=iso-strict %ad%cd is the same as %aI%cI' '
+ git log --format=%ad%n%cd --date=iso-strict >expected &&
git log --format=%aI%n%cI >actual &&
test_cmp expected actual
'
-test_expect_success 'short date' '
+test_expect_success '--date=short %ad%cd is the same as %as%cs' '
git log --format=%ad%n%cd --date=short >expected &&
git log --format=%as%n%cs >actual &&
test_cmp expected actual
'
+test_expect_success '--date=human %ad%cd is the same as %ah%ch' '
+ git log --format=%ad%n%cd --date=human >expected &&
+ git log --format=%ah%n%ch >actual &&
+ test_cmp expected actual
+'
+
# get new digests (with no abbreviations)
test_expect_success 'set up log decoration tests' '
head1=$(git rev-parse --verify HEAD~0) &&
@@ -962,4 +967,39 @@ test_expect_success 'log --pretty=reference is colored appropriately' '
test_cmp expect actual
'
+test_expect_success '%(describe) vs git describe' '
+ git log --format="%H" | while read hash
+ do
+ if desc=$(git describe $hash)
+ then
+ : >expect-contains-good
+ else
+ : >expect-contains-bad
+ fi &&
+ echo "$hash $desc"
+ done >expect &&
+ test_path_exists expect-contains-good &&
+ test_path_exists expect-contains-bad &&
+
+ git log --format="%H %(describe)" >actual 2>err &&
+ test_cmp expect actual &&
+ test_must_be_empty err
+'
+
+test_expect_success '%(describe:match=...) vs git describe --match ...' '
+ test_when_finished "git tag -d tag-match" &&
+ git tag -a -m tagged tag-match&&
+ git describe --match "*-match" >expect &&
+ git log -1 --format="%(describe:match=*-match)" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '%(describe:exclude=...) vs git describe --exclude ...' '
+ test_when_finished "git tag -d tag-exclude" &&
+ git tag -a -m tagged tag-exclude &&
+ git describe --exclude "*-exclude" >expect &&
+ git log -1 --format="%(describe:exclude=*-exclude)" >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t4258-am-quoted-cr.sh b/t/t4258-am-quoted-cr.sh
new file mode 100755
index 0000000000..fb5071f914
--- /dev/null
+++ b/t/t4258-am-quoted-cr.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+test_description='test am --quoted-cr=<action>'
+
+. ./test-lib.sh
+
+DATA="$TEST_DIRECTORY/t4258"
+
+test_expect_success 'setup' '
+ test_write_lines one two three >text &&
+ test_commit one text &&
+ test_write_lines one owt three >text &&
+ test_commit two text
+'
+
+test_expect_success 'am warn if quoted-cr is found' '
+ git reset --hard one &&
+ test_must_fail git am "$DATA/mbox" 2>err &&
+ grep "quoted CRLF detected" err
+'
+
+test_expect_success 'am --quoted-cr=strip' '
+ test_might_fail git am --abort &&
+ git reset --hard one &&
+ git am --quoted-cr=strip "$DATA/mbox" &&
+ git diff --exit-code HEAD two
+'
+
+test_expect_success 'am with config mailinfo.quotecr=strip' '
+ test_might_fail git am --abort &&
+ git reset --hard one &&
+ test_config mailinfo.quotedCr strip &&
+ git am "$DATA/mbox" &&
+ git diff --exit-code HEAD two
+'
+
+test_done
diff --git a/t/t4258/mbox b/t/t4258/mbox
new file mode 100644
index 0000000000..c62819f3d2
--- /dev/null
+++ b/t/t4258/mbox
@@ -0,0 +1,12 @@
+From: A U Thor <mail@example.com>
+To: list@example.org
+Subject: [PATCH v2] sample
+Date: Mon, 3 Aug 2020 22:40:55 +0700
+Message-Id: <msg-id@example.com>
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: base64
+
+VGhpcyBpcyBjb21taXQgbWVzc2FnZS4NCi0tLQ0KIHRleHQgfCAyICstDQogMSBmaWxlIGNoYW5n
+ZWQsIDEgaW5zZXJ0aW9uKCspLCAxIGRlbGV0aW9uKC0pDQoNCmRpZmYgLS1naXQgYS90ZXh0IGIv
+dGV4dA0KaW5kZXggNTYyNmFiZi4uZjcxOWVmZCAxMDA2NDQNCi0tLSBhL3RleHQNCisrKyBiL3Rl
+eHQNCkBAIC0xICsxIEBADQotb25lDQordHdvDQotLSANCjIuMzEuMQoK
diff --git a/t/t5001-archive-attr.sh b/t/t5001-archive-attr.sh
index e9aa97117a..712ae52299 100755
--- a/t/t5001-archive-attr.sh
+++ b/t/t5001-archive-attr.sh
@@ -128,4 +128,18 @@ test_expect_success 'export-subst' '
test_cmp substfile2 archive/substfile2
'
+test_expect_success 'export-subst expands %(describe) once' '
+ echo "\$Format:%(describe)\$" >substfile3 &&
+ echo "\$Format:%(describe)\$" >>substfile3 &&
+ echo "\$Format:%(describe)${LF}%(describe)\$" >substfile4 &&
+ git add substfile[34] &&
+ git commit -m export-subst-describe &&
+ git tag -m export-subst-describe export-subst-describe &&
+ git archive HEAD >archive-describe.tar &&
+ extract_tar_to_dir archive-describe &&
+ desc=$(git describe) &&
+ grep -F "$desc" archive-describe/substfile[34] >substituted &&
+ test_line_count = 1 substituted
+'
+
test_done
diff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh
index 147e616533..141b29f031 100755
--- a/t/t5100-mailinfo.sh
+++ b/t/t5100-mailinfo.sh
@@ -228,4 +228,44 @@ test_expect_success 'mailinfo handles unusual header whitespace' '
test_cmp expect actual
'
+check_quoted_cr_mail () {
+ mail="$1" && shift &&
+ git mailinfo -u "$@" "$mail.msg" "$mail.patch" \
+ <"$mail" >"$mail.info" 2>"$mail.err" &&
+ test_cmp "$mail-expected.msg" "$mail.msg" &&
+ test_cmp "$mail-expected.patch" "$mail.patch" &&
+ test_cmp "$DATA/quoted-cr-info" "$mail.info"
+}
+
+test_expect_success 'split base64 email with quoted-cr' '
+ mkdir quoted-cr &&
+ git mailsplit -oquoted-cr "$DATA/quoted-cr.mbox" >quoted-cr/last &&
+ test $(cat quoted-cr/last) = 2
+'
+
+test_expect_success 'mailinfo warn CR in base64 encoded email' '
+ sed -e "s/%%$//" -e "s/%%/$(printf \\015)/g" "$DATA/quoted-cr-msg" \
+ >quoted-cr/0001-expected.msg &&
+ sed "s/%%/$(printf \\015)/g" "$DATA/quoted-cr-msg" \
+ >quoted-cr/0002-expected.msg &&
+ sed -e "s/%%$//" -e "s/%%/$(printf \\015)/g" "$DATA/quoted-cr-patch" \
+ >quoted-cr/0001-expected.patch &&
+ sed "s/%%/$(printf \\015)/g" "$DATA/quoted-cr-patch" \
+ >quoted-cr/0002-expected.patch &&
+ check_quoted_cr_mail quoted-cr/0001 &&
+ test_must_be_empty quoted-cr/0001.err &&
+ check_quoted_cr_mail quoted-cr/0002 &&
+ grep "quoted CRLF detected" quoted-cr/0002.err &&
+ check_quoted_cr_mail quoted-cr/0001 --quoted-cr=nowarn &&
+ test_must_be_empty quoted-cr/0001.err &&
+ check_quoted_cr_mail quoted-cr/0002 --quoted-cr=nowarn &&
+ test_must_be_empty quoted-cr/0002.err &&
+ cp quoted-cr/0001-expected.msg quoted-cr/0002-expected.msg &&
+ cp quoted-cr/0001-expected.patch quoted-cr/0002-expected.patch &&
+ check_quoted_cr_mail quoted-cr/0001 --quoted-cr=strip &&
+ test_must_be_empty quoted-cr/0001.err &&
+ check_quoted_cr_mail quoted-cr/0002 --quoted-cr=strip &&
+ test_must_be_empty quoted-cr/0002.err
+'
+
test_done
diff --git a/t/t5100/quoted-cr-info b/t/t5100/quoted-cr-info
new file mode 100644
index 0000000000..dab2228b70
--- /dev/null
+++ b/t/t5100/quoted-cr-info
@@ -0,0 +1,5 @@
+Author: A U Thor
+Email: mail@example.com
+Subject: sample
+Date: Mon, 3 Aug 2020 22:40:55 +0700
+
diff --git a/t/t5100/quoted-cr-msg b/t/t5100/quoted-cr-msg
new file mode 100644
index 0000000000..89b05a0784
--- /dev/null
+++ b/t/t5100/quoted-cr-msg
@@ -0,0 +1,2 @@
+On different distro, %%pytest is suffixed with different patterns.%%
+%%
diff --git a/t/t5100/quoted-cr-patch b/t/t5100/quoted-cr-patch
new file mode 100644
index 0000000000..65b13eeef7
--- /dev/null
+++ b/t/t5100/quoted-cr-patch
@@ -0,0 +1,22 @@
+---%%
+ configure | 2 +-%%
+ 1 file changed, 1 insertion(+), 1 deletion(-)%%
+%%
+diff --git a/configure b/configure%%
+index db3538b3..f7c1c095 100755%%
+--- a/configure%%
++++ b/configure%%
+@@ -814,7 +814,7 @@ if [ $have_python3 -eq 1 ]; then%%
+ printf "%%Checking for python3 pytest (>= 3.0)... "%%
+ conf=$(mktemp)%%
+ printf "[pytest]\nminversion=3.0\n" > $conf%%
+- if pytest-3 -c $conf --version >/dev/null 2>&1; then%%
++ if "$python" -m pytest -c $conf --version >/dev/null 2>&1; then%%
+ printf "Yes.\n"%%
+ have_python3_pytest=1%%
+ else%%
+-- %%
+2.28.0%%
+_______________________________________________
+example mailing list -- list@example.org
+To unsubscribe send an email to list-leave@example.org
diff --git a/t/t5100/quoted-cr.mbox b/t/t5100/quoted-cr.mbox
new file mode 100644
index 0000000000..909021bb7a
--- /dev/null
+++ b/t/t5100/quoted-cr.mbox
@@ -0,0 +1,47 @@
+From nobody Mon Sep 17 00:00:00 2001
+From: A U Thor <mail@example.com>
+To: list@example.org
+Subject: [PATCH v2] sample
+Date: Mon, 3 Aug 2020 22:40:55 +0700
+Message-Id: <msg-id@example.com>
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: base64
+
+T24gZGlmZmVyZW50IGRpc3RybywgDXB5dGVzdCBpcyBzdWZmaXhlZCB3aXRoIGRpZmZlcmVudCBw
+YXR0ZXJucy4KCi0tLQogY29uZmlndXJlIHwgMiArLQogMSBmaWxlIGNoYW5nZWQsIDEgaW5zZXJ0
+aW9uKCspLCAxIGRlbGV0aW9uKC0pCgpkaWZmIC0tZ2l0IGEvY29uZmlndXJlIGIvY29uZmlndXJl
+CmluZGV4IGRiMzUzOGIzLi5mN2MxYzA5NSAxMDA3NTUKLS0tIGEvY29uZmlndXJlCisrKyBiL2Nv
+bmZpZ3VyZQpAQCAtODE0LDcgKzgxNCw3IEBAIGlmIFsgJGhhdmVfcHl0aG9uMyAtZXEgMSBdOyB0
+aGVuCiAgICAgcHJpbnRmICINQ2hlY2tpbmcgZm9yIHB5dGhvbjMgcHl0ZXN0ICg+PSAzLjApLi4u
+ICIKICAgICBjb25mPSQobWt0ZW1wKQogICAgIHByaW50ZiAiW3B5dGVzdF1cbm1pbnZlcnNpb249
+My4wXG4iID4gJGNvbmYKLSAgICBpZiBweXRlc3QtMyAtYyAkY29uZiAtLXZlcnNpb24gPi9kZXYv
+bnVsbCAyPiYxOyB0aGVuCisgICAgaWYgIiRweXRob24iIC1tIHB5dGVzdCAtYyAkY29uZiAtLXZl
+cnNpb24gPi9kZXYvbnVsbCAyPiYxOyB0aGVuCiAgICAgICAgIHByaW50ZiAiWWVzLlxuIgogICAg
+ICAgICBoYXZlX3B5dGhvbjNfcHl0ZXN0PTEKICAgICBlbHNlCi0tIAoyLjI4LjAKX19fX19fX19f
+X19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX18KZXhhbXBsZSBtYWlsaW5nIGxp
+c3QgLS0gbGlzdEBleGFtcGxlLm9yZwpUbyB1bnN1YnNjcmliZSBzZW5kIGFuIGVtYWlsIHRvIGxp
+c3QtbGVhdmVAZXhhbXBsZS5vcmcK
+
+From nobody Mon Sep 17 00:00:00 2001
+From: A U Thor <mail@example.com>
+To: list@example.org
+Subject: [PATCH v2] sample
+Date: Mon, 3 Aug 2020 22:40:55 +0700
+Message-Id: <msg-id2@example.com>
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: base64
+
+T24gZGlmZmVyZW50IGRpc3RybywgDXB5dGVzdCBpcyBzdWZmaXhlZCB3aXRoIGRpZmZlcmVudCBw
+YXR0ZXJucy4NCg0KLS0tDQogY29uZmlndXJlIHwgMiArLQ0KIDEgZmlsZSBjaGFuZ2VkLCAxIGlu
+c2VydGlvbigrKSwgMSBkZWxldGlvbigtKQ0KDQpkaWZmIC0tZ2l0IGEvY29uZmlndXJlIGIvY29u
+ZmlndXJlDQppbmRleCBkYjM1MzhiMy4uZjdjMWMwOTUgMTAwNzU1DQotLS0gYS9jb25maWd1cmUN
+CisrKyBiL2NvbmZpZ3VyZQ0KQEAgLTgxNCw3ICs4MTQsNyBAQCBpZiBbICRoYXZlX3B5dGhvbjMg
+LWVxIDEgXTsgdGhlbg0KICAgICBwcmludGYgIg1DaGVja2luZyBmb3IgcHl0aG9uMyBweXRlc3Qg
+KD49IDMuMCkuLi4gIg0KICAgICBjb25mPSQobWt0ZW1wKQ0KICAgICBwcmludGYgIltweXRlc3Rd
+XG5taW52ZXJzaW9uPTMuMFxuIiA+ICRjb25mDQotICAgIGlmIHB5dGVzdC0zIC1jICRjb25mIC0t
+dmVyc2lvbiA+L2Rldi9udWxsIDI+JjE7IHRoZW4NCisgICAgaWYgIiRweXRob24iIC1tIHB5dGVz
+dCAtYyAkY29uZiAtLXZlcnNpb24gPi9kZXYvbnVsbCAyPiYxOyB0aGVuDQogICAgICAgICBwcmlu
+dGYgIlllcy5cbiINCiAgICAgICAgIGhhdmVfcHl0aG9uM19weXRlc3Q9MQ0KICAgICBlbHNlDQot
+LSANCjIuMjguMA0KX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19f
+X18KZXhhbXBsZSBtYWlsaW5nIGxpc3QgLS0gbGlzdEBleGFtcGxlLm9yZwpUbyB1bnN1YnNjcmli
+ZSBzZW5kIGFuIGVtYWlsIHRvIGxpc3QtbGVhdmVAZXhhbXBsZS5vcmcK
diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh
index d586fdc7a9..5c5e53f0be 100755
--- a/t/t5300-pack-object.sh
+++ b/t/t5300-pack-object.sh
@@ -8,125 +8,91 @@ test_description='git pack-object
'
. ./test-lib.sh
-TRASH=$(pwd)
-
-test_expect_success \
- 'setup' \
- 'rm -f .git/index* &&
- perl -e "print \"a\" x 4096;" > a &&
- perl -e "print \"b\" x 4096;" > b &&
- perl -e "print \"c\" x 4096;" > c &&
- test-tool genrandom "seed a" 2097152 > a_big &&
- test-tool genrandom "seed b" 2097152 > b_big &&
- git update-index --add a a_big b b_big c &&
- cat c >d && echo foo >>d && git update-index --add d &&
- tree=$(git write-tree) &&
- commit=$(git commit-tree $tree </dev/null) && {
- echo $tree &&
- echo $commit &&
- git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\) .*/\\1/"
- } >obj-list && {
- git diff-tree --root -p $commit &&
- while read object
- do
- t=$(git cat-file -t $object) &&
- git cat-file $t $object || return 1
- done <obj-list
- } >expect'
-
-test_expect_success \
- 'pack without delta' \
- 'packname_1=$(git pack-objects --window=0 test-1 <obj-list)'
-
-test_expect_success \
- 'pack-objects with bogus arguments' \
- 'test_must_fail git pack-objects --window=0 test-1 blah blah <obj-list'
-
-rm -fr .git2
-mkdir .git2
-
-test_expect_success \
- 'unpack without delta' \
- "GIT_OBJECT_DIRECTORY=.git2/objects &&
- export GIT_OBJECT_DIRECTORY &&
- git init &&
- git unpack-objects -n <test-1-${packname_1}.pack &&
- git unpack-objects <test-1-${packname_1}.pack"
-
-unset GIT_OBJECT_DIRECTORY
-cd "$TRASH/.git2"
+test_expect_success 'setup' '
+ rm -f .git/index* &&
+ perl -e "print \"a\" x 4096;" >a &&
+ perl -e "print \"b\" x 4096;" >b &&
+ perl -e "print \"c\" x 4096;" >c &&
+ test-tool genrandom "seed a" 2097152 >a_big &&
+ test-tool genrandom "seed b" 2097152 >b_big &&
+ git update-index --add a a_big b b_big c &&
+ cat c >d && echo foo >>d && git update-index --add d &&
+ tree=$(git write-tree) &&
+ commit=$(git commit-tree $tree </dev/null) &&
+ {
+ echo $tree &&
+ echo $commit &&
+ git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\) .*/\\1/"
+ } >obj-list &&
+ {
+ git diff-tree --root -p $commit &&
+ while read object
+ do
+ t=$(git cat-file -t $object) &&
+ git cat-file $t $object || return 1
+ done <obj-list
+ } >expect
+'
-test_expect_success \
- 'check unpack without delta' \
- '(cd ../.git && find objects -type f -print) |
- while read path
- do
- cmp $path ../.git/$path || {
- echo $path differs.
- return 1
- }
- done'
-cd "$TRASH"
+# usage: check_deltas <stderr_from_pack_objects> <cmp_op> <nr_deltas>
+# e.g.: check_deltas stderr -gt 0
+check_deltas() {
+ deltas=$(perl -lne '/delta (\d+)/ and print $1' "$1") &&
+ shift &&
+ if ! test "$deltas" "$@"
+ then
+ echo >&2 "unexpected number of deltas (compared $delta $*)"
+ return 1
+ fi
+}
+
+test_expect_success 'pack without delta' '
+ packname_1=$(git pack-objects --progress --window=0 test-1 \
+ <obj-list 2>stderr) &&
+ check_deltas stderr = 0
+'
-test_expect_success \
- 'pack with REF_DELTA' \
- 'pwd &&
- packname_2=$(git pack-objects test-2 <obj-list)'
+test_expect_success 'pack-objects with bogus arguments' '
+ test_must_fail git pack-objects --window=0 test-1 blah blah <obj-list
+'
-rm -fr .git2
-mkdir .git2
+check_unpack () {
+ test_when_finished "rm -rf git2" &&
+ git init --bare git2 &&
+ git -C git2 unpack-objects -n <"$1".pack &&
+ git -C git2 unpack-objects <"$1".pack &&
+ (cd .git && find objects -type f -print) |
+ while read path
+ do
+ cmp git2/$path .git/$path || {
+ echo $path differs.
+ return 1
+ }
+ done
+}
+
+test_expect_success 'unpack without delta' '
+ check_unpack test-1-${packname_1}
+'
-test_expect_success \
- 'unpack with REF_DELTA' \
- 'GIT_OBJECT_DIRECTORY=.git2/objects &&
- export GIT_OBJECT_DIRECTORY &&
- git init &&
- git unpack-objects -n <test-2-${packname_2}.pack &&
- git unpack-objects <test-2-${packname_2}.pack'
-
-unset GIT_OBJECT_DIRECTORY
-cd "$TRASH/.git2"
-test_expect_success \
- 'check unpack with REF_DELTA' \
- '(cd ../.git && find objects -type f -print) |
- while read path
- do
- cmp $path ../.git/$path || {
- echo $path differs.
- return 1
- }
- done'
-cd "$TRASH"
+test_expect_success 'pack with REF_DELTA' '
+ packname_2=$(git pack-objects --progress test-2 <obj-list 2>stderr) &&
+ check_deltas stderr -gt 0
+'
-test_expect_success \
- 'pack with OFS_DELTA' \
- 'pwd &&
- packname_3=$(git pack-objects --delta-base-offset test-3 <obj-list)'
+test_expect_success 'unpack with REF_DELTA' '
+ check_unpack test-2-${packname_2}
+'
-rm -fr .git2
-mkdir .git2
+test_expect_success 'pack with OFS_DELTA' '
+ packname_3=$(git pack-objects --progress --delta-base-offset test-3 \
+ <obj-list 2>stderr) &&
+ check_deltas stderr -gt 0
+'
-test_expect_success \
- 'unpack with OFS_DELTA' \
- 'GIT_OBJECT_DIRECTORY=.git2/objects &&
- export GIT_OBJECT_DIRECTORY &&
- git init &&
- git unpack-objects -n <test-3-${packname_3}.pack &&
- git unpack-objects <test-3-${packname_3}.pack'
-
-unset GIT_OBJECT_DIRECTORY
-cd "$TRASH/.git2"
-test_expect_success \
- 'check unpack with OFS_DELTA' \
- '(cd ../.git && find objects -type f -print) |
- while read path
- do
- cmp $path ../.git/$path || {
- echo $path differs.
- return 1
- }
- done'
-cd "$TRASH"
+test_expect_success 'unpack with OFS_DELTA' '
+ check_unpack test-3-${packname_3}
+'
test_expect_success 'compare delta flavors' '
perl -e '\''
@@ -135,55 +101,33 @@ test_expect_success 'compare delta flavors' '
'\'' test-2-$packname_2.pack test-3-$packname_3.pack
'
-rm -fr .git2
-mkdir .git2
+check_use_objects () {
+ test_when_finished "rm -rf git2" &&
+ git init --bare git2 &&
+ cp "$1".pack "$1".idx git2/objects/pack &&
+ (
+ cd git2 &&
+ git diff-tree --root -p $commit &&
+ while read object
+ do
+ t=$(git cat-file -t $object) &&
+ git cat-file $t $object || exit 1
+ done
+ ) <obj-list >current &&
+ cmp expect current
+}
-test_expect_success \
- 'use packed objects' \
- 'GIT_OBJECT_DIRECTORY=.git2/objects &&
- export GIT_OBJECT_DIRECTORY &&
- git init &&
- cp test-1-${packname_1}.pack test-1-${packname_1}.idx .git2/objects/pack && {
- git diff-tree --root -p $commit &&
- while read object
- do
- t=$(git cat-file -t $object) &&
- git cat-file $t $object || return 1
- done <obj-list
- } >current &&
- cmp expect current'
+test_expect_success 'use packed objects' '
+ check_use_objects test-1-${packname_1}
+'
-test_expect_success \
- 'use packed deltified (REF_DELTA) objects' \
- 'GIT_OBJECT_DIRECTORY=.git2/objects &&
- export GIT_OBJECT_DIRECTORY &&
- rm -f .git2/objects/pack/test-* &&
- cp test-2-${packname_2}.pack test-2-${packname_2}.idx .git2/objects/pack && {
- git diff-tree --root -p $commit &&
- while read object
- do
- t=$(git cat-file -t $object) &&
- git cat-file $t $object || return 1
- done <obj-list
- } >current &&
- cmp expect current'
+test_expect_success 'use packed deltified (REF_DELTA) objects' '
+ check_use_objects test-2-${packname_2}
+'
-test_expect_success \
- 'use packed deltified (OFS_DELTA) objects' \
- 'GIT_OBJECT_DIRECTORY=.git2/objects &&
- export GIT_OBJECT_DIRECTORY &&
- rm -f .git2/objects/pack/test-* &&
- cp test-3-${packname_3}.pack test-3-${packname_3}.idx .git2/objects/pack && {
- git diff-tree --root -p $commit &&
- while read object
- do
- t=$(git cat-file -t $object) &&
- git cat-file $t $object || return 1
- done <obj-list
- } >current &&
- cmp expect current'
-
-unset GIT_OBJECT_DIRECTORY
+test_expect_success 'use packed deltified (OFS_DELTA) objects' '
+ check_use_objects test-3-${packname_3}
+'
test_expect_success 'survive missing objects/pack directory' '
(
@@ -427,7 +371,8 @@ test_expect_success 'index-pack --strict <pack> works in non-repo' '
test_path_is_file foo.idx
'
-test_expect_success !PTHREADS 'index-pack --threads=N or pack.threads=N warns when no pthreads' '
+test_expect_success !PTHREADS,!FAIL_PREREQS \
+ 'index-pack --threads=N or pack.threads=N warns when no pthreads' '
test_must_fail git index-pack --threads=2 2>err &&
grep ^warning: err >warnings &&
test_line_count = 1 warnings &&
@@ -445,7 +390,8 @@ test_expect_success !PTHREADS 'index-pack --threads=N or pack.threads=N warns wh
grep -F "no threads support, ignoring pack.threads" err
'
-test_expect_success !PTHREADS 'pack-objects --threads=N or pack.threads=N warns when no pthreads' '
+test_expect_success !PTHREADS,!FAIL_PREREQS \
+ 'pack-objects --threads=N or pack.threads=N warns when no pthreads' '
git pack-objects --threads=2 --stdout --all </dev/null >/dev/null 2>err &&
grep ^warning: err >warnings &&
test_line_count = 1 warnings &&
@@ -532,4 +478,144 @@ test_expect_success 'prefetch objects' '
test_line_count = 1 donelines
'
+test_expect_success 'setup for --stdin-packs tests' '
+ git init stdin-packs &&
+ (
+ cd stdin-packs &&
+
+ test_commit A &&
+ test_commit B &&
+ test_commit C &&
+
+ for id in A B C
+ do
+ git pack-objects .git/objects/pack/pack-$id \
+ --incremental --revs <<-EOF
+ refs/tags/$id
+ EOF
+ done &&
+
+ ls -la .git/objects/pack
+ )
+'
+
+test_expect_success '--stdin-packs with excluded packs' '
+ (
+ cd stdin-packs &&
+
+ PACK_A="$(basename .git/objects/pack/pack-A-*.pack)" &&
+ PACK_B="$(basename .git/objects/pack/pack-B-*.pack)" &&
+ PACK_C="$(basename .git/objects/pack/pack-C-*.pack)" &&
+
+ git pack-objects test --stdin-packs <<-EOF &&
+ $PACK_A
+ ^$PACK_B
+ $PACK_C
+ EOF
+
+ (
+ git show-index <$(ls .git/objects/pack/pack-A-*.idx) &&
+ git show-index <$(ls .git/objects/pack/pack-C-*.idx)
+ ) >expect.raw &&
+ git show-index <$(ls test-*.idx) >actual.raw &&
+
+ cut -d" " -f2 <expect.raw | sort >expect &&
+ cut -d" " -f2 <actual.raw | sort >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success '--stdin-packs is incompatible with --filter' '
+ (
+ cd stdin-packs &&
+ test_must_fail git pack-objects --stdin-packs --stdout \
+ --filter=blob:none </dev/null 2>err &&
+ test_i18ngrep "cannot use --filter with --stdin-packs" err
+ )
+'
+
+test_expect_success '--stdin-packs is incompatible with --revs' '
+ (
+ cd stdin-packs &&
+ test_must_fail git pack-objects --stdin-packs --revs out \
+ </dev/null 2>err &&
+ test_i18ngrep "cannot use internal rev list with --stdin-packs" err
+ )
+'
+
+test_expect_success '--stdin-packs with loose objects' '
+ (
+ cd stdin-packs &&
+
+ PACK_A="$(basename .git/objects/pack/pack-A-*.pack)" &&
+ PACK_B="$(basename .git/objects/pack/pack-B-*.pack)" &&
+ PACK_C="$(basename .git/objects/pack/pack-C-*.pack)" &&
+
+ test_commit D && # loose
+
+ git pack-objects test2 --stdin-packs --unpacked <<-EOF &&
+ $PACK_A
+ ^$PACK_B
+ $PACK_C
+ EOF
+
+ (
+ git show-index <$(ls .git/objects/pack/pack-A-*.idx) &&
+ git show-index <$(ls .git/objects/pack/pack-C-*.idx) &&
+ git rev-list --objects --no-object-names \
+ refs/tags/C..refs/tags/D
+
+ ) >expect.raw &&
+ ls -la . &&
+ git show-index <$(ls test2-*.idx) >actual.raw &&
+
+ cut -d" " -f2 <expect.raw | sort >expect &&
+ cut -d" " -f2 <actual.raw | sort >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success '--stdin-packs with broken links' '
+ (
+ cd stdin-packs &&
+
+ # make an unreachable object with a bogus parent
+ git cat-file -p HEAD >commit &&
+ sed "s/$(git rev-parse HEAD^)/$(test_oid zero)/" <commit |
+ git hash-object -w -t commit --stdin >in &&
+
+ git pack-objects .git/objects/pack/pack-D <in &&
+
+ PACK_A="$(basename .git/objects/pack/pack-A-*.pack)" &&
+ PACK_B="$(basename .git/objects/pack/pack-B-*.pack)" &&
+ PACK_C="$(basename .git/objects/pack/pack-C-*.pack)" &&
+ PACK_D="$(basename .git/objects/pack/pack-D-*.pack)" &&
+
+ git pack-objects test3 --stdin-packs --unpacked <<-EOF &&
+ $PACK_A
+ ^$PACK_B
+ $PACK_C
+ $PACK_D
+ EOF
+
+ (
+ git show-index <$(ls .git/objects/pack/pack-A-*.idx) &&
+ git show-index <$(ls .git/objects/pack/pack-C-*.idx) &&
+ git show-index <$(ls .git/objects/pack/pack-D-*.idx) &&
+ git rev-list --objects --no-object-names \
+ refs/tags/C..refs/tags/D
+ ) >expect.raw &&
+ git show-index <$(ls test3-*.idx) >actual.raw &&
+
+ cut -d" " -f2 <expect.raw | sort >expect &&
+ cut -d" " -f2 <actual.raw | sort >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'negative window clamps to 0' '
+ git pack-objects --progress --window=-1 neg-window <obj-list 2>stderr &&
+ check_deltas stderr = 0
+'
+
test_done
diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh
index b447ce56a9..3475b06aeb 100755
--- a/t/t5304-prune.sh
+++ b/t/t5304-prune.sh
@@ -352,4 +352,20 @@ test_expect_success 'trivial prune with bitmaps enabled' '
test_must_fail git cat-file -e $blob
'
+test_expect_success 'old reachable-from-recent retained with bitmaps' '
+ git repack -adb &&
+ to_drop=$(echo bitmap-from-recent-1 | git hash-object -w --stdin) &&
+ test-tool chmtime -86400 .git/objects/$(test_oid_to_path $to_drop) &&
+ to_save=$(echo bitmap-from-recent-2 | git hash-object -w --stdin) &&
+ test-tool chmtime -86400 .git/objects/$(test_oid_to_path $to_save) &&
+ tree=$(printf "100644 blob $to_save\tfile\n" | git mktree) &&
+ test-tool chmtime -86400 .git/objects/$(test_oid_to_path $tree) &&
+ commit=$(echo foo | git commit-tree $tree) &&
+ git prune --expire=12.hours.ago &&
+ git cat-file -e $commit &&
+ git cat-file -e $tree &&
+ git cat-file -e $to_save &&
+ test_must_fail git cat-file -e $to_drop
+'
+
test_done
diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh
index 40b9f63244..b02838750e 100755
--- a/t/t5310-pack-bitmaps.sh
+++ b/t/t5310-pack-bitmaps.sh
@@ -461,6 +461,29 @@ test_expect_success 'truncated bitmap fails gracefully (cache)' '
test_i18ngrep corrupted.bitmap.index stderr
'
+test_expect_success 'enumerating progress counts pack-reused objects' '
+ count=$(git rev-list --objects --all --count) &&
+ git repack -adb &&
+
+ # check first with only reused objects; confirm that our progress
+ # showed the right number, and also that we did pack-reuse as expected.
+ # Check only the final "done" line of the meter (there may be an
+ # arbitrary number of intermediate lines ending with CR).
+ GIT_PROGRESS_DELAY=0 \
+ git pack-objects --all --stdout --progress \
+ </dev/null >/dev/null 2>stderr &&
+ grep "Enumerating objects: $count, done" stderr &&
+ grep "pack-reused $count" stderr &&
+
+ # now the same but with one non-reused object
+ git commit --allow-empty -m "an extra commit object" &&
+ GIT_PROGRESS_DELAY=0 \
+ git pack-objects --all --stdout --progress \
+ </dev/null >/dev/null 2>stderr &&
+ grep "Enumerating objects: $((count+1)), done" stderr &&
+ grep "pack-reused $count" stderr
+'
+
# have_delta <obj> <expected_base>
#
# Note that because this relies on cat-file, it might find _any_ copy of an
@@ -554,4 +577,42 @@ test_expect_success 'fetch with bitmaps can reuse old base' '
)
'
+test_expect_success 'pack.preferBitmapTips' '
+ git init repo &&
+ test_when_finished "rm -fr repo" &&
+ (
+ cd repo &&
+
+ # create enough commits that not all are receive bitmap
+ # coverage even if they are all at the tip of some reference.
+ test_commit_bulk --message="%s" 103 &&
+
+ git rev-list HEAD >commits.raw &&
+ sort <commits.raw >commits &&
+
+ git log --format="create refs/tags/%s %H" HEAD >refs &&
+ git update-ref --stdin <refs &&
+
+ git repack -adb &&
+ test-tool bitmap list-commits | sort >bitmaps &&
+
+ # remember which commits did not receive bitmaps
+ comm -13 bitmaps commits >before &&
+ test_file_not_empty before &&
+
+ # mark the commits which did not receive bitmaps as preferred,
+ # and generate the bitmap again
+ perl -pe "s{^}{create refs/tags/include/$. }" <before |
+ git update-ref --stdin &&
+ git -c pack.preferBitmapTips=refs/tags/include repack -adb &&
+
+ # finally, check that the commit(s) without bitmap coverage
+ # are not the same ones as before
+ test-tool bitmap list-commits | sort >bitmaps &&
+ comm -13 bitmaps commits >after &&
+
+ ! test_cmp before after
+ )
+'
+
test_done
diff --git a/t/t5316-pack-delta-depth.sh b/t/t5316-pack-delta-depth.sh
index a8c1bc0f66..759169d074 100755
--- a/t/t5316-pack-delta-depth.sh
+++ b/t/t5316-pack-delta-depth.sh
@@ -69,6 +69,7 @@ test_expect_success 'create series of packs' '
max_chain() {
git index-pack --verify-stat-only "$1" >output &&
perl -lne '
+ BEGIN { $len = 0 }
/chain length = (\d+)/ and $len = $1;
END { print $len }
' output
@@ -94,4 +95,18 @@ test_expect_success '--depth limits depth' '
test_cmp expect actual
'
+test_expect_success '--depth=0 disables deltas' '
+ pack=$(git pack-objects --all --depth=0 </dev/null pack) &&
+ echo 0 >expect &&
+ max_chain pack-$pack.pack >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'negative depth disables deltas' '
+ pack=$(git pack-objects --all --depth=-1 </dev/null pack) &&
+ echo 0 >expect &&
+ max_chain pack-$pack.pack >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh
index edeb6d6d31..af88f805aa 100755
--- a/t/t5318-commit-graph.sh
+++ b/t/t5318-commit-graph.sh
@@ -475,7 +475,7 @@ test_expect_success 'lower layers have overflow chunk' '
test_expect_success 'git commit-graph verify' '
cd "$TRASH_DIRECTORY/full" &&
- git rev-parse commits/8 | GIT_TEST_COMMIT_GRAPH_NO_GDAT=1 git commit-graph write --stdin-commits &&
+ git rev-parse commits/8 | git -c commitGraph.generationVersion=1 commit-graph write --stdin-commits &&
git commit-graph verify >output &&
graph_read_expect 9 extra_edges
'
diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh
index b4afab1dfc..5641d158df 100755
--- a/t/t5319-multi-pack-index.sh
+++ b/t/t5319-multi-pack-index.sh
@@ -234,6 +234,48 @@ test_expect_success 'warn on improper hash version' '
)
'
+test_expect_success 'midx picks objects from preferred pack' '
+ test_when_finished rm -rf preferred.git &&
+ git init --bare preferred.git &&
+ (
+ cd preferred.git &&
+
+ a=$(echo "a" | git hash-object -w --stdin) &&
+ b=$(echo "b" | git hash-object -w --stdin) &&
+ c=$(echo "c" | git hash-object -w --stdin) &&
+
+ # Set up two packs, duplicating the object "B" at different
+ # offsets.
+ #
+ # Note that the "BC" pack (the one we choose as preferred) sorts
+ # lexically after the "AB" pack, meaning that omitting the
+ # --preferred-pack argument would cause this test to fail (since
+ # the MIDX code would select the copy of "b" in the "AB" pack).
+ git pack-objects objects/pack/test-AB <<-EOF &&
+ $a
+ $b
+ EOF
+ bc=$(git pack-objects objects/pack/test-BC <<-EOF
+ $b
+ $c
+ EOF
+ ) &&
+
+ git multi-pack-index --object-dir=objects \
+ write --preferred-pack=test-BC-$bc.idx 2>err &&
+ test_must_be_empty err &&
+
+ test-tool read-midx --show-objects objects >out &&
+
+ ofs=$(git show-index <objects/pack/test-BC-$bc.idx | grep $b |
+ cut -d" " -f1) &&
+ printf "%s %s\tobjects/pack/test-BC-%s.pack\n" \
+ "$b" "$ofs" "$bc" >expect &&
+ grep ^$b out >actual &&
+
+ test_cmp expect actual
+ )
+'
test_expect_success 'verify multi-pack-index success' '
git multi-pack-index verify --object-dir=$objdir
diff --git a/t/t5324-split-commit-graph.sh b/t/t5324-split-commit-graph.sh
index 8e90f3423b..587226ed10 100755
--- a/t/t5324-split-commit-graph.sh
+++ b/t/t5324-split-commit-graph.sh
@@ -489,7 +489,7 @@ test_expect_success 'setup repo for mixed generation commit-graph-chain' '
test_commit $i &&
git branch commits/$i || return 1
done &&
- git commit-graph write --reachable --split &&
+ git -c commitGraph.generationVersion=2 commit-graph write --reachable --split &&
graph_read_expect $NUM_FIRST_LAYER_COMMITS &&
test_line_count = 1 $graphdir/commit-graph-chain &&
for i in $(test_seq $SECOND_LAYER_SEQUENCE_START $SECOND_LAYER_SEQUENCE_END)
@@ -497,7 +497,7 @@ test_expect_success 'setup repo for mixed generation commit-graph-chain' '
test_commit $i &&
git branch commits/$i || return 1
done &&
- GIT_TEST_COMMIT_GRAPH_NO_GDAT=1 git commit-graph write --reachable --split=no-merge &&
+ git -c commitGraph.generationVersion=1 commit-graph write --reachable --split=no-merge &&
test_line_count = 2 $graphdir/commit-graph-chain &&
test-tool read-graph >output &&
cat >expect <<-EOF &&
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 8c462f20ae..c7b392794b 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -594,6 +594,7 @@ test_expect_success 'add --no-tags' '
cd add-no-tags &&
git init &&
git remote add -f --no-tags origin ../one &&
+ grep tagOpt .git/config &&
git tag -l some-tag >../test/output &&
git tag -l foobar-tag >../test/output &&
git config remote.origin.tagopt >>../test/output
@@ -756,6 +757,7 @@ test_expect_success 'rename a remote' '
cd four &&
git config branch.main.pushRemote origin &&
git remote rename origin upstream &&
+ grep "pushRemote" .git/config &&
test -z "$(git for-each-ref refs/remotes/origin)" &&
test "$(git symbolic-ref refs/remotes/upstream/HEAD)" = "refs/remotes/upstream/main" &&
test "$(git rev-parse upstream/main)" = "$(git rev-parse main)" &&
@@ -772,6 +774,7 @@ test_expect_success 'rename a remote renames repo remote.pushDefault' '
cd four.1 &&
git config remote.pushDefault origin &&
git remote rename origin upstream &&
+ grep pushDefault .git/config &&
test "$(git config --local remote.pushDefault)" = "upstream"
)
'
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index f11742ed59..0916f76302 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -191,6 +191,41 @@ test_expect_success 'fetch with pushInsteadOf (should not rewrite)' '
)
'
+grep_wrote () {
+ object_count=$1
+ file_name=$2
+ grep 'write_pack_file/wrote.*"value":"'$1'"' $2
+}
+
+test_expect_success 'push with negotiation' '
+ # Without negotiation
+ mk_empty testrepo &&
+ git push testrepo $the_first_commit:refs/remotes/origin/first_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 &&
+ grep_wrote 5 event && # 2 commits, 2 trees, 1 blob
+
+ # Same commands, but with negotiation
+ rm event &&
+ mk_empty testrepo &&
+ git push testrepo $the_first_commit:refs/remotes/origin/first_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
+'
+
+test_expect_success 'push with negotiation proceeds anyway even if negotiation fails' '
+ rm event &&
+ mk_empty testrepo &&
+ git push testrepo $the_first_commit:refs/remotes/origin/first_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 &&
+ grep_wrote 5 event && # 2 commits, 2 trees, 1 blob
+ test_i18ngrep "push negotiation failed" err
+'
+
test_expect_success 'push without wildcard' '
mk_empty testrepo &&
diff --git a/t/t5523-push-upstream.sh b/t/t5523-push-upstream.sh
index 9fbe7f784d..fdb4292056 100755
--- a/t/t5523-push-upstream.sh
+++ b/t/t5523-push-upstream.sh
@@ -119,4 +119,11 @@ test_expect_success TTY 'quiet push' '
test_must_be_empty output
'
+test_expect_success TTY 'quiet push -u' '
+ ensure_fresh_upstream &&
+
+ test_terminal git push --quiet -u --no-progress upstream main 2>&1 | tee output &&
+ test_must_be_empty output
+'
+
test_done
diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh
index 984dba22af..4f87d90c5b 100755
--- a/t/t5551-http-fetch-smart.sh
+++ b/t/t5551-http-fetch-smart.sh
@@ -517,4 +517,45 @@ test_expect_success 'server-side error detected' '
test_i18ngrep "server-side error" actual
'
+test_expect_success 'http auth remembers successful credentials' '
+ rm -f .git-credentials &&
+ test_config credential.helper store &&
+
+ # the first request prompts the user...
+ set_askpass user@host pass@host &&
+ git ls-remote "$HTTPD_URL/auth/smart/repo.git" >/dev/null &&
+ expect_askpass both user@host &&
+
+ # ...and the second one uses the stored value rather than
+ # prompting the user.
+ set_askpass bogus-user bogus-pass &&
+ git ls-remote "$HTTPD_URL/auth/smart/repo.git" >/dev/null &&
+ expect_askpass none
+'
+
+test_expect_success 'http auth forgets bogus credentials' '
+ # seed credential store with bogus values. In real life,
+ # this would probably come from a password which worked
+ # for a previous request.
+ rm -f .git-credentials &&
+ test_config credential.helper store &&
+ {
+ echo "url=$HTTPD_URL" &&
+ echo "username=bogus" &&
+ echo "password=bogus"
+ } | git credential approve &&
+
+ # we expect this to use the bogus values and fail, never even
+ # prompting the user...
+ set_askpass user@host pass@host &&
+ test_must_fail git ls-remote "$HTTPD_URL/auth/smart/repo.git" >/dev/null &&
+ expect_askpass none &&
+
+ # ...but now we should have forgotten the bad value, causing
+ # us to prompt the user again.
+ set_askpass user@host pass@host &&
+ git ls-remote "$HTTPD_URL/auth/smart/repo.git" >/dev/null &&
+ expect_askpass both user@host
+'
+
test_done
diff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh
index 29537f4798..4f92a116e1 100755
--- a/t/t5572-pull-submodule.sh
+++ b/t/t5572-pull-submodule.sh
@@ -42,8 +42,11 @@ git_pull_noff () {
$2 git pull --no-ff
}
-KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
-KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
+if test "$GIT_TEST_MERGE_ALGORITHM" != ort
+then
+ KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
+ KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
+fi
test_submodule_switch_func "git_pull_noff"
test_expect_success 'pull --recurse-submodule setup' '
diff --git a/t/t5582-fetch-negative-refspec.sh b/t/t5582-fetch-negative-refspec.sh
index f345097277..e5d2e79ad3 100755
--- a/t/t5582-fetch-negative-refspec.sh
+++ b/t/t5582-fetch-negative-refspec.sh
@@ -240,4 +240,47 @@ test_expect_success "push with matching +: and negative refspec" '
git -C two push -v one
'
+test_expect_success '--prefetch correctly modifies refspecs' '
+ git -C one config --unset-all remote.origin.fetch &&
+ git -C one config --add remote.origin.fetch ^refs/heads/bogus/ignore &&
+ git -C one config --add remote.origin.fetch "refs/tags/*:refs/tags/*" &&
+ git -C one config --add remote.origin.fetch "refs/heads/bogus/*:bogus/*" &&
+
+ git tag -a -m never never-fetch-tag HEAD &&
+
+ git branch bogus/fetched HEAD~1 &&
+ git branch bogus/ignore HEAD &&
+
+ git -C one fetch --prefetch --no-tags &&
+ test_must_fail git -C one rev-parse never-fetch-tag &&
+ git -C one rev-parse refs/prefetch/bogus/fetched &&
+ test_must_fail git -C one rev-parse refs/prefetch/bogus/ignore &&
+
+ # correctly handle when refspec set becomes empty
+ # after removing the refs/tags/* refspec.
+ git -C one config --unset-all remote.origin.fetch &&
+ git -C one config --add remote.origin.fetch "refs/tags/*:refs/tags/*" &&
+
+ git -C one fetch --prefetch --no-tags &&
+ test_must_fail git -C one rev-parse never-fetch-tag &&
+
+ # The refspec for refs that are not fully qualified
+ # are filtered multiple times.
+ git -C one rev-parse refs/prefetch/bogus/fetched &&
+ test_must_fail git -C one rev-parse refs/prefetch/bogus/ignore
+'
+
+test_expect_success '--prefetch succeeds when refspec becomes empty' '
+ git checkout bogus/fetched &&
+ test_commit extra &&
+
+ git -C one config --unset-all remote.origin.fetch &&
+ git -C one config --unset branch.main.remote &&
+ git -C one config remote.origin.fetch "+refs/tags/extra" &&
+ git -C one config remote.origin.skipfetchall true &&
+ git -C one config remote.origin.tagopt "--no-tags" &&
+
+ git -C one fetch --prefetch
+'
+
test_done
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index e7e6c08955..c0688467e7 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -759,6 +759,15 @@ test_expect_success 'partial clone using HTTP' '
partial_clone "$HTTPD_DOCUMENT_ROOT_PATH/server" "$HTTPD_URL/smart/server"
'
+test_expect_success 'reject cloning shallow repository using HTTP' '
+ test_when_finished "rm -rf repo" &&
+ git clone --bare --no-local --depth=1 src "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+ test_must_fail git -c protocol.version=2 clone --reject-shallow $HTTPD_URL/smart/repo.git repo 2>err &&
+ test_i18ngrep -e "source repository is shallow, reject to clone." err &&
+
+ git clone --no-reject-shallow $HTTPD_URL/smart/repo.git repo
+'
+
# DO NOT add non-httpd-specific tests here, because the last part of this
# test script is only executed when httpd is available and enabled.
diff --git a/t/t5606-clone-options.sh b/t/t5606-clone-options.sh
index 52e5789fb0..3a595c0f82 100755
--- a/t/t5606-clone-options.sh
+++ b/t/t5606-clone-options.sh
@@ -11,7 +11,8 @@ test_expect_success 'setup' '
mkdir parent &&
(cd parent && git init &&
echo one >file && git add file &&
- git commit -m one)
+ git commit -m one) &&
+ git clone --depth=1 --no-local parent shallow-repo
'
@@ -45,6 +46,30 @@ test_expect_success 'disallows --bare with --separate-git-dir' '
'
+test_expect_success 'reject cloning shallow repository' '
+ test_when_finished "rm -rf repo" &&
+ test_must_fail git clone --reject-shallow shallow-repo out 2>err &&
+ test_i18ngrep -e "source repository is shallow, reject to clone." err &&
+
+ git clone --no-reject-shallow shallow-repo repo
+'
+
+test_expect_success 'reject cloning non-local shallow repository' '
+ test_when_finished "rm -rf repo" &&
+ test_must_fail git clone --reject-shallow --no-local shallow-repo out 2>err &&
+ test_i18ngrep -e "source repository is shallow, reject to clone." err &&
+
+ git clone --no-reject-shallow --no-local shallow-repo repo
+'
+
+test_expect_success 'succeed cloning normal repository' '
+ test_when_finished "rm -rf chilad1 child2 child3 child4 " &&
+ git clone --reject-shallow parent child1 &&
+ git clone --reject-shallow --no-local parent child2 &&
+ git clone --no-reject-shallow parent child3 &&
+ git clone --no-reject-shallow --no-local parent child4
+'
+
test_expect_success 'uses "origin" for default remote name' '
git clone parent clone-default-origin &&
@@ -104,12 +129,20 @@ test_expect_success 'redirected clone -v does show progress' '
'
+test_expect_success 'clone does not segfault with --bare and core.bare=false' '
+ test_config_global core.bare false &&
+ git clone --bare parent clone-bare &&
+ echo true >expect &&
+ git -C clone-bare rev-parse --is-bare-repository >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'chooses correct default initial branch name' '
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \
git -c init.defaultBranch=foo init --bare empty &&
test_config -C empty lsrefs.unborn advertise &&
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \
- git -c init.defaultBranch=up clone empty whats-up &&
+ git -c init.defaultBranch=up -c protocol.version=2 clone empty whats-up &&
test refs/heads/foo = $(git -C whats-up symbolic-ref HEAD) &&
test refs/heads/foo = $(git -C whats-up config branch.foo.merge)
'
diff --git a/t/t5611-clone-config.sh b/t/t5611-clone-config.sh
index 9f555b87ec..f8625f9158 100755
--- a/t/t5611-clone-config.sh
+++ b/t/t5611-clone-config.sh
@@ -95,6 +95,31 @@ test_expect_success 'clone -c remote.<remote>.fetch=<refspec> --origin=<name>' '
test_cmp expect actual
'
+test_expect_success 'set up shallow repository' '
+ git clone --depth=1 --no-local . shallow-repo
+'
+
+test_expect_success 'clone.rejectshallow=true should reject cloning shallow repo' '
+ test_when_finished "rm -rf out" &&
+ test_must_fail git -c clone.rejectshallow=true clone --no-local shallow-repo out 2>err &&
+ test_i18ngrep -e "source repository is shallow, reject to clone." err &&
+
+ git -c clone.rejectshallow=false clone --no-local shallow-repo out
+'
+
+test_expect_success 'option --[no-]reject-shallow override clone.rejectshallow config' '
+ test_when_finished "rm -rf out" &&
+ test_must_fail git -c clone.rejectshallow=false clone --reject-shallow --no-local shallow-repo out 2>err &&
+ test_i18ngrep -e "source repository is shallow, reject to clone." err &&
+
+ git -c clone.rejectshallow=true clone --no-reject-shallow --no-local shallow-repo out
+'
+
+test_expect_success 'clone.rejectshallow=true should succeed cloning normal repo' '
+ test_when_finished "rm -rf out" &&
+ git -c clone.rejectshallow=true clone --no-local . out
+'
+
test_expect_success MINGW 'clone -c core.hideDotFiles' '
test_commit attributes .gitattributes "" &&
rm -rf child &&
diff --git a/t/t5612-clone-refspec.sh b/t/t5612-clone-refspec.sh
index 6a6af7449c..3126cfd7e9 100755
--- a/t/t5612-clone-refspec.sh
+++ b/t/t5612-clone-refspec.sh
@@ -97,6 +97,7 @@ test_expect_success 'by default no tags will be kept updated' '
test_expect_success 'clone with --no-tags' '
(
cd dir_all_no_tags &&
+ grep tagOpt .git/config &&
git fetch &&
git for-each-ref refs/tags >../actual
) &&
diff --git a/t/t5616-partial-clone.sh b/t/t5616-partial-clone.sh
index 5cb415386e..cf3e82bdf5 100755
--- a/t/t5616-partial-clone.sh
+++ b/t/t5616-partial-clone.sh
@@ -548,6 +548,14 @@ test_expect_success 'fetch from a partial clone, protocol v2' '
grep "version 2" trace
'
+test_expect_success 'repack does not loosen promisor objects' '
+ rm -rf client trace &&
+ git clone --bare --filter=blob:none "file://$(pwd)/srv.bare" client &&
+ test_when_finished "rm -rf client trace" &&
+ GIT_TRACE2_PERF="$(pwd)/trace" git -C client repack -A -d &&
+ grep "loosen_unused_packed_objects/loosened:0" trace
+'
+
. "$TEST_DIRECTORY"/lib-httpd.sh
start_httpd
diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh
index 509f379d49..930721f053 100755
--- a/t/t5701-git-serve.sh
+++ b/t/t5701-git-serve.sh
@@ -16,9 +16,10 @@ test_expect_success 'test capability advertisement' '
version 2
agent=git/$(git version | cut -d" " -f3)
ls-refs=unborn
- fetch=shallow
+ fetch=shallow wait-for-done
server-option
object-format=$(test_oid algo)
+ object-info
0000
EOF
@@ -240,4 +241,29 @@ test_expect_success 'unexpected lines are not allowed in fetch request' '
grep "unexpected line: .this-is-not-a-command." err
'
+# Test the basics of object-info
+#
+test_expect_success 'basics of object-info' '
+ test-tool pkt-line pack >in <<-EOF &&
+ command=object-info
+ object-format=$(test_oid algo)
+ 0001
+ size
+ oid $(git rev-parse two:two.t)
+ oid $(git rev-parse two:two.t)
+ 0000
+ EOF
+
+ cat >expect <<-EOF &&
+ size
+ $(git rev-parse two:two.t) $(wc -c <two.t | xargs)
+ $(git rev-parse two:two.t) $(wc -c <two.t | xargs)
+ 0000
+ EOF
+
+ test-tool serve-v2 --stateless-rpc <in >out &&
+ test-tool pkt-line unpack <out >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh
index 2e1243ca40..66af411057 100755
--- a/t/t5702-protocol-v2.sh
+++ b/t/t5702-protocol-v2.sh
@@ -585,6 +585,49 @@ test_expect_success 'deepen-relative' '
test_cmp expected actual
'
+setup_negotiate_only () {
+ SERVER="$1"
+ URI="$2"
+
+ rm -rf "$SERVER" client
+
+ git init "$SERVER"
+ test_commit -C "$SERVER" one
+ test_commit -C "$SERVER" two
+
+ git clone "$URI" client
+ test_commit -C client three
+}
+
+test_expect_success 'file:// --negotiate-only' '
+ SERVER="server" &&
+ URI="file://$(pwd)/server" &&
+
+ setup_negotiate_only "$SERVER" "$URI" &&
+
+ git -c protocol.version=2 -C client fetch \
+ --no-tags \
+ --negotiate-only \
+ --negotiation-tip=$(git -C client rev-parse HEAD) \
+ origin >out &&
+ COMMON=$(git -C "$SERVER" rev-parse two) &&
+ grep "$COMMON" out
+'
+
+test_expect_success 'file:// --negotiate-only with protocol v0' '
+ SERVER="server" &&
+ URI="file://$(pwd)/server" &&
+
+ setup_negotiate_only "$SERVER" "$URI" &&
+
+ test_must_fail git -c protocol.version=0 -C client fetch \
+ --no-tags \
+ --negotiate-only \
+ --negotiation-tip=$(git -C client rev-parse HEAD) \
+ origin 2>err &&
+ test_i18ngrep "negotiate-only requires protocol v2" err
+'
+
# Test protocol v2 with 'http://' transport
#
. "$TEST_DIRECTORY"/lib-httpd.sh
@@ -1035,6 +1078,52 @@ test_expect_success 'packfile-uri with transfer.fsckobjects fails when .gitmodul
test_i18ngrep "disallowed submodule name" err
'
+test_expect_success 'http:// --negotiate-only' '
+ SERVER="$HTTPD_DOCUMENT_ROOT_PATH/server" &&
+ URI="$HTTPD_URL/smart/server" &&
+
+ setup_negotiate_only "$SERVER" "$URI" &&
+
+ git -c protocol.version=2 -C client fetch \
+ --no-tags \
+ --negotiate-only \
+ --negotiation-tip=$(git -C client rev-parse HEAD) \
+ origin >out &&
+ COMMON=$(git -C "$SERVER" rev-parse two) &&
+ grep "$COMMON" out
+'
+
+test_expect_success 'http:// --negotiate-only without wait-for-done support' '
+ SERVER="server" &&
+ URI="$HTTPD_URL/one_time_perl/server" &&
+
+ setup_negotiate_only "$SERVER" "$URI" &&
+
+ echo "s/ wait-for-done/ xxxx-xxx-xxxx/" \
+ >"$HTTPD_ROOT_PATH/one-time-perl" &&
+
+ test_must_fail git -c protocol.version=2 -C client fetch \
+ --no-tags \
+ --negotiate-only \
+ --negotiation-tip=$(git -C client rev-parse HEAD) \
+ origin 2>err &&
+ test_i18ngrep "server does not support wait-for-done" err
+'
+
+test_expect_success 'http:// --negotiate-only with protocol v0' '
+ SERVER="$HTTPD_DOCUMENT_ROOT_PATH/server" &&
+ URI="$HTTPD_URL/smart/server" &&
+
+ setup_negotiate_only "$SERVER" "$URI" &&
+
+ test_must_fail git -c protocol.version=0 -C client fetch \
+ --no-tags \
+ --negotiate-only \
+ --negotiation-tip=$(git -C client rev-parse HEAD) \
+ origin 2>err &&
+ test_i18ngrep "negotiate-only requires protocol v2" err
+'
+
# DO NOT add non-httpd-specific tests here, because the last part of this
# test script is only executed when httpd is available and enabled.
diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
index 0ba5a91b4e..a1baf4e451 100755
--- a/t/t6030-bisect-porcelain.sh
+++ b/t/t6030-bisect-porcelain.sh
@@ -922,6 +922,17 @@ test_expect_success 'bisect start takes options and revs in any order' '
test_cmp expected actual
'
+# Bisect is started with --term-new and --term-old arguments,
+# then skip. The HEAD should be changed.
+test_expect_success 'bisect skip works with --term*' '
+ git bisect reset &&
+ git bisect start --term-new=fixed --term-old=unfixed HEAD $HASH1 &&
+ hash_skipped_from=$(git rev-parse --verify HEAD) &&
+ git bisect skip &&
+ hash_skipped_to=$(git rev-parse --verify HEAD) &&
+ test "$hash_skipped_from" != "$hash_skipped_to"
+'
+
test_expect_success 'git bisect reset cleans bisection state properly' '
git bisect reset &&
git bisect start &&
@@ -939,4 +950,16 @@ test_expect_success 'git bisect reset cleans bisection state properly' '
test_path_is_missing ".git/BISECT_START"
'
+test_expect_success 'bisect handles annotated tags' '
+ test_commit commit-one &&
+ git tag -m foo tag-one &&
+ test_commit commit-two &&
+ git tag -m foo tag-two &&
+ git bisect start &&
+ git bisect good tag-one &&
+ git bisect bad tag-two >output &&
+ bad=$(git rev-parse --verify tag-two^{commit}) &&
+ grep "$bad is the first bad commit" output
+'
+
test_done
diff --git a/t/t6112-rev-list-filters-objects.sh b/t/t6112-rev-list-filters-objects.sh
index 31457d13b9..4ade105db3 100755
--- a/t/t6112-rev-list-filters-objects.sh
+++ b/t/t6112-rev-list-filters-objects.sh
@@ -159,6 +159,78 @@ test_expect_success 'verify blob:limit=1m' '
test_must_be_empty observed
'
+# Test object:type=<type> filter.
+
+test_expect_success 'setup object-type' '
+ test_create_repo object-type &&
+ test_commit --no-tag -C object-type message blob &&
+ git -C object-type tag tag -m tag-message
+'
+
+test_expect_success 'verify object:type= fails with invalid type' '
+ test_must_fail git -C object-type rev-list --objects --filter=object:type= HEAD &&
+ test_must_fail git -C object-type rev-list --objects --filter=object:type=invalid HEAD
+'
+
+test_expect_success 'verify object:type=blob prints blob and commit' '
+ git -C object-type rev-parse HEAD >expected &&
+ printf "%s blob\n" $(git -C object-type rev-parse HEAD:blob) >>expected &&
+ git -C object-type rev-list --objects --filter=object:type=blob HEAD >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'verify object:type=tree prints tree and commit' '
+ (
+ git -C object-type rev-parse HEAD &&
+ printf "%s \n" $(git -C object-type rev-parse HEAD^{tree})
+ ) >expected &&
+ git -C object-type rev-list --objects --filter=object:type=tree HEAD >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'verify object:type=commit prints commit' '
+ git -C object-type rev-parse HEAD >expected &&
+ git -C object-type rev-list --objects --filter=object:type=commit HEAD >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'verify object:type=tag prints tag' '
+ (
+ git -C object-type rev-parse HEAD &&
+ printf "%s tag\n" $(git -C object-type rev-parse tag)
+ ) >expected &&
+ git -C object-type rev-list --objects --filter=object:type=tag tag >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'verify object:type=blob prints only blob with --filter-provided-objects' '
+ printf "%s blob\n" $(git -C object-type rev-parse HEAD:blob) >expected &&
+ git -C object-type rev-list --objects \
+ --filter=object:type=blob --filter-provided-objects HEAD >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'verify object:type=tree prints only tree with --filter-provided-objects' '
+ printf "%s \n" $(git -C object-type rev-parse HEAD^{tree}) >expected &&
+ git -C object-type rev-list --objects \
+ --filter=object:type=tree HEAD --filter-provided-objects >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'verify object:type=commit prints only commit with --filter-provided-objects' '
+ git -C object-type rev-parse HEAD >expected &&
+ git -C object-type rev-list --objects \
+ --filter=object:type=commit --filter-provided-objects HEAD >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'verify object:type=tag prints only tag with --filter-provided-objects' '
+ printf "%s tag\n" $(git -C object-type rev-parse tag) >expected &&
+ git -C object-type rev-list --objects \
+ --filter=object:type=tag --filter-provided-objects tag >actual &&
+ test_cmp expected actual
+'
+
# Test sparse:path=<path> filter.
# !!!!
# NOTE: sparse:path filter support has been dropped for security reasons,
diff --git a/t/t6113-rev-list-bitmap-filters.sh b/t/t6113-rev-list-bitmap-filters.sh
index 3f889949ca..4d8e09167e 100755
--- a/t/t6113-rev-list-bitmap-filters.sh
+++ b/t/t6113-rev-list-bitmap-filters.sh
@@ -10,7 +10,8 @@ test_expect_success 'set up bitmapped repo' '
test_commit much-larger-blob-one &&
git repack -adb &&
test_commit two &&
- test_commit much-larger-blob-two
+ test_commit much-larger-blob-two &&
+ git tag tag
'
test_expect_success 'filters fallback to non-bitmap traversal' '
@@ -75,4 +76,69 @@ test_expect_success 'tree:1 filter' '
test_cmp expect actual
'
+test_expect_success 'object:type filter' '
+ git rev-list --objects --filter=object:type=tag tag >expect &&
+ git rev-list --use-bitmap-index \
+ --objects --filter=object:type=tag tag >actual &&
+ test_cmp expect actual &&
+
+ git rev-list --objects --filter=object:type=commit tag >expect &&
+ git rev-list --use-bitmap-index \
+ --objects --filter=object:type=commit tag >actual &&
+ test_bitmap_traversal expect actual &&
+
+ git rev-list --objects --filter=object:type=tree tag >expect &&
+ git rev-list --use-bitmap-index \
+ --objects --filter=object:type=tree tag >actual &&
+ test_bitmap_traversal expect actual &&
+
+ git rev-list --objects --filter=object:type=blob tag >expect &&
+ git rev-list --use-bitmap-index \
+ --objects --filter=object:type=blob tag >actual &&
+ test_bitmap_traversal expect actual
+'
+
+test_expect_success 'object:type filter with --filter-provided-objects' '
+ git rev-list --objects --filter-provided-objects --filter=object:type=tag tag >expect &&
+ git rev-list --use-bitmap-index \
+ --objects --filter-provided-objects --filter=object:type=tag tag >actual &&
+ test_cmp expect actual &&
+
+ git rev-list --objects --filter-provided-objects --filter=object:type=commit tag >expect &&
+ git rev-list --use-bitmap-index \
+ --objects --filter-provided-objects --filter=object:type=commit tag >actual &&
+ test_bitmap_traversal expect actual &&
+
+ git rev-list --objects --filter-provided-objects --filter=object:type=tree tag >expect &&
+ git rev-list --use-bitmap-index \
+ --objects --filter-provided-objects --filter=object:type=tree tag >actual &&
+ test_bitmap_traversal expect actual &&
+
+ git rev-list --objects --filter-provided-objects --filter=object:type=blob tag >expect &&
+ git rev-list --use-bitmap-index \
+ --objects --filter-provided-objects --filter=object:type=blob tag >actual &&
+ test_bitmap_traversal expect actual
+'
+
+test_expect_success 'combine filter' '
+ git rev-list --objects --filter=blob:limit=1000 --filter=object:type=blob tag >expect &&
+ git rev-list --use-bitmap-index \
+ --objects --filter=blob:limit=1000 --filter=object:type=blob tag >actual &&
+ test_bitmap_traversal expect actual
+'
+
+test_expect_success 'combine filter with --filter-provided-objects' '
+ git rev-list --objects --filter-provided-objects --filter=blob:limit=1000 --filter=object:type=blob tag >expect &&
+ git rev-list --use-bitmap-index \
+ --objects --filter-provided-objects --filter=blob:limit=1000 --filter=object:type=blob tag >actual &&
+ test_bitmap_traversal expect actual &&
+
+ git cat-file --batch-check="%(objecttype) %(objectsize)" <actual >objects &&
+ while read objecttype objectsize
+ do
+ test "$objecttype" = blob || return 1
+ test "$objectsize" -le 1000 || return 1
+ done <objects
+'
+
test_done
diff --git a/t/t6114-keep-packs.sh b/t/t6114-keep-packs.sh
new file mode 100755
index 0000000000..9239d8aa46
--- /dev/null
+++ b/t/t6114-keep-packs.sh
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+test_description='rev-list with .keep packs'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ test_commit loose &&
+ test_commit packed &&
+ test_commit kept &&
+
+ KEPT_PACK=$(git pack-objects --revs .git/objects/pack/pack <<-EOF
+ refs/tags/kept
+ ^refs/tags/packed
+ EOF
+ ) &&
+ MISC_PACK=$(git pack-objects --revs .git/objects/pack/pack <<-EOF
+ refs/tags/packed
+ ^refs/tags/loose
+ EOF
+ ) &&
+
+ touch .git/objects/pack/pack-$KEPT_PACK.keep
+'
+
+rev_list_objects () {
+ git rev-list "$@" >out &&
+ sort out
+}
+
+idx_objects () {
+ git show-index <$1 >expect-idx &&
+ cut -d" " -f2 <expect-idx | sort
+}
+
+test_expect_success '--no-kept-objects excludes trees and blobs in .keep packs' '
+ rev_list_objects --objects --all --no-object-names >kept &&
+ rev_list_objects --objects --all --no-object-names --no-kept-objects >no-kept &&
+
+ idx_objects .git/objects/pack/pack-$KEPT_PACK.idx >expect &&
+ comm -3 kept no-kept >actual &&
+
+ test_cmp expect actual
+'
+
+test_expect_success '--no-kept-objects excludes kept non-MIDX object' '
+ test_config core.multiPackIndex true &&
+
+ # Create a pack with just the commit object in pack, and do not mark it
+ # as kept (even though it appears in $KEPT_PACK, which does have a .keep
+ # file).
+ MIDX_PACK=$(git pack-objects .git/objects/pack/pack <<-EOF
+ $(git rev-parse kept)
+ EOF
+ ) &&
+
+ # Write a MIDX containing all packs, but use the version of the commit
+ # at "kept" in a non-kept pack by touching $MIDX_PACK.
+ touch .git/objects/pack/pack-$MIDX_PACK.pack &&
+ git multi-pack-index write &&
+
+ rev_list_objects --objects --no-object-names --no-kept-objects HEAD >actual &&
+ (
+ idx_objects .git/objects/pack/pack-$MISC_PACK.idx &&
+ git rev-list --objects --no-object-names refs/tags/loose
+ ) | sort >expect &&
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index cac7f443d0..9e0214076b 100755
--- a/t/t6300-for-each-ref.sh
+++ b/t/t6300-for-each-ref.sh
@@ -945,9 +945,9 @@ test_failing_trailer_option () {
test_expect_success "$title" '
# error message cannot be checked under i18n
test_must_fail git for-each-ref --format="%($option)" refs/heads/main 2>actual &&
- test_i18ncmp expect actual &&
+ test_cmp expect actual &&
test_must_fail git for-each-ref --format="%(contents:$option)" refs/heads/main 2>actual &&
- test_i18ncmp expect actual
+ test_cmp expect actual
'
}
@@ -966,7 +966,7 @@ test_expect_success 'if arguments, %(contents:trailers) shows error if colon is
fatal: unrecognized %(contents) argument: trailersonly
EOF
test_must_fail git for-each-ref --format="%(contents:trailersonly)" 2>actual &&
- test_i18ncmp expect actual
+ test_cmp expect actual
'
test_expect_success 'basic atom: head contents:trailers' '
@@ -1134,4 +1134,14 @@ test_expect_success 'for-each-ref --ignore-case works on multiple sort keys' '
test_cmp expect actual
'
+test_expect_success 'for-each-ref reports broken tags' '
+ git tag -m "good tag" broken-tag-good HEAD &&
+ git cat-file tag broken-tag-good >good &&
+ sed s/commit/blob/ <good >bad &&
+ bad=$(git hash-object -w -t tag bad) &&
+ git update-ref refs/tags/broken-tag-bad $bad &&
+ test_must_fail git for-each-ref --format="%(*objectname)" \
+ refs/tags/broken-tag-*
+'
+
test_done
diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh
index 9866b1b573..1537aa2179 100755
--- a/t/t6302-for-each-ref-filter.sh
+++ b/t/t6302-for-each-ref-filter.sh
@@ -117,6 +117,25 @@ test_expect_success '%(color) must fail' '
test_must_fail git for-each-ref --format="%(color)%(refname)"
'
+test_expect_success '%(color:#aa22ac) must succeed' '
+ test_when_finished rm -rf test &&
+ git init test &&
+ (
+ cd test &&
+ test_commit initial &&
+ git branch -M main &&
+ cat >expect <<-\EOF &&
+ refs/heads/main
+ refs/tags/initial
+ EOF
+ git remote add origin nowhere &&
+ git config branch.main.remote origin &&
+ git config branch.main.merge refs/heads/main &&
+ git for-each-ref --format="%(color:#aa22ac)%(refname)" >actual &&
+ test_cmp expect actual
+ )
+'
+
test_expect_success 'left alignment is default' '
cat >expect <<-\EOF &&
refname is refs/heads/main |refs/heads/main
diff --git a/t/t6423-merge-rename-directories.sh b/t/t6423-merge-rename-directories.sh
index 5d3b711fe6..7134769149 100755
--- a/t/t6423-merge-rename-directories.sh
+++ b/t/t6423-merge-rename-directories.sh
@@ -4797,7 +4797,7 @@ test_setup_12f () {
)
}
-test_expect_merge_algorithm failure success '12f: Trivial directory resolve, caching, all kinds of fun' '
+test_expect_merge_algorithm failure failure '12f: Trivial directory resolve, caching, all kinds of fun' '
test_setup_12f &&
(
cd 12f &&
@@ -4895,6 +4895,77 @@ test_expect_merge_algorithm failure success '12f: Trivial directory resolve, cac
)
'
+# Testcase 12g, Testcase with two kinds of "relevant" renames
+# Commit O: somefile_O, subdir/{a_O,b_O}
+# Commit A: somefile_A, subdir/{a_O,b_O,c_A}
+# Commit B: newfile_B, newdir/{a_B,b_B}
+# Expected: newfile_{merged}, newdir/{a_B,b_B,c_A}
+
+test_setup_12g () {
+ test_create_repo 12g &&
+ (
+ cd 12g &&
+
+ mkdir -p subdir &&
+ test_write_lines upon a time there was a >somefile &&
+ test_write_lines 1 2 3 4 5 6 7 8 9 10 >subdir/a &&
+ test_write_lines one two three four five six >subdir/b &&
+ git add . &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git switch A &&
+ test_write_lines once upon a time there was a >somefile &&
+ > subdir/c &&
+ git add somefile subdir/c &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv somefile newfile &&
+ git mv subdir newdir &&
+ echo repo >>newfile &&
+ test_write_lines 1 2 3 4 5 6 7 8 9 10 11 >newdir/a &&
+ test_write_lines one two three four five six seven >newdir/b &&
+ git add newfile newdir &&
+ test_tick &&
+ git commit -m "B"
+ )
+}
+
+test_expect_success '12g: Testcase with two kinds of "relevant" renames' '
+ test_setup_12g &&
+ (
+ cd 12g &&
+
+ git checkout A^0 &&
+
+ git -c merge.directoryRenames=true merge -s recursive B^0 &&
+
+ test_write_lines once upon a time there was a repo >expect &&
+ test_cmp expect newfile &&
+
+ git ls-files -s >out &&
+ test_line_count = 4 out &&
+
+ git rev-parse >actual \
+ HEAD:newdir/a HEAD:newdir/b HEAD:newdir/c &&
+ git rev-parse >expect \
+ B:newdir/a B:newdir/b A:subdir/c &&
+ test_cmp expect actual &&
+
+ test_must_fail git rev-parse HEAD:subdir/a &&
+ test_must_fail git rev-parse HEAD:subdir/b &&
+ test_must_fail git rev-parse HEAD:subdir/c &&
+ test_path_is_missing subdir/ &&
+ test_path_is_file newdir/c
+ )
+'
+
###########################################################################
# SECTION 13: Checking informational and conflict messages
#
diff --git a/t/t6428-merge-conflicts-sparse.sh b/t/t6428-merge-conflicts-sparse.sh
new file mode 100755
index 0000000000..7e8bf497f8
--- /dev/null
+++ b/t/t6428-merge-conflicts-sparse.sh
@@ -0,0 +1,158 @@
+#!/bin/sh
+
+test_description="merge cases"
+
+# The setup for all of them, pictorially, is:
+#
+# A
+# o
+# / \
+# O o ?
+# \ /
+# o
+# B
+#
+# To help make it easier to follow the flow of tests, they have been
+# divided into sections and each test will start with a quick explanation
+# of what commits O, A, and B contain.
+#
+# Notation:
+# z/{b,c} means files z/b and z/c both exist
+# x/d_1 means file x/d exists with content d1. (Purpose of the
+# underscore notation is to differentiate different
+# files that might be renamed into each other's paths.)
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-merge.sh
+
+
+# Testcase basic, conflicting changes in 'numerals'
+
+test_setup_numerals () {
+ test_create_repo numerals_$1 &&
+ (
+ cd numerals_$1 &&
+
+ >README &&
+ test_write_lines I II III >numerals &&
+ git add README numerals &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ test_write_lines I II III IIII >numerals &&
+ git add numerals &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ test_write_lines I II III IV >numerals &&
+ git add numerals &&
+ test_tick &&
+ git commit -m "B" &&
+
+ cat <<-EOF >expected-index &&
+ H README
+ M numerals
+ M numerals
+ M numerals
+ EOF
+
+ cat <<-EOF >expected-merge
+ I
+ II
+ III
+ <<<<<<< HEAD
+ IIII
+ =======
+ IV
+ >>>>>>> B^0
+ EOF
+
+ )
+}
+
+test_expect_success 'conflicting entries written to worktree even if sparse' '
+ test_setup_numerals plain &&
+ (
+ cd numerals_plain &&
+
+ git checkout A^0 &&
+
+ test_path_is_file README &&
+ test_path_is_file numerals &&
+
+ git sparse-checkout init &&
+ git sparse-checkout set README &&
+
+ test_path_is_file README &&
+ test_path_is_missing numerals &&
+
+ test_must_fail git merge -s recursive B^0 &&
+
+ git ls-files -t >index_files &&
+ test_cmp expected-index index_files &&
+
+ test_path_is_file README &&
+ test_path_is_file numerals &&
+
+ test_cmp expected-merge numerals &&
+
+ # 4 other files:
+ # * expected-merge
+ # * expected-index
+ # * index_files
+ # * others
+ git ls-files -o >others &&
+ test_line_count = 4 others
+ )
+'
+
+test_expect_merge_algorithm failure success 'present-despite-SKIP_WORKTREE handled reasonably' '
+ test_setup_numerals in_the_way &&
+ (
+ cd numerals_in_the_way &&
+
+ git checkout A^0 &&
+
+ test_path_is_file README &&
+ test_path_is_file numerals &&
+
+ git sparse-checkout init &&
+ git sparse-checkout set README &&
+
+ test_path_is_file README &&
+ test_path_is_missing numerals &&
+
+ echo foobar >numerals &&
+
+ test_must_fail git merge -s recursive B^0 &&
+
+ git ls-files -t >index_files &&
+ test_cmp expected-index index_files &&
+
+ test_path_is_file README &&
+ test_path_is_file numerals &&
+
+ test_cmp expected-merge numerals &&
+
+ # There should still be a file with "foobar" in it
+ grep foobar * &&
+
+ # 5 other files:
+ # * expected-merge
+ # * expected-index
+ # * index_files
+ # * others
+ # * whatever name was given to the numerals file that had
+ # "foobar" in it
+ git ls-files -o >others &&
+ test_line_count = 5 others
+ )
+'
+
+test_done
diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh
index 0f92bcf326..e5e89c2045 100755
--- a/t/t6437-submodule-merge.sh
+++ b/t/t6437-submodule-merge.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-merge.sh
#
# history
@@ -328,7 +329,7 @@ test_expect_success 'setup file/submodule conflict' '
)
'
-test_expect_failure 'file/submodule conflict' '
+test_expect_merge_algorithm failure success 'file/submodule conflict' '
test_when_finished "git -C file-submodule reset --hard" &&
(
cd file-submodule &&
@@ -437,7 +438,7 @@ test_expect_failure 'directory/submodule conflict; keep submodule clean' '
)
'
-test_expect_failure !FAIL_PREREQS 'directory/submodule conflict; should not treat submodule files as untracked or in the way' '
+test_expect_merge_algorithm failure success !FAIL_PREREQS 'directory/submodule conflict; should not treat submodule files as untracked or in the way' '
test_when_finished "git -C directory-submodule/path reset --hard" &&
test_when_finished "git -C directory-submodule reset --hard" &&
(
diff --git a/t/t6438-submodule-directory-file-conflicts.sh b/t/t6438-submodule-directory-file-conflicts.sh
index 04bf4be7d7..8df67a0ef9 100755
--- a/t/t6438-submodule-directory-file-conflicts.sh
+++ b/t/t6438-submodule-directory-file-conflicts.sh
@@ -12,8 +12,11 @@ test_submodule_switch "merge --ff"
test_submodule_switch "merge --ff-only"
-KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
-KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
+if test "$GIT_TEST_MERGE_ALGORITHM" != ort
+then
+ KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
+ KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
+fi
test_submodule_switch "merge --no-ff"
test_done
diff --git a/t/t6501-freshen-objects.sh b/t/t6501-freshen-objects.sh
index 75210f012b..10662456ae 100755
--- a/t/t6501-freshen-objects.sh
+++ b/t/t6501-freshen-objects.sh
@@ -43,15 +43,25 @@ commit () {
}
maybe_repack () {
- if test -n "$repack"; then
+ case "$title" in
+ loose)
+ : skip repack
+ ;;
+ repack)
git repack -ad
- fi
+ ;;
+ bitmap)
+ git repack -adb
+ ;;
+ *)
+ echo >&2 "unknown test type in maybe_repack"
+ return 1
+ ;;
+ esac
}
-for repack in '' true; do
- title=${repack:+repack}
- title=${title:-loose}
-
+for title in loose repack bitmap
+do
test_expect_success "make repo completely empty ($title)" '
rm -rf .git &&
git init
diff --git a/t/t6600-test-reach.sh b/t/t6600-test-reach.sh
index e2d33a8a4c..3d7a62ddab 100755
--- a/t/t6600-test-reach.sh
+++ b/t/t6600-test-reach.sh
@@ -55,7 +55,7 @@ test_expect_success 'setup' '
git show-ref -s commit-5-5 | git commit-graph write --stdin-commits &&
mv .git/objects/info/commit-graph commit-graph-half &&
chmod u+w commit-graph-half &&
- GIT_TEST_COMMIT_GRAPH_NO_GDAT=1 git commit-graph write --reachable &&
+ git -c commitGraph.generationVersion=1 commit-graph write --reachable &&
mv .git/objects/info/commit-graph commit-graph-no-gdat &&
chmod u+w commit-graph-no-gdat &&
git config core.commitGraph true
diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh
index 1c55695034..1349e5b232 100755
--- a/t/t7003-filter-branch.sh
+++ b/t/t7003-filter-branch.sh
@@ -506,4 +506,35 @@ test_expect_success 'rewrite repository including refs that point at non-commit
! fgrep fatal filter-output
'
+test_expect_success 'filter-branch handles ref deletion' '
+ git switch --orphan empty-commit &&
+ git commit --allow-empty -m "empty commit" &&
+ git tag empty &&
+ git branch to-delete &&
+ git filter-branch -f --prune-empty to-delete >out 2>&1 &&
+ grep "to-delete.*was deleted" out &&
+ test_must_fail git rev-parse --verify to-delete
+'
+
+test_expect_success 'filter-branch handles ref rewrite' '
+ git checkout empty &&
+ test_commit to-drop &&
+ git branch rewrite &&
+ git filter-branch -f \
+ --index-filter "git rm --ignore-unmatch --cached to-drop.t" \
+ rewrite >out 2>&1 &&
+ grep "rewrite.*was rewritten" out &&
+ ! grep -i warning out &&
+ git diff-tree empty rewrite
+'
+
+test_expect_success 'filter-branch handles ancestor rewrite' '
+ test_commit to-exclude &&
+ git branch ancestor &&
+ git filter-branch -f ancestor -- :^to-exclude.t >out 2>&1 &&
+ grep "ancestor.*was rewritten" out &&
+ ! grep -i warning out &&
+ git diff-tree HEAD^ ancestor
+'
+
test_done
diff --git a/t/t7007-show.sh b/t/t7007-show.sh
index 42d3db6246..d6cc69e0f2 100755
--- a/t/t7007-show.sh
+++ b/t/t7007-show.sh
@@ -38,6 +38,45 @@ test_expect_success 'showing two commits' '
test_cmp expect actual.filtered
'
+test_expect_success 'showing a tree' '
+ cat >expected <<-EOF &&
+ tree main1:
+
+ main1.t
+ EOF
+ git show main1: >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'showing two trees' '
+ cat >expected <<-EOF &&
+ tree main1^{tree}
+
+ main1.t
+
+ tree main2^{tree}
+
+ main1.t
+ main2.t
+ EOF
+ git show main1^{tree} main2^{tree} >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'showing a trees is not recursive' '
+ git worktree add not-recursive main1 &&
+ mkdir not-recursive/a &&
+ test_commit -C not-recursive a/file &&
+ cat >expected <<-EOF &&
+ tree HEAD^{tree}
+
+ a/
+ main1.t
+ EOF
+ git -C not-recursive show HEAD^{tree} >actual &&
+ test_cmp expected actual
+'
+
test_expect_success 'showing a range walks (linear)' '
cat >expect <<-EOF &&
commit $(git rev-parse main3)
diff --git a/t/t7011-skip-worktree-reading.sh b/t/t7011-skip-worktree-reading.sh
index 26852586ac..1761a2b1b9 100755
--- a/t/t7011-skip-worktree-reading.sh
+++ b/t/t7011-skip-worktree-reading.sh
@@ -132,11 +132,6 @@ test_expect_success 'diff-files does not examine skip-worktree dirty entries' '
test -z "$(git diff-files -- one)"
'
-test_expect_success 'git-rm succeeds on skip-worktree absent entries' '
- setup_absent &&
- git rm 1
-'
-
test_expect_success 'commit on skip-worktree absent entries' '
git reset &&
setup_absent &&
diff --git a/t/t7012-skip-worktree-writing.sh b/t/t7012-skip-worktree-writing.sh
index f2a8e76511..a1080b94e3 100755
--- a/t/t7012-skip-worktree-writing.sh
+++ b/t/t7012-skip-worktree-writing.sh
@@ -60,13 +60,6 @@ setup_absent() {
git update-index --skip-worktree 1
}
-test_absent() {
- echo "100644 $EMPTY_BLOB 0 1" > expected &&
- git ls-files --stage 1 > result &&
- test_cmp expected result &&
- test ! -f 1
-}
-
setup_dirty() {
git update-index --force-remove 1 &&
echo dirty > 1 &&
@@ -100,18 +93,6 @@ test_expect_success 'index setup' '
test_cmp expected result
'
-test_expect_success 'git-add ignores worktree content' '
- setup_absent &&
- git add 1 &&
- test_absent
-'
-
-test_expect_success 'git-add ignores worktree content' '
- setup_dirty &&
- git add 1 &&
- test_dirty
-'
-
test_expect_success 'git-rm fails if worktree is dirty' '
setup_dirty &&
test_must_fail git rm 1 &&
diff --git a/t/t7063-status-untracked-cache.sh b/t/t7063-status-untracked-cache.sh
index accefde72f..a0c123b0a7 100755
--- a/t/t7063-status-untracked-cache.sh
+++ b/t/t7063-status-untracked-cache.sh
@@ -57,6 +57,20 @@ iuc () {
return $ret
}
+get_relevant_traces () {
+ # From the GIT_TRACE2_PERF data of the form
+ # $TIME $FILE:$LINE | d0 | main | data | r1 | ? | ? | read_directo | $RELEVANT_STAT
+ # extract the $RELEVANT_STAT fields. We don't care about region_enter
+ # or region_leave, or stats for things outside read_directory.
+ INPUT_FILE=$1
+ OUTPUT_FILE=$2
+ grep data.*read_directo $INPUT_FILE |
+ cut -d "|" -f 9 |
+ grep -v visited \
+ >"$OUTPUT_FILE"
+}
+
+
test_lazy_prereq UNTRACKED_CACHE '
{ git update-index --test-untracked-cache; ret=$?; } &&
test $ret -ne 1
@@ -129,19 +143,21 @@ EOF
test_expect_success 'status first time (empty cache)' '
avoid_racy &&
- : >../trace &&
- GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+ : >../trace.output &&
+ GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
git status --porcelain >../actual &&
iuc status --porcelain >../status.iuc &&
test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../actual &&
+ get_relevant_traces ../trace.output ../trace.relevant &&
cat >../trace.expect <<EOF &&
-node creation: 3
-gitignore invalidation: 1
-directory invalidation: 0
-opendir: 4
+ ....path:
+ ....node-creation:3
+ ....gitignore-invalidation:1
+ ....directory-invalidation:0
+ ....opendir:4
EOF
- test_cmp ../trace.expect ../trace
+ test_cmp ../trace.expect ../trace.relevant
'
test_expect_success 'untracked cache after first status' '
@@ -151,19 +167,21 @@ test_expect_success 'untracked cache after first status' '
test_expect_success 'status second time (fully populated cache)' '
avoid_racy &&
- : >../trace &&
- GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+ : >../trace.output &&
+ GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
git status --porcelain >../actual &&
iuc status --porcelain >../status.iuc &&
test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../actual &&
+ get_relevant_traces ../trace.output ../trace.relevant &&
cat >../trace.expect <<EOF &&
-node creation: 0
-gitignore invalidation: 0
-directory invalidation: 0
-opendir: 0
+ ....path:
+ ....node-creation:0
+ ....gitignore-invalidation:0
+ ....directory-invalidation:0
+ ....opendir:0
EOF
- test_cmp ../trace.expect ../trace
+ test_cmp ../trace.expect ../trace.relevant
'
test_expect_success 'untracked cache after second status' '
@@ -174,8 +192,8 @@ test_expect_success 'untracked cache after second status' '
test_expect_success 'modify in root directory, one dir invalidation' '
avoid_racy &&
: >four &&
- : >../trace &&
- GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+ : >../trace.output &&
+ GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
git status --porcelain >../actual &&
iuc status --porcelain >../status.iuc &&
cat >../status.expect <<EOF &&
@@ -189,13 +207,15 @@ A two
EOF
test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../actual &&
+ get_relevant_traces ../trace.output ../trace.relevant &&
cat >../trace.expect <<EOF &&
-node creation: 0
-gitignore invalidation: 0
-directory invalidation: 1
-opendir: 1
+ ....path:
+ ....node-creation:0
+ ....gitignore-invalidation:0
+ ....directory-invalidation:1
+ ....opendir:1
EOF
- test_cmp ../trace.expect ../trace
+ test_cmp ../trace.expect ../trace.relevant
'
@@ -223,8 +243,8 @@ EOF
test_expect_success 'new .gitignore invalidates recursively' '
avoid_racy &&
echo four >.gitignore &&
- : >../trace &&
- GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+ : >../trace.output &&
+ GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
git status --porcelain >../actual &&
iuc status --porcelain >../status.iuc &&
cat >../status.expect <<EOF &&
@@ -238,13 +258,15 @@ A two
EOF
test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../actual &&
+ get_relevant_traces ../trace.output ../trace.relevant &&
cat >../trace.expect <<EOF &&
-node creation: 0
-gitignore invalidation: 1
-directory invalidation: 1
-opendir: 4
+ ....path:
+ ....node-creation:0
+ ....gitignore-invalidation:1
+ ....directory-invalidation:1
+ ....opendir:4
EOF
- test_cmp ../trace.expect ../trace
+ test_cmp ../trace.expect ../trace.relevant
'
@@ -272,8 +294,8 @@ EOF
test_expect_success 'new info/exclude invalidates everything' '
avoid_racy &&
echo three >>.git/info/exclude &&
- : >../trace &&
- GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+ : >../trace.output &&
+ GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
git status --porcelain >../actual &&
iuc status --porcelain >../status.iuc &&
cat >../status.expect <<EOF &&
@@ -285,13 +307,15 @@ A two
EOF
test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../actual &&
+ get_relevant_traces ../trace.output ../trace.relevant &&
cat >../trace.expect <<EOF &&
-node creation: 0
-gitignore invalidation: 1
-directory invalidation: 0
-opendir: 4
+ ....path:
+ ....node-creation:0
+ ....gitignore-invalidation:1
+ ....directory-invalidation:0
+ ....opendir:4
EOF
- test_cmp ../trace.expect ../trace
+ test_cmp ../trace.expect ../trace.relevant
'
test_expect_success 'verify untracked cache dump' '
@@ -330,8 +354,8 @@ EOF
'
test_expect_success 'status after the move' '
- : >../trace &&
- GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+ : >../trace.output &&
+ GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
git status --porcelain >../actual &&
iuc status --porcelain >../status.iuc &&
cat >../status.expect <<EOF &&
@@ -343,13 +367,15 @@ A one
EOF
test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../actual &&
+ get_relevant_traces ../trace.output ../trace.relevant &&
cat >../trace.expect <<EOF &&
-node creation: 0
-gitignore invalidation: 0
-directory invalidation: 0
-opendir: 1
+ ....path:
+ ....node-creation:0
+ ....gitignore-invalidation:0
+ ....directory-invalidation:0
+ ....opendir:1
EOF
- test_cmp ../trace.expect ../trace
+ test_cmp ../trace.expect ../trace.relevant
'
test_expect_success 'verify untracked cache dump' '
@@ -389,8 +415,8 @@ EOF
'
test_expect_success 'status after the move' '
- : >../trace &&
- GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+ : >../trace.output &&
+ GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
git status --porcelain >../actual &&
iuc status --porcelain >../status.iuc &&
cat >../status.expect <<EOF &&
@@ -402,13 +428,15 @@ A two
EOF
test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../actual &&
+ get_relevant_traces ../trace.output ../trace.relevant &&
cat >../trace.expect <<EOF &&
-node creation: 0
-gitignore invalidation: 0
-directory invalidation: 0
-opendir: 1
+ ....path:
+ ....node-creation:0
+ ....gitignore-invalidation:0
+ ....directory-invalidation:0
+ ....opendir:1
EOF
- test_cmp ../trace.expect ../trace
+ test_cmp ../trace.expect ../trace.relevant
'
test_expect_success 'verify untracked cache dump' '
@@ -438,8 +466,8 @@ test_expect_success 'set up for sparse checkout testing' '
'
test_expect_success 'status after commit' '
- : >../trace &&
- GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+ : >../trace.output &&
+ GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
git status --porcelain >../actual &&
iuc status --porcelain >../status.iuc &&
cat >../status.expect <<EOF &&
@@ -448,13 +476,15 @@ test_expect_success 'status after commit' '
EOF
test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../actual &&
+ get_relevant_traces ../trace.output ../trace.relevant &&
cat >../trace.expect <<EOF &&
-node creation: 0
-gitignore invalidation: 0
-directory invalidation: 0
-opendir: 2
+ ....path:
+ ....node-creation:0
+ ....gitignore-invalidation:0
+ ....directory-invalidation:0
+ ....opendir:2
EOF
- test_cmp ../trace.expect ../trace
+ test_cmp ../trace.expect ../trace.relevant
'
test_expect_success 'untracked cache correct after commit' '
@@ -496,9 +526,9 @@ test_expect_success 'create/modify files, some of which are gitignored' '
'
test_expect_success 'test sparse status with untracked cache' '
- : >../trace &&
+ : >../trace.output &&
avoid_racy &&
- GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+ GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
git status --porcelain >../status.actual &&
iuc status --porcelain >../status.iuc &&
cat >../status.expect <<EOF &&
@@ -509,13 +539,15 @@ test_expect_success 'test sparse status with untracked cache' '
EOF
test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../status.actual &&
+ get_relevant_traces ../trace.output ../trace.relevant &&
cat >../trace.expect <<EOF &&
-node creation: 0
-gitignore invalidation: 1
-directory invalidation: 2
-opendir: 2
+ ....path:
+ ....node-creation:0
+ ....gitignore-invalidation:1
+ ....directory-invalidation:2
+ ....opendir:2
EOF
- test_cmp ../trace.expect ../trace
+ test_cmp ../trace.expect ../trace.relevant
'
test_expect_success 'untracked cache correct after status' '
@@ -539,8 +571,8 @@ EOF
test_expect_success 'test sparse status again with untracked cache' '
avoid_racy &&
- : >../trace &&
- GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+ : >../trace.output &&
+ GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
git status --porcelain >../status.actual &&
iuc status --porcelain >../status.iuc &&
cat >../status.expect <<EOF &&
@@ -551,13 +583,15 @@ test_expect_success 'test sparse status again with untracked cache' '
EOF
test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../status.actual &&
+ get_relevant_traces ../trace.output ../trace.relevant &&
cat >../trace.expect <<EOF &&
-node creation: 0
-gitignore invalidation: 0
-directory invalidation: 0
-opendir: 0
+ ....path:
+ ....node-creation:0
+ ....gitignore-invalidation:0
+ ....directory-invalidation:0
+ ....opendir:0
EOF
- test_cmp ../trace.expect ../trace
+ test_cmp ../trace.expect ../trace.relevant
'
test_expect_success 'set up for test of subdir and sparse checkouts' '
@@ -568,8 +602,8 @@ test_expect_success 'set up for test of subdir and sparse checkouts' '
test_expect_success 'test sparse status with untracked cache and subdir' '
avoid_racy &&
- : >../trace &&
- GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+ : >../trace.output &&
+ GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
git status --porcelain >../status.actual &&
iuc status --porcelain >../status.iuc &&
cat >../status.expect <<EOF &&
@@ -581,13 +615,15 @@ test_expect_success 'test sparse status with untracked cache and subdir' '
EOF
test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../status.actual &&
+ get_relevant_traces ../trace.output ../trace.relevant &&
cat >../trace.expect <<EOF &&
-node creation: 2
-gitignore invalidation: 0
-directory invalidation: 1
-opendir: 3
+ ....path:
+ ....node-creation:2
+ ....gitignore-invalidation:0
+ ....directory-invalidation:1
+ ....opendir:3
EOF
- test_cmp ../trace.expect ../trace
+ test_cmp ../trace.expect ../trace.relevant
'
test_expect_success 'verify untracked cache dump (sparse/subdirs)' '
@@ -616,19 +652,21 @@ EOF
test_expect_success 'test sparse status again with untracked cache and subdir' '
avoid_racy &&
- : >../trace &&
- GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
+ : >../trace.output &&
+ GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
git status --porcelain >../status.actual &&
iuc status --porcelain >../status.iuc &&
test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../status.actual &&
+ get_relevant_traces ../trace.output ../trace.relevant &&
cat >../trace.expect <<EOF &&
-node creation: 0
-gitignore invalidation: 0
-directory invalidation: 0
-opendir: 0
+ ....path:
+ ....node-creation:0
+ ....gitignore-invalidation:0
+ ....directory-invalidation:0
+ ....opendir:0
EOF
- test_cmp ../trace.expect ../trace
+ test_cmp ../trace.expect ../trace.relevant
'
test_expect_success 'move entry in subdir from untracked to cached' '
diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh
index a74816ca8b..0399701e62 100755
--- a/t/t7300-clean.sh
+++ b/t/t7300-clean.sh
@@ -746,4 +746,46 @@ test_expect_success 'clean untracked paths by pathspec' '
test_must_be_empty actual
'
+test_expect_success 'avoid traversing into ignored directories' '
+ test_when_finished rm -f output error trace.* &&
+ test_create_repo avoid-traversing-deep-hierarchy &&
+ (
+ cd avoid-traversing-deep-hierarchy &&
+
+ mkdir -p untracked/subdir/with/a &&
+ >untracked/subdir/with/a/random-file.txt &&
+
+ GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
+ git clean -ffdxn -e untracked
+ ) &&
+
+ # Make sure we only visited into the top-level directory, and did
+ # not traverse into the "untracked" subdirectory since it was excluded
+ grep data.*read_directo.*directories-visited trace.output |
+ cut -d "|" -f 9 >trace.relevant &&
+ cat >trace.expect <<-EOF &&
+ ..directories-visited:1
+ EOF
+ test_cmp trace.expect trace.relevant
+'
+
+test_expect_success 'traverse into directories that may have ignored entries' '
+ test_when_finished rm -f output &&
+ test_create_repo need-to-traverse-into-hierarchy &&
+ (
+ cd need-to-traverse-into-hierarchy &&
+ mkdir -p modules/foobar/src/generated &&
+ > modules/foobar/src/generated/code.c &&
+ > modules/foobar/Makefile &&
+ echo "/modules/**/src/generated/" >.gitignore &&
+
+ git clean -fX modules/foobar >../output &&
+
+ grep Removing ../output &&
+
+ test_path_is_missing modules/foobar/src/generated/code.c &&
+ test_path_is_file modules/foobar/Makefile
+ )
+'
+
test_done
diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh
index ff3ba5422e..f4f61fe554 100755
--- a/t/t7406-submodule-update.sh
+++ b/t/t7406-submodule-update.sh
@@ -1037,4 +1037,28 @@ test_expect_success 'submodule update --quiet passes quietness to merge/rebase'
)
'
+test_expect_success 'submodule update --quiet passes quietness to fetch with a shallow clone' '
+ test_when_finished "rm -rf super4 super5 super6" &&
+ git clone . super4 &&
+ (cd super4 &&
+ git submodule add --quiet file://"$TRASH_DIRECTORY"/submodule submodule3 &&
+ git commit -am "setup submodule3"
+ ) &&
+ (cd submodule &&
+ test_commit line6 file
+ ) &&
+ git clone super4 super5 &&
+ (cd super5 &&
+ git submodule update --quiet --init --depth=1 submodule3 >out 2>err &&
+ test_must_be_empty out &&
+ test_must_be_empty err
+ ) &&
+ git clone super4 super6 &&
+ (cd super6 &&
+ git submodule update --init --depth=1 submodule3 >out 2>err &&
+ test_file_not_empty out &&
+ test_file_not_empty err
+ )
+'
+
test_done
diff --git a/t/t7415-submodule-names.sh b/t/t7450-bad-git-dotfiles.sh
index f70368bc2e..41706c1c9f 100755
--- a/t/t7415-submodule-names.sh
+++ b/t/t7450-bad-git-dotfiles.sh
@@ -1,9 +1,16 @@
#!/bin/sh
-test_description='check handling of .. in submodule names
+test_description='check broken or malicious patterns in .git* files
-Exercise the name-checking function on a variety of names, and then give a
-real-world setup that confirms we catch this in practice.
+Such as:
+
+ - presence of .. in submodule names;
+ Exercise the name-checking function on a variety of names, and then give a
+ real-world setup that confirms we catch this in practice.
+
+ - nested submodule names
+
+ - symlinked .gitmodules, etc
'
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-pack.sh
@@ -132,31 +139,84 @@ test_expect_success 'index-pack --strict works for non-repo pack' '
grep gitmodulesName output
'
-test_expect_success 'fsck detects symlinked .gitmodules file' '
- git init symlink &&
- (
- cd symlink &&
-
- # Make the tree directly to avoid index restrictions.
- #
- # Because symlinks store the target as a blob, choose
- # a pathname that could be parsed as a .gitmodules file
- # to trick naive non-symlink-aware checking.
- tricky="[foo]bar=true" &&
- content=$(git hash-object -w ../.gitmodules) &&
- target=$(printf "$tricky" | git hash-object -w --stdin) &&
- {
- printf "100644 blob $content\t$tricky\n" &&
- printf "120000 blob $target\t.gitmodules\n"
- } | git mktree &&
-
- # Check not only that we fail, but that it is due to the
- # symlink detector; this grep string comes from the config
- # variable name and will not be translated.
- test_must_fail git fsck 2>output &&
- test_i18ngrep gitmodulesSymlink output
- )
-'
+check_dotx_symlink () {
+ fsck_must_fail=test_must_fail
+ fsck_prefix=error
+ refuse_index=t
+ case "$1" in
+ --warning)
+ fsck_must_fail=
+ fsck_prefix=warning
+ refuse_index=
+ shift
+ ;;
+ esac
+
+ name=$1
+ type=$2
+ path=$3
+ dir=symlink-$name-$type
+
+ test_expect_success "set up repo with symlinked $name ($type)" '
+ git init $dir &&
+ (
+ cd $dir &&
+
+ # Make the tree directly to avoid index restrictions.
+ #
+ # Because symlinks store the target as a blob, choose
+ # a pathname that could be parsed as a .gitmodules file
+ # to trick naive non-symlink-aware checking.
+ tricky="[foo]bar=true" &&
+ content=$(git hash-object -w ../.gitmodules) &&
+ target=$(printf "$tricky" | git hash-object -w --stdin) &&
+ {
+ printf "100644 blob $content\t$tricky\n" &&
+ printf "120000 blob $target\t$path\n"
+ } >bad-tree
+ ) &&
+ tree=$(git -C $dir mktree <$dir/bad-tree)
+ '
+
+ test_expect_success "fsck detects symlinked $name ($type)" '
+ (
+ cd $dir &&
+
+ # Check not only that we fail, but that it is due to the
+ # symlink detector
+ $fsck_must_fail git fsck 2>output &&
+ grep "$fsck_prefix.*tree $tree: ${name}Symlink" output
+ )
+ '
+
+ test -n "$refuse_index" &&
+ test_expect_success "refuse to load symlinked $name into index ($type)" '
+ test_must_fail \
+ git -C $dir \
+ -c core.protectntfs \
+ -c core.protecthfs \
+ read-tree $tree 2>err &&
+ grep "invalid path.*$name" err &&
+ git -C $dir ls-files -s >out &&
+ test_must_be_empty out
+ '
+}
+
+check_dotx_symlink gitmodules vanilla .gitmodules
+check_dotx_symlink gitmodules ntfs ".gitmodules ."
+check_dotx_symlink gitmodules hfs ".${u200c}gitmodules"
+
+check_dotx_symlink --warning gitattributes vanilla .gitattributes
+check_dotx_symlink --warning gitattributes ntfs ".gitattributes ."
+check_dotx_symlink --warning gitattributes hfs ".${u200c}gitattributes"
+
+check_dotx_symlink --warning gitignore vanilla .gitignore
+check_dotx_symlink --warning gitignore ntfs ".gitignore ."
+check_dotx_symlink --warning gitignore hfs ".${u200c}gitignore"
+
+check_dotx_symlink --warning mailmap vanilla .mailmap
+check_dotx_symlink --warning mailmap ntfs ".mailmap ."
+check_dotx_symlink --warning mailmap hfs ".${u200c}mailmap"
test_expect_success 'fsck detects non-blob .gitmodules' '
git init non-blob &&
@@ -191,7 +251,7 @@ test_expect_success 'fsck detects corrupt .gitmodules' '
)
'
-test_expect_success MINGW 'prevent git~1 squatting on Windows' '
+test_expect_success WINDOWS 'prevent git~1 squatting on Windows' '
git init squatting &&
(
cd squatting &&
@@ -219,10 +279,13 @@ test_expect_success MINGW 'prevent git~1 squatting on Windows' '
test_tick &&
git -c core.protectNTFS=false commit -m "module"
) &&
- test_must_fail git -c core.protectNTFS=false \
- clone --recurse-submodules squatting squatting-clone 2>err &&
- test_i18ngrep -e "directory not empty" -e "not an empty directory" err &&
- ! grep gitdir squatting-clone/d/a/git~2
+ if test_have_prereq MINGW
+ then
+ test_must_fail git -c core.protectNTFS=false \
+ clone --recurse-submodules squatting squatting-clone 2>err &&
+ test_i18ngrep -e "directory not empty" -e "not an empty directory" err &&
+ ! grep gitdir squatting-clone/d/a/git~2
+ fi
'
test_expect_success 'git dirs of sibling submodules must not be nested' '
diff --git a/t/t7500-commit-template-squash-signoff.sh b/t/t7500-commit-template-squash-signoff.sh
index e41ac18e7e..7d02f79c0d 100755
--- a/t/t7500-commit-template-squash-signoff.sh
+++ b/t/t7500-commit-template-squash-signoff.sh
@@ -9,6 +9,8 @@ Tests for template, signoff, squash and -F functions.'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
commit_msg_is () {
expect=commit_msg_is.expect
actual=commit_msg_is.actual
@@ -279,6 +281,163 @@ test_expect_success 'commit --fixup -m"something" -m"extra"' '
extra"
'
+get_commit_msg () {
+ rev="$1" &&
+ git log -1 --pretty=format:"%B" "$rev"
+}
+
+test_expect_success 'commit --fixup=amend: creates amend! commit' '
+ commit_for_rebase_autosquash_setup &&
+ cat >expected <<-EOF &&
+ amend! $(git log -1 --format=%s HEAD~)
+
+ $(get_commit_msg HEAD~)
+
+ edited
+ EOF
+ (
+ set_fake_editor &&
+ FAKE_COMMIT_AMEND="edited" \
+ git commit --fixup=amend:HEAD~
+ ) &&
+ get_commit_msg HEAD >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '--fixup=amend: --only ignores staged changes' '
+ commit_for_rebase_autosquash_setup &&
+ cat >expected <<-EOF &&
+ amend! $(git log -1 --format=%s HEAD~)
+
+ $(get_commit_msg HEAD~)
+
+ edited
+ EOF
+ (
+ set_fake_editor &&
+ FAKE_COMMIT_AMEND="edited" \
+ git commit --fixup=amend:HEAD~ --only
+ ) &&
+ get_commit_msg HEAD >actual &&
+ test_cmp expected actual &&
+ test_cmp_rev HEAD@{1}^{tree} HEAD^{tree} &&
+ test_cmp_rev HEAD@{1} HEAD^ &&
+ test_expect_code 1 git diff --cached --exit-code &&
+ git cat-file blob :foo >actual &&
+ test_cmp foo actual
+'
+
+test_expect_success '--fixup=reword: ignores staged changes' '
+ commit_for_rebase_autosquash_setup &&
+ cat >expected <<-EOF &&
+ amend! $(git log -1 --format=%s HEAD~)
+
+ $(get_commit_msg HEAD~)
+
+ edited
+ EOF
+ (
+ set_fake_editor &&
+ FAKE_COMMIT_AMEND="edited" \
+ git commit --fixup=reword:HEAD~
+ ) &&
+ get_commit_msg HEAD >actual &&
+ test_cmp expected actual &&
+ test_cmp_rev HEAD@{1}^{tree} HEAD^{tree} &&
+ test_cmp_rev HEAD@{1} HEAD^ &&
+ test_expect_code 1 git diff --cached --exit-code &&
+ git cat-file blob :foo >actual &&
+ test_cmp foo actual
+'
+
+test_expect_success '--fixup=reword: error out with -m option' '
+ commit_for_rebase_autosquash_setup &&
+ echo "fatal: cannot combine -m with --fixup:reword" >expect &&
+ test_must_fail git commit --fixup=reword:HEAD~ -m "reword commit message" 2>actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--fixup=amend: error out with -m option' '
+ commit_for_rebase_autosquash_setup &&
+ echo "fatal: cannot combine -m with --fixup:amend" >expect &&
+ test_must_fail git commit --fixup=amend:HEAD~ -m "amend commit message" 2>actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'consecutive amend! commits remove amend! line from commit msg body' '
+ commit_for_rebase_autosquash_setup &&
+ cat >expected <<-EOF &&
+ amend! amend! $(git log -1 --format=%s HEAD~)
+
+ $(get_commit_msg HEAD~)
+
+ edited 1
+
+ edited 2
+ EOF
+ echo "reword new commit message" >actual &&
+ (
+ set_fake_editor &&
+ FAKE_COMMIT_AMEND="edited 1" \
+ git commit --fixup=reword:HEAD~ &&
+ FAKE_COMMIT_AMEND="edited 2" \
+ git commit --fixup=reword:HEAD
+ ) &&
+ get_commit_msg HEAD >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'deny to create amend! commit if its commit msg body is empty' '
+ commit_for_rebase_autosquash_setup &&
+ echo "Aborting commit due to empty commit message body." >expected &&
+ (
+ set_fake_editor &&
+ test_must_fail env FAKE_COMMIT_MESSAGE="amend! target message subject line" \
+ git commit --fixup=amend:HEAD~ 2>actual
+ ) &&
+ test_cmp expected actual
+'
+
+test_expect_success 'amend! commit allows empty commit msg body with --allow-empty-message' '
+ commit_for_rebase_autosquash_setup &&
+ cat >expected <<-EOF &&
+ amend! $(git log -1 --format=%s HEAD~)
+ EOF
+ (
+ set_fake_editor &&
+ FAKE_COMMIT_MESSAGE="amend! target message subject line" \
+ git commit --fixup=amend:HEAD~ --allow-empty-message &&
+ get_commit_msg HEAD >actual
+ ) &&
+ test_cmp expected actual
+'
+
+test_fixup_reword_opt () {
+ test_expect_success "--fixup=reword: incompatible with $1" "
+ echo 'fatal: reword option of --fixup is mutually exclusive with'\
+ '--patch/--interactive/--all/--include/--only' >expect &&
+ test_must_fail git commit --fixup=reword:HEAD~ $1 2>actual &&
+ test_cmp expect actual
+ "
+}
+
+for opt in --all --include --only --interactive --patch
+do
+ test_fixup_reword_opt $opt
+done
+
+test_expect_success '--fixup=reword: give error with pathsec' '
+ commit_for_rebase_autosquash_setup &&
+ echo "fatal: cannot combine reword option of --fixup with path '\''foo'\''" >expect &&
+ test_must_fail git commit --fixup=reword:HEAD~ -- foo 2>actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--fixup=reword: -F give error message' '
+ echo "fatal: Only one of -c/-C/-F/--fixup can be used." >expect &&
+ test_must_fail git commit --fixup=reword:HEAD~ -F msg 2>actual &&
+ test_cmp expect actual
+'
test_expect_success 'commit --squash works with -F' '
commit_for_rebase_autosquash_setup &&
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 6396897cc8..38a532d81c 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -38,6 +38,16 @@ check_summary_oneline() {
test_cmp exp act
}
+trailer_commit_base () {
+ echo "fun" >>file &&
+ git add file &&
+ git commit -s --trailer "Signed-off-by=C1 E1 " \
+ --trailer "Helped-by:C2 E2 " \
+ --trailer "Reported-by=C3 E3" \
+ --trailer "Mentored-by:C4 E4" \
+ -m "hello"
+}
+
test_expect_success 'output summary format' '
echo new >file1 &&
@@ -154,6 +164,308 @@ test_expect_success 'sign off' '
'
+test_expect_success 'commit --trailer with "="' '
+ trailer_commit_base &&
+ cat >expected <<-\EOF &&
+ hello
+
+ Signed-off-by: C O Mitter <committer@example.com>
+ Signed-off-by: C1 E1
+ Helped-by: C2 E2
+ Reported-by: C3 E3
+ Mentored-by: C4 E4
+ EOF
+ git cat-file commit HEAD >commit.msg &&
+ sed -e "1,/^\$/d" commit.msg >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "replace" as ifexists' '
+ trailer_commit_base &&
+ cat >expected <<-\EOF &&
+ hello
+
+ Signed-off-by: C O Mitter <committer@example.com>
+ Signed-off-by: C1 E1
+ Reported-by: C3 E3
+ Mentored-by: C4 E4
+ Helped-by: C3 E3
+ EOF
+ git -c trailer.ifexists="replace" \
+ commit --trailer "Mentored-by: C4 E4" \
+ --trailer "Helped-by: C3 E3" \
+ --amend &&
+ git cat-file commit HEAD >commit.msg &&
+ sed -e "1,/^\$/d" commit.msg >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "add" as ifexists' '
+ trailer_commit_base &&
+ cat >expected <<-\EOF &&
+ hello
+
+ Signed-off-by: C O Mitter <committer@example.com>
+ Signed-off-by: C1 E1
+ Helped-by: C2 E2
+ Reported-by: C3 E3
+ Mentored-by: C4 E4
+ Reported-by: C3 E3
+ Mentored-by: C4 E4
+ EOF
+ git -c trailer.ifexists="add" \
+ commit --trailer "Reported-by: C3 E3" \
+ --trailer "Mentored-by: C4 E4" \
+ --amend &&
+ git cat-file commit HEAD >commit.msg &&
+ sed -e "1,/^\$/d" commit.msg >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "donothing" as ifexists' '
+ trailer_commit_base &&
+ cat >expected <<-\EOF &&
+ hello
+
+ Signed-off-by: C O Mitter <committer@example.com>
+ Signed-off-by: C1 E1
+ Helped-by: C2 E2
+ Reported-by: C3 E3
+ Mentored-by: C4 E4
+ Reviewed-by: C6 E6
+ EOF
+ git -c trailer.ifexists="donothing" \
+ commit --trailer "Mentored-by: C5 E5" \
+ --trailer "Reviewed-by: C6 E6" \
+ --amend &&
+ git cat-file commit HEAD >commit.msg &&
+ sed -e "1,/^\$/d" commit.msg >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "addIfDifferent" as ifexists' '
+ trailer_commit_base &&
+ cat >expected <<-\EOF &&
+ hello
+
+ Signed-off-by: C O Mitter <committer@example.com>
+ Signed-off-by: C1 E1
+ Helped-by: C2 E2
+ Reported-by: C3 E3
+ Mentored-by: C4 E4
+ Mentored-by: C5 E5
+ EOF
+ git -c trailer.ifexists="addIfDifferent" \
+ commit --trailer "Reported-by: C3 E3" \
+ --trailer "Mentored-by: C5 E5" \
+ --amend &&
+ git cat-file commit HEAD >commit.msg &&
+ sed -e "1,/^\$/d" commit.msg >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "addIfDifferentNeighbor" as ifexists' '
+ trailer_commit_base &&
+ cat >expected <<-\EOF &&
+ hello
+
+ Signed-off-by: C O Mitter <committer@example.com>
+ Signed-off-by: C1 E1
+ Helped-by: C2 E2
+ Reported-by: C3 E3
+ Mentored-by: C4 E4
+ Reported-by: C3 E3
+ EOF
+ git -c trailer.ifexists="addIfDifferentNeighbor" \
+ commit --trailer "Mentored-by: C4 E4" \
+ --trailer "Reported-by: C3 E3" \
+ --amend &&
+ git cat-file commit HEAD >commit.msg &&
+ sed -e "1,/^\$/d" commit.msg >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "end" as where' '
+ trailer_commit_base &&
+ cat >expected <<-\EOF &&
+ hello
+
+ Signed-off-by: C O Mitter <committer@example.com>
+ Signed-off-by: C1 E1
+ Helped-by: C2 E2
+ Reported-by: C3 E3
+ Mentored-by: C4 E4
+ Reported-by: C3 E3
+ Mentored-by: C4 E4
+ EOF
+ git -c trailer.where="end" \
+ commit --trailer "Reported-by: C3 E3" \
+ --trailer "Mentored-by: C4 E4" \
+ --amend &&
+ git cat-file commit HEAD >commit.msg &&
+ sed -e "1,/^\$/d" commit.msg >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "start" as where' '
+ trailer_commit_base &&
+ cat >expected <<-\EOF &&
+ hello
+
+ Signed-off-by: C1 E1
+ Signed-off-by: C O Mitter <committer@example.com>
+ Signed-off-by: C1 E1
+ Helped-by: C2 E2
+ Reported-by: C3 E3
+ Mentored-by: C4 E4
+ EOF
+ git -c trailer.where="start" \
+ commit --trailer "Signed-off-by: C O Mitter <committer@example.com>" \
+ --trailer "Signed-off-by: C1 E1" \
+ --amend &&
+ git cat-file commit HEAD >commit.msg &&
+ sed -e "1,/^\$/d" commit.msg >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "after" as where' '
+ trailer_commit_base &&
+ cat >expected <<-\EOF &&
+ hello
+
+ Signed-off-by: C O Mitter <committer@example.com>
+ Signed-off-by: C1 E1
+ Helped-by: C2 E2
+ Reported-by: C3 E3
+ Mentored-by: C4 E4
+ Mentored-by: C5 E5
+ EOF
+ git -c trailer.where="after" \
+ commit --trailer "Mentored-by: C4 E4" \
+ --trailer "Mentored-by: C5 E5" \
+ --amend &&
+ git cat-file commit HEAD >commit.msg &&
+ sed -e "1,/^\$/d" commit.msg >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "before" as where' '
+ trailer_commit_base &&
+ cat >expected <<-\EOF &&
+ hello
+
+ Signed-off-by: C O Mitter <committer@example.com>
+ Signed-off-by: C1 E1
+ Helped-by: C2 E2
+ Reported-by: C3 E3
+ Mentored-by: C2 E2
+ Mentored-by: C3 E3
+ Mentored-by: C4 E4
+ EOF
+ git -c trailer.where="before" \
+ commit --trailer "Mentored-by: C3 E3" \
+ --trailer "Mentored-by: C2 E2" \
+ --amend &&
+ git cat-file commit HEAD >commit.msg &&
+ sed -e "1,/^\$/d" commit.msg >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "donothing" as ifmissing' '
+ trailer_commit_base &&
+ cat >expected <<-\EOF &&
+ hello
+
+ Signed-off-by: C O Mitter <committer@example.com>
+ Signed-off-by: C1 E1
+ Helped-by: C2 E2
+ Reported-by: C3 E3
+ Mentored-by: C4 E4
+ Helped-by: C5 E5
+ EOF
+ git -c trailer.ifmissing="donothing" \
+ commit --trailer "Helped-by: C5 E5" \
+ --trailer "Based-by: C6 E6" \
+ --amend &&
+ git cat-file commit HEAD >commit.msg &&
+ sed -e "1,/^\$/d" commit.msg >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "add" as ifmissing' '
+ trailer_commit_base &&
+ cat >expected <<-\EOF &&
+ hello
+
+ Signed-off-by: C O Mitter <committer@example.com>
+ Signed-off-by: C1 E1
+ Helped-by: C2 E2
+ Reported-by: C3 E3
+ Mentored-by: C4 E4
+ Helped-by: C5 E5
+ Based-by: C6 E6
+ EOF
+ git -c trailer.ifmissing="add" \
+ commit --trailer "Helped-by: C5 E5" \
+ --trailer "Based-by: C6 E6" \
+ --amend &&
+ git cat-file commit HEAD >commit.msg &&
+ sed -e "1,/^\$/d" commit.msg >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c ack.key ' '
+ echo "fun" >>file1 &&
+ git add file1 &&
+ cat >expected <<-\EOF &&
+ hello
+
+ Acked-by: Peff
+ EOF
+ git -c trailer.ack.key="Acked-by" \
+ commit --trailer "ack = Peff" -m "hello" &&
+ git cat-file commit HEAD >commit.msg &&
+ sed -e "1,/^\$/d" commit.msg >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and ":=#" as separators' '
+ echo "fun" >>file1 &&
+ git add file1 &&
+ cat >expected <<-\EOF &&
+ I hate bug
+
+ Bug #42
+ EOF
+ git -c trailer.separators=":=#" \
+ -c trailer.bug.key="Bug #" \
+ commit --trailer "bug = 42" -m "I hate bug" &&
+ git cat-file commit HEAD >commit.msg &&
+ sed -e "1,/^\$/d" commit.msg >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and command' '
+ trailer_commit_base &&
+ cat >expected <<-\EOF &&
+ hello
+
+ Signed-off-by: C O Mitter <committer@example.com>
+ Signed-off-by: C1 E1
+ Helped-by: C2 E2
+ Mentored-by: C4 E4
+ Reported-by: A U Thor <author@example.com>
+ EOF
+ git -c trailer.report.key="Reported-by: " \
+ -c trailer.report.ifexists="replace" \
+ -c trailer.report.command="NAME=\"\$ARG\"; test -n \"\$NAME\" && \
+ git log --author=\"\$NAME\" -1 --format=\"format:%aN <%aE>\" || true" \
+ commit --trailer "report = author" --amend &&
+ git cat-file commit HEAD >commit.msg &&
+ sed -e "1,/^\$/d" commit.msg >actual &&
+ test_cmp expected actual
+'
+
test_expect_success 'multiple -m' '
>negative &&
diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
index 6602790b5f..04885d0a5e 100755
--- a/t/t7513-interpret-trailers.sh
+++ b/t/t7513-interpret-trailers.sh
@@ -51,6 +51,69 @@ test_expect_success 'setup' '
EOF
'
+test_expect_success 'with cmd' '
+ test_when_finished "git config --remove-section trailer.bug" &&
+ git config trailer.bug.key "Bug-maker: " &&
+ git config trailer.bug.ifExists "add" &&
+ git config trailer.bug.cmd "echo \"maybe is\"" &&
+ cat >expected2 <<-EOF &&
+
+ Bug-maker: maybe is him
+ Bug-maker: maybe is me
+ EOF
+ git interpret-trailers --trailer "bug: him" --trailer "bug:me" \
+ >actual2 &&
+ test_cmp expected2 actual2
+'
+
+test_expect_success 'with cmd and $1' '
+ test_when_finished "git config --remove-section trailer.bug" &&
+ git config trailer.bug.key "Bug-maker: " &&
+ git config trailer.bug.ifExists "add" &&
+ git config trailer.bug.cmd "echo \"\$1\" is" &&
+ cat >expected2 <<-EOF &&
+
+ Bug-maker: him is him
+ Bug-maker: me is me
+ EOF
+ git interpret-trailers --trailer "bug: him" --trailer "bug:me" \
+ >actual2 &&
+ test_cmp expected2 actual2
+'
+
+test_expect_success 'with cmd and $1 with sh -c' '
+ test_when_finished "git config --remove-section trailer.bug" &&
+ git config trailer.bug.key "Bug-maker: " &&
+ git config trailer.bug.ifExists "replace" &&
+ git config trailer.bug.cmd "sh -c \"echo who is \"\$1\"\"" &&
+ cat >expected2 <<-EOF &&
+
+ Bug-maker: who is me
+ EOF
+ git interpret-trailers --trailer "bug: him" --trailer "bug:me" \
+ >actual2 &&
+ test_cmp expected2 actual2
+'
+
+test_expect_success 'with cmd and $1 with shell script' '
+ test_when_finished "git config --remove-section trailer.bug" &&
+ git config trailer.bug.key "Bug-maker: " &&
+ git config trailer.bug.ifExists "replace" &&
+ git config trailer.bug.cmd "./echoscript" &&
+ cat >expected2 <<-EOF &&
+
+ Bug-maker: who is me
+ EOF
+ cat >echoscript <<-EOF &&
+ #!/bin/sh
+ echo who is "\$1"
+ EOF
+ chmod +x echoscript &&
+ git interpret-trailers --trailer "bug: him" --trailer "bug:me" \
+ >actual2 &&
+ test_cmp expected2 actual2
+'
+
test_expect_success 'without config' '
sed -e "s/ Z\$/ /" >expected <<-\EOF &&
@@ -1274,6 +1337,27 @@ test_expect_success 'setup a commit' '
git commit -m "Add file a.txt"
'
+test_expect_success 'cmd takes precedence over command' '
+ test_when_finished "git config --unset trailer.fix.cmd" &&
+ git config trailer.fix.ifExists "replace" &&
+ git config trailer.fix.cmd "test -n \"\$1\" && git log -1 --oneline --format=\"%h (%aN)\" \
+ --abbrev-commit --abbrev=14 \"\$1\" || true" &&
+ git config trailer.fix.command "git log -1 --oneline --format=\"%h (%s)\" \
+ --abbrev-commit --abbrev=14 \$ARG" &&
+ FIXED=$(git log -1 --oneline --format="%h (%aN)" --abbrev-commit --abbrev=14 HEAD) &&
+ cat complex_message_body >expected2 &&
+ sed -e "s/ Z\$/ /" >>expected2 <<-EOF &&
+ Fixes: $FIXED
+ Acked-by= Z
+ Reviewed-by:
+ Signed-off-by: Z
+ Signed-off-by: A U Thor <author@example.com>
+ EOF
+ git interpret-trailers --trailer "review:" --trailer "fix=HEAD" \
+ <complex_message >actual2 &&
+ test_cmp expected2 actual2
+'
+
test_expect_success 'with command using $ARG' '
git config trailer.fix.ifExists "replace" &&
git config trailer.fix.command "git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \$ARG" &&
diff --git a/t/t7519-status-fsmonitor.sh b/t/t7519-status-fsmonitor.sh
index 45d025f960..637391c6ce 100755
--- a/t/t7519-status-fsmonitor.sh
+++ b/t/t7519-status-fsmonitor.sh
@@ -334,7 +334,7 @@ test_expect_success UNTRACKED_CACHE 'ignore .git changes when invalidating UNTR'
git config core.fsmonitor .git/hooks/fsmonitor-test &&
git update-index --untracked-cache &&
git update-index --fsmonitor &&
- GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace-before" \
+ GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace-before" \
git status &&
test-tool dump-untracked-cache >../before
) &&
@@ -346,12 +346,12 @@ test_expect_success UNTRACKED_CACHE 'ignore .git changes when invalidating UNTR'
EOF
(
cd dot-git &&
- GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace-after" \
+ GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace-after" \
git status &&
test-tool dump-untracked-cache >../after
) &&
- grep "directory invalidation" trace-before >>before &&
- grep "directory invalidation" trace-after >>after &&
+ grep "directory-invalidation" trace-before | cut -d"|" -f 9 >>before &&
+ grep "directory-invalidation" trace-after | cut -d"|" -f 9 >>after &&
# UNTR extension unchanged, dir invalidation count unchanged
test_cmp before after
'
diff --git a/t/t7703-repack-geometric.sh b/t/t7703-repack-geometric.sh
new file mode 100755
index 0000000000..5ccaa440e0
--- /dev/null
+++ b/t/t7703-repack-geometric.sh
@@ -0,0 +1,183 @@
+#!/bin/sh
+
+test_description='git repack --geometric works correctly'
+
+. ./test-lib.sh
+
+GIT_TEST_MULTI_PACK_INDEX=0
+
+objdir=.git/objects
+midx=$objdir/pack/multi-pack-index
+
+test_expect_success '--geometric with no packs' '
+ git init geometric &&
+ test_when_finished "rm -fr geometric" &&
+ (
+ cd geometric &&
+
+ git repack --geometric 2 >out &&
+ test_i18ngrep "Nothing new to pack" out
+ )
+'
+
+test_expect_success '--geometric with one pack' '
+ git init geometric &&
+ test_when_finished "rm -fr geometric" &&
+ (
+ cd geometric &&
+
+ test_commit "base" &&
+ git repack -d &&
+
+ git repack --geometric 2 >out &&
+
+ test_i18ngrep "Nothing new to pack" out
+ )
+'
+
+test_expect_success '--geometric with an intact progression' '
+ git init geometric &&
+ test_when_finished "rm -fr geometric" &&
+ (
+ cd geometric &&
+
+ # These packs already form a geometric progression.
+ test_commit_bulk --start=1 1 && # 3 objects
+ test_commit_bulk --start=2 2 && # 6 objects
+ test_commit_bulk --start=4 4 && # 12 objects
+
+ find $objdir/pack -name "*.pack" | sort >expect &&
+ git repack --geometric 2 -d &&
+ find $objdir/pack -name "*.pack" | sort >actual &&
+
+ test_cmp expect actual
+ )
+'
+
+test_expect_success '--geometric with loose objects' '
+ git init geometric &&
+ test_when_finished "rm -fr geometric" &&
+ (
+ cd geometric &&
+
+ # These packs already form a geometric progression.
+ test_commit_bulk --start=1 1 && # 3 objects
+ test_commit_bulk --start=2 2 && # 6 objects
+ # The loose objects are packed together, breaking the
+ # progression.
+ test_commit loose && # 3 objects
+
+ find $objdir/pack -name "*.pack" | sort >before &&
+ git repack --geometric 2 -d &&
+ find $objdir/pack -name "*.pack" | sort >after &&
+
+ comm -13 before after >new &&
+ comm -23 before after >removed &&
+
+ test_line_count = 1 new &&
+ test_must_be_empty removed &&
+
+ git repack --geometric 2 -d &&
+ find $objdir/pack -name "*.pack" | sort >after &&
+
+ # The progression (3, 3, 6) is combined into one new pack.
+ test_line_count = 1 after
+ )
+'
+
+test_expect_success '--geometric with small-pack rollup' '
+ git init geometric &&
+ test_when_finished "rm -fr geometric" &&
+ (
+ cd geometric &&
+
+ test_commit_bulk --start=1 1 && # 3 objects
+ test_commit_bulk --start=2 1 && # 3 objects
+ find $objdir/pack -name "*.pack" | sort >small &&
+ test_commit_bulk --start=3 4 && # 12 objects
+ test_commit_bulk --start=7 8 && # 24 objects
+ find $objdir/pack -name "*.pack" | sort >before &&
+
+ git repack --geometric 2 -d &&
+
+ # Three packs in total; two of the existing large ones, and one
+ # new one.
+ find $objdir/pack -name "*.pack" | sort >after &&
+ test_line_count = 3 after &&
+ comm -3 small before | tr -d "\t" >large &&
+ grep -qFf large after
+ )
+'
+
+test_expect_success '--geometric with small- and large-pack rollup' '
+ git init geometric &&
+ test_when_finished "rm -fr geometric" &&
+ (
+ cd geometric &&
+
+ # size(small1) + size(small2) > size(medium) / 2
+ test_commit_bulk --start=1 1 && # 3 objects
+ test_commit_bulk --start=2 1 && # 3 objects
+ test_commit_bulk --start=2 3 && # 7 objects
+ test_commit_bulk --start=6 9 && # 27 objects &&
+
+ find $objdir/pack -name "*.pack" | sort >before &&
+
+ git repack --geometric 2 -d &&
+
+ find $objdir/pack -name "*.pack" | sort >after &&
+ comm -12 before after >untouched &&
+
+ # Two packs in total; the largest pack from before running "git
+ # repack", and one new one.
+ test_line_count = 1 untouched &&
+ test_line_count = 2 after
+ )
+'
+
+test_expect_success '--geometric ignores kept packs' '
+ git init geometric &&
+ test_when_finished "rm -fr geometric" &&
+ (
+ cd geometric &&
+
+ test_commit kept && # 3 objects
+ test_commit pack && # 3 objects
+
+ KEPT=$(git pack-objects --revs $objdir/pack/pack <<-EOF
+ refs/tags/kept
+ EOF
+ ) &&
+ PACK=$(git pack-objects --revs $objdir/pack/pack <<-EOF
+ refs/tags/pack
+ ^refs/tags/kept
+ EOF
+ ) &&
+
+ # neither pack contains more than twice the number of objects in
+ # the other, so they should be combined. but, marking one as
+ # .kept on disk will "freeze" it, so the pack structure should
+ # remain unchanged.
+ touch $objdir/pack/pack-$KEPT.keep &&
+
+ find $objdir/pack -name "*.pack" | sort >before &&
+ git repack --geometric 2 -d &&
+ find $objdir/pack -name "*.pack" | sort >after &&
+
+ # both packs should still exist
+ test_path_is_file $objdir/pack/pack-$KEPT.pack &&
+ test_path_is_file $objdir/pack/pack-$PACK.pack &&
+
+ # and no new packs should be created
+ test_cmp before after &&
+
+ # Passing --pack-kept-objects causes packs with a .keep file to
+ # be repacked, too.
+ git repack --geometric 2 -d --pack-kept-objects &&
+
+ find $objdir/pack -name "*.pack" >after &&
+ test_line_count = 1 after
+ )
+'
+
+test_done
diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh
index edfaa9a6d1..5830733f3d 100755
--- a/t/t7810-grep.sh
+++ b/t/t7810-grep.sh
@@ -969,7 +969,8 @@ do
"
done
-test_expect_success !PTHREADS 'grep --threads=N or pack.threads=N warns when no pthreads' '
+test_expect_success !PTHREADS,!FAIL_PREREQS \
+ 'grep --threads=N or pack.threads=N warns when no pthreads' '
git grep --threads=2 Hello hello_world 2>err &&
grep ^warning: err >warnings &&
test_line_count = 1 warnings &&
diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh
index 2412d8c5c0..b93ae014ee 100755
--- a/t/t7900-maintenance.sh
+++ b/t/t7900-maintenance.sh
@@ -141,19 +141,25 @@ test_expect_success 'prefetch multiple remotes' '
test_commit -C clone1 one &&
test_commit -C clone2 two &&
GIT_TRACE2_EVENT="$(pwd)/run-prefetch.txt" git maintenance run --task=prefetch 2>/dev/null &&
- fetchargs="--prune --no-tags --no-write-fetch-head --recurse-submodules=no --refmap= --quiet" &&
- test_subcommand git fetch remote1 $fetchargs +refs/heads/\\*:refs/prefetch/remote1/\\* <run-prefetch.txt &&
- test_subcommand git fetch remote2 $fetchargs +refs/heads/\\*:refs/prefetch/remote2/\\* <run-prefetch.txt &&
+ fetchargs="--prefetch --prune --no-tags --no-write-fetch-head --recurse-submodules=no --quiet" &&
+ test_subcommand git fetch remote1 $fetchargs <run-prefetch.txt &&
+ test_subcommand git fetch remote2 $fetchargs <run-prefetch.txt &&
test_path_is_missing .git/refs/remotes &&
- git log prefetch/remote1/one &&
- git log prefetch/remote2/two &&
+ git log prefetch/remotes/remote1/one &&
+ git log prefetch/remotes/remote2/two &&
git fetch --all &&
- test_cmp_rev refs/remotes/remote1/one refs/prefetch/remote1/one &&
- test_cmp_rev refs/remotes/remote2/two refs/prefetch/remote2/two &&
+ test_cmp_rev refs/remotes/remote1/one refs/prefetch/remotes/remote1/one &&
+ test_cmp_rev refs/remotes/remote2/two refs/prefetch/remotes/remote2/two &&
test_cmp_config refs/prefetch/ log.excludedecoration &&
git log --oneline --decorate --all >log &&
- ! grep "prefetch" log
+ ! grep "prefetch" log &&
+
+ test_when_finished git config --unset remote.remote1.skipFetchAll &&
+ git config remote.remote1.skipFetchAll true &&
+ GIT_TRACE2_EVENT="$(pwd)/skip-remote1.txt" git maintenance run --task=prefetch 2>/dev/null &&
+ test_subcommand ! git fetch remote1 $fetchargs <skip-remote1.txt &&
+ test_subcommand git fetch remote2 $fetchargs <skip-remote1.txt
'
test_expect_success 'prefetch and existing log.excludeDecoration values' '
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index 4eee9c3dcb..3b7540050c 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -415,15 +415,23 @@ test_expect_success $PREREQ 'reject long lines' '
z512=$z64$z64$z64$z64$z64$z64$z64$z64 &&
clean_fake_sendmail &&
cp $patches longline.patch &&
- echo $z512$z512 >>longline.patch &&
+ cat >>longline.patch <<-EOF &&
+ $z512$z512
+ not a long line
+ $z512$z512
+ EOF
test_must_fail git send-email \
--from="Example <nobody@example.com>" \
--to=nobody@example.com \
--smtp-server="$(pwd)/fake.sendmail" \
--transfer-encoding=8bit \
$patches longline.patch \
- 2>errors &&
- grep longline.patch errors
+ 2>actual &&
+ cat >expect <<-\EOF &&
+ fatal: longline.patch:35 is longer than 998 characters
+ warning: no patches were sent
+ EOF
+ test_cmp expect actual
'
test_expect_success $PREREQ 'no patch was sent' '
@@ -513,6 +521,49 @@ do
done
+test_expect_success $PREREQ "--validate respects relative core.hooksPath path" '
+ clean_fake_sendmail &&
+ mkdir my-hooks &&
+ test_when_finished "rm my-hooks.ran" &&
+ write_script my-hooks/sendemail-validate <<-\EOF &&
+ >my-hooks.ran
+ exit 1
+ EOF
+ test_config core.hooksPath "my-hooks" &&
+ test_must_fail git send-email \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ --validate \
+ longline.patch 2>actual &&
+ test_path_is_file my-hooks.ran &&
+ cat >expect <<-EOF &&
+ fatal: longline.patch: rejected by sendemail-validate hook
+ fatal: command '"'"'my-hooks/sendemail-validate'"'"' died with exit code 1
+ warning: no patches were sent
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success $PREREQ "--validate respects absolute core.hooksPath path" '
+ hooks_path="$(pwd)/my-hooks" &&
+ test_config core.hooksPath "$hooks_path" &&
+ test_when_finished "rm my-hooks.ran" &&
+ test_must_fail git send-email \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ --validate \
+ longline.patch 2>actual &&
+ test_path_is_file my-hooks.ran &&
+ cat >expect <<-EOF &&
+ fatal: longline.patch: rejected by sendemail-validate hook
+ fatal: command '"'"'$hooks_path/sendemail-validate'"'"' died with exit code 1
+ warning: no patches were sent
+ EOF
+ test_cmp expect actual
+'
+
for enc in 7bit 8bit quoted-printable base64
do
test_expect_success $PREREQ "--transfer-encoding=$enc produces correct header" '
@@ -593,14 +644,33 @@ test_expect_success $PREREQ 'In-Reply-To with --chain-reply-to' '
test_cmp expect actual
'
+test_set_editor "$(pwd)/fake-editor"
+
+test_expect_success $PREREQ 'setup erroring fake editor' '
+ write_script fake-editor <<-\EOF
+ echo >&2 "I am about to error"
+ exit 1
+ EOF
+'
+
+test_expect_success $PREREQ 'fake editor dies with error' '
+ clean_fake_sendmail &&
+ test_must_fail git send-email \
+ --compose --subject foo \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ $patches 2>err &&
+ grep "I am about to error" err &&
+ grep "the editor exited uncleanly, aborting everything" err
+'
+
test_expect_success $PREREQ 'setup fake editor' '
write_script fake-editor <<-\EOF
echo fake edit >>"$1"
EOF
'
-test_set_editor "$(pwd)/fake-editor"
-
test_expect_success $PREREQ '--compose works' '
clean_fake_sendmail &&
git send-email \
diff --git a/t/t9117-git-svn-init-clone.sh b/t/t9117-git-svn-init-clone.sh
index 044f65e916..62de819a44 100755
--- a/t/t9117-git-svn-init-clone.sh
+++ b/t/t9117-git-svn-init-clone.sh
@@ -7,12 +7,6 @@ test_description='git svn init/clone tests'
. ./lib-git-svn.sh
-# setup, run inside tmp so we don't have any conflicts with $svnrepo
-set -e
-rm -r .git
-mkdir tmp
-cd tmp
-
test_expect_success 'setup svnrepo' '
mkdir project project/trunk project/branches project/tags &&
echo foo > project/trunk/foo &&
diff --git a/t/t9148-git-svn-propset.sh b/t/t9148-git-svn-propset.sh
index 102639090c..aebb28995e 100755
--- a/t/t9148-git-svn-propset.sh
+++ b/t/t9148-git-svn-propset.sh
@@ -7,19 +7,22 @@ test_description='git svn propset tests'
. ./lib-git-svn.sh
-foo_subdir2="subdir/subdir2/foo_subdir2"
+test_expect_success 'setup propset via import' '
+ test_when_finished "rm -rf import" &&
-set -e
-mkdir import &&
-(set -e ; cd import
- mkdir subdir
- mkdir subdir/subdir2
- touch foo # for 'add props top level'
- touch subdir/foo_subdir # for 'add props relative'
- touch "$foo_subdir2" # for 'add props subdir'
- svn_cmd import -m 'import for git svn' . "$svnrepo" >/dev/null
-)
-rm -rf import
+ foo_subdir2="subdir/subdir2/foo_subdir2" &&
+ mkdir -p import/subdir/subdir2 &&
+ (
+ cd import &&
+ # for "add props top level"
+ >foo &&
+ # for "add props relative"
+ >subdir/foo_subdir &&
+ # for "add props subdir"
+ >"$foo_subdir2" &&
+ svn_cmd import -m "import for git svn" . "$svnrepo"
+ )
+'
test_expect_success 'initialize git svn' '
git svn init "$svnrepo"
diff --git a/t/t9801-git-p4-branch.sh b/t/t9801-git-p4-branch.sh
index 56e64697a8..50a6f8bad5 100755
--- a/t/t9801-git-p4-branch.sh
+++ b/t/t9801-git-p4-branch.sh
@@ -203,19 +203,19 @@ test_expect_success 'git p4 clone simple branches' '
git p4 clone --dest=. --detect-branches //depot@all &&
git log --all --graph --decorate --stat &&
git reset --hard p4/depot/branch1 &&
- test -f file1 &&
- test -f file2 &&
- test -f file3 &&
+ test_path_is_file file1 &&
+ test_path_is_file file2 &&
+ test_path_is_file file3 &&
grep update file2 &&
git reset --hard p4/depot/branch2 &&
- test -f file1 &&
- test -f file2 &&
+ test_path_is_file file1 &&
+ test_path_is_file file2 &&
test ! -f file3 &&
! grep update file2 &&
git reset --hard p4/depot/branch3 &&
- test -f file1 &&
- test -f file2 &&
- test -f file3 &&
+ test_path_is_file file1 &&
+ test_path_is_file file2 &&
+ test_path_is_file file3 &&
grep update file2 &&
cd "$cli" &&
cd branch1 &&
@@ -294,11 +294,13 @@ test_expect_success 'git p4 clone complex branches' '
test_path_is_file file3 &&
grep update file2 &&
git reset --hard p4/depot/branch4 &&
+ git diff-tree --quiet HEAD &&
test_path_is_file file1 &&
test_path_is_file file2 &&
test_path_is_missing file3 &&
! grep update file2 &&
git reset --hard p4/depot/branch5 &&
+ git diff-tree --quiet HEAD &&
test_path_is_file file1 &&
test_path_is_file file2 &&
test_path_is_file file3 &&
@@ -606,22 +608,22 @@ test_expect_success 'git p4 clone simple branches with base folder on server sid
git p4 clone --dest=. --use-client-spec --detect-branches //depot@all &&
git log --all --graph --decorate --stat &&
git reset --hard p4/depot/branch1 &&
- test -f file1 &&
- test -f file2 &&
- test -f file3 &&
- test -f sub_file1 &&
+ test_path_is_file file1 &&
+ test_path_is_file file2 &&
+ test_path_is_file file3 &&
+ test_path_is_file sub_file1 &&
grep update file2 &&
git reset --hard p4/depot/branch2 &&
- test -f file1 &&
- test -f file2 &&
+ test_path_is_file file1 &&
+ test_path_is_file file2 &&
test ! -f file3 &&
- test -f sub_file1 &&
+ test_path_is_file sub_file1 &&
! grep update file2 &&
git reset --hard p4/depot/branch3 &&
- test -f file1 &&
- test -f file2 &&
- test -f file3 &&
- test -f sub_file1 &&
+ test_path_is_file file1 &&
+ test_path_is_file file2 &&
+ test_path_is_file file3 &&
+ test_path_is_file sub_file1 &&
grep update file2 &&
cd "$cli" &&
cd branch1 &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 04ce884ef5..cb057ef161 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -1879,6 +1879,7 @@ test_expect_success '__git_find_on_cmdline - single match' '
(
words=(git command --opt list) &&
cword=${#words[@]} &&
+ __git_cmd_idx=1 &&
__git_find_on_cmdline "add list remove" >actual
) &&
test_cmp expect actual
@@ -1889,6 +1890,7 @@ test_expect_success '__git_find_on_cmdline - multiple matches' '
(
words=(git command -o --opt remove list add) &&
cword=${#words[@]} &&
+ __git_cmd_idx=1 &&
__git_find_on_cmdline "add list remove" >actual
) &&
test_cmp expect actual
@@ -1898,6 +1900,7 @@ test_expect_success '__git_find_on_cmdline - no match' '
(
words=(git command --opt branch) &&
cword=${#words[@]} &&
+ __git_cmd_idx=1 &&
__git_find_on_cmdline "add list remove" >actual
) &&
test_must_be_empty actual
@@ -1908,6 +1911,7 @@ test_expect_success '__git_find_on_cmdline - single match with index' '
(
words=(git command --opt list) &&
cword=${#words[@]} &&
+ __git_cmd_idx=1 &&
__git_find_on_cmdline --show-idx "add list remove" >actual
) &&
test_cmp expect actual
@@ -1918,6 +1922,7 @@ test_expect_success '__git_find_on_cmdline - multiple matches with index' '
(
words=(git command -o --opt remove list add) &&
cword=${#words[@]} &&
+ __git_cmd_idx=1 &&
__git_find_on_cmdline --show-idx "add list remove" >actual
) &&
test_cmp expect actual
@@ -1927,11 +1932,23 @@ test_expect_success '__git_find_on_cmdline - no match with index' '
(
words=(git command --opt branch) &&
cword=${#words[@]} &&
+ __git_cmd_idx=1 &&
__git_find_on_cmdline --show-idx "add list remove" >actual
) &&
test_must_be_empty actual
'
+test_expect_success '__git_find_on_cmdline - ignores matches before command with index' '
+ echo "6 remove" >expect &&
+ (
+ words=(git -C remove command -o --opt remove list add) &&
+ cword=${#words[@]} &&
+ __git_cmd_idx=3 &&
+ __git_find_on_cmdline --show-idx "add list remove" >actual
+ ) &&
+ test_cmp expect actual
+'
+
test_expect_success '__git_get_config_variables' '
cat >expect <<-EOF &&
name-1
@@ -2275,6 +2292,7 @@ do
(
words=(git push '$flag' other ma) &&
cword=${#words[@]} cur=${words[cword-1]} &&
+ __git_cmd_idx=1 &&
__git_complete_remote_or_refspec &&
print_comp
) &&
@@ -2288,6 +2306,7 @@ do
(
words=(git push other '$flag' ma) &&
cword=${#words[@]} cur=${words[cword-1]} &&
+ __git_cmd_idx=1 &&
__git_complete_remote_or_refspec &&
print_comp
) &&
@@ -2306,6 +2325,7 @@ test_expect_success 'git config - variable name' '
test_completion "git config log.d" <<-\EOF
log.date Z
log.decorate Z
+ log.diffMerges Z
EOF
'
@@ -2327,6 +2347,7 @@ test_expect_success 'git -c - variable name' '
test_completion "git -c log.d" <<-\EOF
log.date=Z
log.decorate=Z
+ log.diffMerges=Z
EOF
'
@@ -2348,6 +2369,7 @@ test_expect_success 'git clone --config= - variable name' '
test_completion "git clone --config=log.d" <<-\EOF
log.date=Z
log.decorate=Z
+ log.diffMerges=Z
EOF
'
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index 6348e8d733..b823c14027 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -1025,13 +1025,6 @@ test_cmp_bin () {
cmp "$@"
}
-# Wrapper for test_cmp which used to be used for
-# GIT_TEST_GETTEXT_POISON=false. Only here as a shim for other
-# in-flight changes. Should not be used and will be removed soon.
-test_i18ncmp () {
- test_cmp "$@"
-}
-
# Wrapper for grep which used to be used for
# GIT_TEST_GETTEXT_POISON=false. Only here as a shim for other
# in-flight changes. Should not be used and will be removed soon.
diff --git a/t/test-lib.sh b/t/test-lib.sh
index d3f6af6a65..adaf03543e 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -448,6 +448,8 @@ export EDITOR
GIT_DEFAULT_HASH="${GIT_TEST_DEFAULT_HASH:-sha1}"
export GIT_DEFAULT_HASH
+GIT_TEST_MERGE_ALGORITHM="${GIT_TEST_MERGE_ALGORITHM:-ort}"
+export GIT_TEST_MERGE_ALGORITHM
# Tests using GIT_TRACE typically don't want <timestamp> <file>:<line> output
GIT_TRACE_BARE=1
@@ -945,8 +947,11 @@ test_run_ () {
trace=
# 117 is magic because it is unlikely to match the exit
# code of other programs
- if $(printf '%s\n' "$1" | sed -f "$GIT_BUILD_DIR/t/chainlint.sed" | grep -q '?![A-Z][A-Z]*?!') ||
- test "OK-117" != "$(test_eval_ "(exit 117) && $1${LF}${LF}echo OK-\$?" 3>&1)"
+ if test "OK-117" != "$(test_eval_ "(exit 117) && $1${LF}${LF}echo OK-\$?" 3>&1)" ||
+ {
+ test "${GIT_TEST_CHAIN_LINT_HARDER:-${GIT_TEST_CHAIN_LINT_HARDER_DEFAULT:-1}}" != 0 &&
+ $(printf '%s\n' "$1" | sed -f "$GIT_BUILD_DIR/t/chainlint.sed" | grep -q '?![A-Z][A-Z]*?!')
+ }
then
BUG "broken &&-chain or run-away HERE-DOC: $1"
fi
@@ -1457,6 +1462,7 @@ case $uname_s in
test_set_prereq NATIVE_CRLF
test_set_prereq SED_STRIPS_CR
test_set_prereq GREP_STRIPS_CR
+ test_set_prereq WINDOWS
GIT_TEST_CMP=mingw_test_cmp
;;
*CYGWIN*)
@@ -1465,6 +1471,7 @@ case $uname_s in
test_set_prereq CYGWIN
test_set_prereq SED_STRIPS_CR
test_set_prereq GREP_STRIPS_CR
+ test_set_prereq WINDOWS
;;
*)
test_set_prereq POSIXPERM
diff --git a/trailer.c b/trailer.c
index 249ed618ed..7c7cb61a94 100644
--- a/trailer.c
+++ b/trailer.c
@@ -14,6 +14,7 @@ struct conf_info {
char *name;
char *key;
char *command;
+ char *cmd;
enum trailer_where where;
enum trailer_if_exists if_exists;
enum trailer_if_missing if_missing;
@@ -127,6 +128,7 @@ static void free_arg_item(struct arg_item *item)
free(item->conf.name);
free(item->conf.key);
free(item->conf.command);
+ free(item->conf.cmd);
free(item->token);
free(item->value);
free(item);
@@ -174,7 +176,7 @@ static void print_all(FILE *outfile, struct list_head *head,
static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok)
{
- struct trailer_item *new_item = xcalloc(sizeof(*new_item), 1);
+ struct trailer_item *new_item = xcalloc(1, sizeof(*new_item));
new_item->token = arg_tok->token;
new_item->value = arg_tok->value;
arg_tok->token = arg_tok->value = NULL;
@@ -216,18 +218,24 @@ static int check_if_different(struct trailer_item *in_tok,
return 1;
}
-static char *apply_command(const char *command, const char *arg)
+static char *apply_command(struct conf_info *conf, const char *arg)
{
struct strbuf cmd = STRBUF_INIT;
struct strbuf buf = STRBUF_INIT;
struct child_process cp = CHILD_PROCESS_INIT;
char *result;
- strbuf_addstr(&cmd, command);
- if (arg)
- strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);
-
- strvec_push(&cp.args, cmd.buf);
+ if (conf->cmd) {
+ strbuf_addstr(&cmd, conf->cmd);
+ strvec_push(&cp.args, cmd.buf);
+ if (arg)
+ strvec_push(&cp.args, arg);
+ } else if (conf->command) {
+ strbuf_addstr(&cmd, conf->command);
+ if (arg)
+ strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);
+ strvec_push(&cp.args, cmd.buf);
+ }
cp.env = local_repo_env;
cp.no_stdin = 1;
cp.use_shell = 1;
@@ -247,7 +255,7 @@ static char *apply_command(const char *command, const char *arg)
static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)
{
- if (arg_tok->conf.command) {
+ if (arg_tok->conf.command || arg_tok->conf.cmd) {
const char *arg;
if (arg_tok->value && arg_tok->value[0]) {
arg = arg_tok->value;
@@ -257,7 +265,7 @@ static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg
else
arg = xstrdup("");
}
- arg_tok->value = apply_command(arg_tok->conf.command, arg);
+ arg_tok->value = apply_command(&arg_tok->conf, arg);
free((char *)arg);
}
}
@@ -430,6 +438,7 @@ static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)
dst->name = xstrdup_or_null(src->name);
dst->key = xstrdup_or_null(src->key);
dst->command = xstrdup_or_null(src->command);
+ dst->cmd = xstrdup_or_null(src->cmd);
}
static struct arg_item *get_conf_item(const char *name)
@@ -445,7 +454,7 @@ static struct arg_item *get_conf_item(const char *name)
}
/* Item does not already exists, create it */
- item = xcalloc(sizeof(*item), 1);
+ CALLOC_ARRAY(item, 1);
duplicate_conf(&item->conf, &default_conf_info);
item->conf.name = xstrdup(name);
@@ -454,8 +463,8 @@ static struct arg_item *get_conf_item(const char *name)
return item;
}
-enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,
- TRAILER_IF_EXISTS, TRAILER_IF_MISSING };
+enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_CMD,
+ TRAILER_WHERE, TRAILER_IF_EXISTS, TRAILER_IF_MISSING };
static struct {
const char *name;
@@ -463,6 +472,7 @@ static struct {
} trailer_config_items[] = {
{ "key", TRAILER_KEY },
{ "command", TRAILER_COMMAND },
+ { "cmd", TRAILER_CMD },
{ "where", TRAILER_WHERE },
{ "ifexists", TRAILER_IF_EXISTS },
{ "ifmissing", TRAILER_IF_MISSING }
@@ -542,6 +552,11 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)
warning(_("more than one %s"), conf_key);
conf->command = xstrdup(value);
break;
+ case TRAILER_CMD:
+ if (conf->cmd)
+ warning(_("more than one %s"), conf_key);
+ conf->cmd = xstrdup(value);
+ break;
case TRAILER_WHERE:
if (trailer_set_where(&conf->where, value))
warning(_("unknown value '%s' for key '%s'"), value, conf_key);
@@ -664,7 +679,7 @@ static void parse_trailer(struct strbuf *tok, struct strbuf *val,
static struct trailer_item *add_trailer_item(struct list_head *head, char *tok,
char *val)
{
- struct trailer_item *new_item = xcalloc(sizeof(*new_item), 1);
+ struct trailer_item *new_item = xcalloc(1, sizeof(*new_item));
new_item->token = tok;
new_item->value = val;
list_add_tail(&new_item->list, head);
@@ -675,7 +690,7 @@ static void add_arg_item(struct list_head *arg_head, char *tok, char *val,
const struct conf_info *conf,
const struct new_trailer_item *new_trailer_item)
{
- struct arg_item *new_item = xcalloc(sizeof(*new_item), 1);
+ struct arg_item *new_item = xcalloc(1, sizeof(*new_item));
new_item->token = tok;
new_item->value = val;
duplicate_conf(&new_item->conf, conf);
diff --git a/transport-helper.c b/transport-helper.c
index 49b7fb4dcb..4be035edb8 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -684,6 +684,16 @@ static int fetch(struct transport *transport,
return transport->vtable->fetch(transport, nr_heads, to_fetch);
}
+ /*
+ * If we reach here, then the server, the client, and/or the transport
+ * helper does not support protocol v2. --negotiate-only requires
+ * protocol v2.
+ */
+ if (data->transport_options.acked_commits) {
+ warning(_("--negotiate-only requires protocol v2"));
+ return -1;
+ }
+
if (!data->get_refs_list_called)
get_refs_list_using_list(transport, 0);
@@ -745,13 +755,13 @@ static int push_update_ref_status(struct strbuf *buf,
die(_("'option' without a matching 'ok/error' directive"));
if (state->new_report) {
if (!state->hint->report) {
- state->hint->report = xcalloc(1, sizeof(struct ref_push_report));
+ CALLOC_ARRAY(state->hint->report, 1);
state->report = state->hint->report;
} else {
state->report = state->hint->report;
while (state->report->next)
state->report = state->report->next;
- state->report->next = xcalloc(1, sizeof(struct ref_push_report));
+ CALLOC_ARRAY(state->report->next, 1);
state->report = state->report->next;
}
state->new_report = 0;
diff --git a/transport.c b/transport.c
index b13fab5dc3..6cf3da19eb 100644
--- a/transport.c
+++ b/transport.c
@@ -108,11 +108,11 @@ static void set_upstreams(struct transport *transport, struct ref *refs,
if (!remotename || !starts_with(remotename, "refs/heads/"))
continue;
- if (!pretend)
- install_branch_config(BRANCH_CONFIG_VERBOSE,
- localname + 11, transport->remote->name,
- remotename);
- else
+ if (!pretend) {
+ int flag = transport->verbose < 0 ? 0 : BRANCH_CONFIG_VERBOSE;
+ install_branch_config(flag, localname + 11,
+ transport->remote->name, remotename);
+ } else if (transport->verbose >= 0)
printf(_("Would set upstream of '%s' to '%s' of '%s'\n"),
localname + 11, remotename + 11,
transport->remote->name);
@@ -236,6 +236,9 @@ static int set_git_option(struct git_transport_options *opts,
list_objects_filter_die_if_populated(&opts->filter_options);
parse_list_objects_filter(&opts->filter_options, value);
return 0;
+ } else if (!strcmp(name, TRANS_OPT_REJECT_SHALLOW)) {
+ opts->reject_shallow = !!value;
+ return 0;
}
return 1;
}
@@ -370,6 +373,7 @@ static int fetch_refs_via_pack(struct transport *transport,
args.stateless_rpc = transport->stateless_rpc;
args.server_options = transport->server_options;
args.negotiation_tips = data->options.negotiation_tips;
+ args.reject_shallow_remote = transport->smart_options->reject_shallow;
if (!data->got_remote_heads) {
int i;
@@ -388,16 +392,29 @@ static int fetch_refs_via_pack(struct transport *transport,
else if (data->version <= protocol_v1)
die_if_server_options(transport);
+ if (data->options.acked_commits) {
+ if (data->version < protocol_v2) {
+ warning(_("--negotiate-only requires protocol v2"));
+ ret = -1;
+ } else if (!server_supports_feature("fetch", "wait-for-done", 0)) {
+ warning(_("server does not support wait-for-done"));
+ ret = -1;
+ } else {
+ negotiate_using_fetch(data->options.negotiation_tips,
+ transport->server_options,
+ transport->stateless_rpc,
+ data->fd,
+ data->options.acked_commits);
+ ret = 0;
+ }
+ goto cleanup;
+ }
+
refs = fetch_pack(&args, data->fd,
refs_tmp ? refs_tmp : transport->remote_refs,
to_fetch, nr_heads, &data->shallow,
&transport->pack_lockfiles, data->version);
- close(data->fd[0]);
- close(data->fd[1]);
- if (finish_connect(data->conn))
- ret = -1;
- data->conn = NULL;
data->got_remote_heads = 0;
data->options.self_contained_and_connected =
args.self_contained_and_connected;
@@ -408,6 +425,13 @@ static int fetch_refs_via_pack(struct transport *transport,
if (report_unmatched_refs(to_fetch, nr_heads))
ret = -1;
+cleanup:
+ close(data->fd[0]);
+ close(data->fd[1]);
+ if (finish_connect(data->conn))
+ ret = -1;
+ data->conn = NULL;
+
free_refs(refs_tmp);
free_refs(refs);
return ret;
@@ -871,7 +895,7 @@ void transport_take_over(struct transport *transport,
BUG("taking over transport requires non-NULL "
"smart_options field.");
- data = xcalloc(1, sizeof(*data));
+ CALLOC_ARRAY(data, 1);
data->options = *transport->smart_options;
data->conn = child;
data->fd[0] = data->conn->out;
@@ -1452,6 +1476,8 @@ int transport_disconnect(struct transport *transport)
int ret = 0;
if (transport->vtable->disconnect)
ret = transport->vtable->disconnect(transport);
+ if (transport->got_remote_refs)
+ free_refs((void *)transport->remote_refs);
free(transport);
return ret;
}
diff --git a/transport.h b/transport.h
index 24e15799e7..1cbab11373 100644
--- a/transport.h
+++ b/transport.h
@@ -14,6 +14,7 @@ struct git_transport_options {
unsigned check_self_contained_and_connected : 1;
unsigned self_contained_and_connected : 1;
unsigned update_shallow : 1;
+ unsigned reject_shallow : 1;
unsigned deepen_relative : 1;
/* see documentation of corresponding flag in fetch-pack.h */
@@ -46,6 +47,12 @@ struct git_transport_options {
* transport_set_option().
*/
struct oid_array *negotiation_tips;
+
+ /*
+ * If allocated, whenever transport_fetch_refs() is called, add known
+ * common commits to this oidset instead of fetching any packfiles.
+ */
+ struct oidset *acked_commits;
};
enum transport_family {
@@ -194,6 +201,9 @@ void transport_check_allowed(const char *type);
/* Aggressively fetch annotated tags if possible */
#define TRANS_OPT_FOLLOWTAGS "followtags"
+/* Reject shallow repo transport */
+#define TRANS_OPT_REJECT_SHALLOW "rejectshallow"
+
/* Accept refs that may update .git/shallow without --depth */
#define TRANS_OPT_UPDATE_SHALLOW "updateshallow"
diff --git a/tree-diff.c b/tree-diff.c
index 7cebbb327e..1572615bd9 100644
--- a/tree-diff.c
+++ b/tree-diff.c
@@ -161,7 +161,7 @@ static struct combine_diff_path *path_appendnew(struct combine_diff_path *last,
memcpy(p->path + base->len, path, pathlen);
p->path[len] = 0;
p->mode = mode;
- oidcpy(&p->oid, oid ? oid : &null_oid);
+ oidcpy(&p->oid, oid ? oid : null_oid());
return p;
}
@@ -243,7 +243,7 @@ static struct combine_diff_path *emit_path(struct combine_diff_path *p,
mode_i = tp[i].entry.mode;
}
else {
- oid_i = &null_oid;
+ oid_i = null_oid();
mode_i = 0;
}
diff --git a/tree-walk.c b/tree-walk.c
index 2d6226d5f1..3a94959d64 100644
--- a/tree-walk.c
+++ b/tree-walk.c
@@ -49,7 +49,7 @@ static int decode_tree_entry(struct tree_desc *desc, const char *buf, unsigned l
desc->entry.path = path;
desc->entry.mode = canon_mode(mode);
desc->entry.pathlen = len - 1;
- hashcpy(desc->entry.oid.hash, (const unsigned char *)path + len);
+ oidread(&desc->entry.oid, (const unsigned char *)path + len);
return 0;
}
diff --git a/tree.c b/tree.c
index a52479812c..410e3b477e 100644
--- a/tree.c
+++ b/tree.c
@@ -11,58 +11,10 @@
const char *tree_type = "tree";
-static int read_one_entry_opt(struct index_state *istate,
- const struct object_id *oid,
- const char *base, int baselen,
- const char *pathname,
- unsigned mode, int stage, int opt)
-{
- int len;
- struct cache_entry *ce;
-
- if (S_ISDIR(mode))
- return READ_TREE_RECURSIVE;
-
- len = strlen(pathname);
- ce = make_empty_cache_entry(istate, baselen + len);
-
- ce->ce_mode = create_ce_mode(mode);
- ce->ce_flags = create_ce_flags(stage);
- ce->ce_namelen = baselen + len;
- memcpy(ce->name, base, baselen);
- memcpy(ce->name + baselen, pathname, len+1);
- oidcpy(&ce->oid, oid);
- return add_index_entry(istate, ce, opt);
-}
-
-static int read_one_entry(const struct object_id *oid, struct strbuf *base,
- const char *pathname, unsigned mode, int stage,
- void *context)
-{
- struct index_state *istate = context;
- return read_one_entry_opt(istate, oid, base->buf, base->len, pathname,
- mode, stage,
- ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK);
-}
-
-/*
- * This is used when the caller knows there is no existing entries at
- * the stage that will conflict with the entry being added.
- */
-static int read_one_entry_quick(const struct object_id *oid, struct strbuf *base,
- const char *pathname, unsigned mode, int stage,
- void *context)
-{
- struct index_state *istate = context;
- return read_one_entry_opt(istate, oid, base->buf, base->len, pathname,
- mode, stage,
- ADD_CACHE_JUST_APPEND);
-}
-
-static int read_tree_1(struct repository *r,
- struct tree *tree, struct strbuf *base,
- int stage, const struct pathspec *pathspec,
- read_tree_fn_t fn, void *context)
+int read_tree_at(struct repository *r,
+ struct tree *tree, struct strbuf *base,
+ const struct pathspec *pathspec,
+ read_tree_fn_t fn, void *context)
{
struct tree_desc desc;
struct name_entry entry;
@@ -86,7 +38,7 @@ static int read_tree_1(struct repository *r,
}
switch (fn(&entry.oid, base,
- entry.path, entry.mode, stage, context)) {
+ entry.path, entry.mode, context)) {
case 0:
continue;
case READ_TREE_RECURSIVE:
@@ -119,9 +71,9 @@ static int read_tree_1(struct repository *r,
len = tree_entry_len(&entry);
strbuf_add(base, entry.path, len);
strbuf_addch(base, '/');
- retval = read_tree_1(r, lookup_tree(r, &oid),
- base, stage, pathspec,
- fn, context);
+ retval = read_tree_at(r, lookup_tree(r, &oid),
+ base, pathspec,
+ fn, context);
strbuf_setlen(base, oldlen);
if (retval)
return -1;
@@ -129,17 +81,13 @@ static int read_tree_1(struct repository *r,
return 0;
}
-int read_tree_recursive(struct repository *r,
- struct tree *tree,
- const char *base, int baselen,
- int stage, const struct pathspec *pathspec,
- read_tree_fn_t fn, void *context)
+int read_tree(struct repository *r,
+ struct tree *tree,
+ const struct pathspec *pathspec,
+ read_tree_fn_t fn, void *context)
{
struct strbuf sb = STRBUF_INIT;
- int ret;
-
- strbuf_add(&sb, base, baselen);
- ret = read_tree_1(r, tree, &sb, stage, pathspec, fn, context);
+ int ret = read_tree_at(r, tree, &sb, pathspec, fn, context);
strbuf_release(&sb);
return ret;
}
@@ -154,47 +102,6 @@ int cmp_cache_name_compare(const void *a_, const void *b_)
ce2->name, ce2->ce_namelen, ce_stage(ce2));
}
-int read_tree(struct repository *r, struct tree *tree, int stage,
- struct pathspec *match, struct index_state *istate)
-{
- read_tree_fn_t fn = NULL;
- int i, err;
-
- /*
- * Currently the only existing callers of this function all
- * call it with stage=1 and after making sure there is nothing
- * at that stage; we could always use read_one_entry_quick().
- *
- * But when we decide to straighten out git-read-tree not to
- * use unpack_trees() in some cases, this will probably start
- * to matter.
- */
-
- /*
- * See if we have cache entry at the stage. If so,
- * do it the original slow way, otherwise, append and then
- * sort at the end.
- */
- for (i = 0; !fn && i < istate->cache_nr; i++) {
- const struct cache_entry *ce = istate->cache[i];
- if (ce_stage(ce) == stage)
- fn = read_one_entry;
- }
-
- if (!fn)
- fn = read_one_entry_quick;
- err = read_tree_recursive(r, tree, "", 0, stage, match, fn, istate);
- if (fn == read_one_entry || err)
- return err;
-
- /*
- * Sort the cache entry -- we need to nuke the cache tree, though.
- */
- cache_tree_free(&istate->cache_tree);
- QSORT(istate->cache, istate->cache_nr, cmp_cache_name_compare);
- return 0;
-}
-
struct tree *lookup_tree(struct repository *r, const struct object_id *oid)
{
struct object *obj = lookup_object(r, oid);
diff --git a/tree.h b/tree.h
index 3eb0484cbf..6efff003e2 100644
--- a/tree.h
+++ b/tree.h
@@ -31,16 +31,16 @@ struct tree *parse_tree_indirect(const struct object_id *oid);
int cmp_cache_name_compare(const void *a_, const void *b_);
#define READ_TREE_RECURSIVE 1
-typedef int (*read_tree_fn_t)(const struct object_id *, struct strbuf *, const char *, unsigned int, int, void *);
+typedef int (*read_tree_fn_t)(const struct object_id *, struct strbuf *, const char *, unsigned int, void *);
-int read_tree_recursive(struct repository *r,
- struct tree *tree,
- const char *base, int baselen,
- int stage, const struct pathspec *pathspec,
- read_tree_fn_t fn, void *context);
+int read_tree_at(struct repository *r,
+ struct tree *tree, struct strbuf *base,
+ const struct pathspec *pathspec,
+ read_tree_fn_t fn, void *context);
-int read_tree(struct repository *r, struct tree *tree,
- int stage, struct pathspec *pathspec,
- struct index_state *istate);
+int read_tree(struct repository *r,
+ struct tree *tree,
+ const struct pathspec *pathspec,
+ read_tree_fn_t fn, void *context);
#endif /* TREE_H */
diff --git a/unix-socket.c b/unix-socket.c
index 19ed48be99..e0be1badb5 100644
--- a/unix-socket.c
+++ b/unix-socket.c
@@ -1,13 +1,7 @@
#include "cache.h"
#include "unix-socket.h"
-static int unix_stream_socket(void)
-{
- int fd = socket(AF_UNIX, SOCK_STREAM, 0);
- if (fd < 0)
- die_errno("unable to create socket");
- return fd;
-}
+#define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)
static int chdir_len(const char *orig, int len)
{
@@ -36,16 +30,23 @@ static void unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx)
}
static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,
- struct unix_sockaddr_context *ctx)
+ struct unix_sockaddr_context *ctx,
+ int disallow_chdir)
{
int size = strlen(path) + 1;
ctx->orig_dir = NULL;
if (size > sizeof(sa->sun_path)) {
- const char *slash = find_last_dir_sep(path);
+ const char *slash;
const char *dir;
struct strbuf cwd = STRBUF_INIT;
+ if (disallow_chdir) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ slash = find_last_dir_sep(path);
if (!slash) {
errno = ENAMETOOLONG;
return -1;
@@ -71,15 +72,18 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,
return 0;
}
-int unix_stream_connect(const char *path)
+int unix_stream_connect(const char *path, int disallow_chdir)
{
- int fd, saved_errno;
+ int fd = -1, saved_errno;
struct sockaddr_un sa;
struct unix_sockaddr_context ctx;
- if (unix_sockaddr_init(&sa, path, &ctx) < 0)
+ if (unix_sockaddr_init(&sa, path, &ctx, disallow_chdir) < 0)
return -1;
- fd = unix_stream_socket();
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
+ goto fail;
+
if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)
goto fail;
unix_sockaddr_cleanup(&ctx);
@@ -87,28 +91,36 @@ int unix_stream_connect(const char *path)
fail:
saved_errno = errno;
+ if (fd != -1)
+ close(fd);
unix_sockaddr_cleanup(&ctx);
- close(fd);
errno = saved_errno;
return -1;
}
-int unix_stream_listen(const char *path)
+int unix_stream_listen(const char *path,
+ const struct unix_stream_listen_opts *opts)
{
- int fd, saved_errno;
+ int fd = -1, saved_errno;
+ int backlog;
struct sockaddr_un sa;
struct unix_sockaddr_context ctx;
unlink(path);
- if (unix_sockaddr_init(&sa, path, &ctx) < 0)
+ if (unix_sockaddr_init(&sa, path, &ctx, opts->disallow_chdir) < 0)
return -1;
- fd = unix_stream_socket();
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
+ goto fail;
if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)
goto fail;
- if (listen(fd, 5) < 0)
+ backlog = opts->listen_backlog_size;
+ if (backlog <= 0)
+ backlog = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG;
+ if (listen(fd, backlog) < 0)
goto fail;
unix_sockaddr_cleanup(&ctx);
@@ -116,8 +128,9 @@ int unix_stream_listen(const char *path)
fail:
saved_errno = errno;
+ if (fd != -1)
+ close(fd);
unix_sockaddr_cleanup(&ctx);
- close(fd);
errno = saved_errno;
return -1;
}
diff --git a/unix-socket.h b/unix-socket.h
index e271aeec5a..8542cdd799 100644
--- a/unix-socket.h
+++ b/unix-socket.h
@@ -1,7 +1,15 @@
#ifndef UNIX_SOCKET_H
#define UNIX_SOCKET_H
-int unix_stream_connect(const char *path);
-int unix_stream_listen(const char *path);
+struct unix_stream_listen_opts {
+ int listen_backlog_size;
+ unsigned int disallow_chdir:1;
+};
+
+#define UNIX_STREAM_LISTEN_OPTS_INIT { 0 }
+
+int unix_stream_connect(const char *path, int disallow_chdir);
+int unix_stream_listen(const char *path,
+ const struct unix_stream_listen_opts *opts);
#endif /* UNIX_SOCKET_H */
diff --git a/unix-stream-server.c b/unix-stream-server.c
new file mode 100644
index 0000000000..efa2a207ab
--- /dev/null
+++ b/unix-stream-server.c
@@ -0,0 +1,125 @@
+#include "cache.h"
+#include "lockfile.h"
+#include "unix-socket.h"
+#include "unix-stream-server.h"
+
+#define DEFAULT_LOCK_TIMEOUT (100)
+
+/*
+ * Try to connect to a unix domain socket at `path` (if it exists) and
+ * see if there is a server listening.
+ *
+ * We don't know if the socket exists, whether a server died and
+ * failed to cleanup, or whether we have a live server listening, so
+ * we "poke" it.
+ *
+ * We immediately hangup without sending/receiving any data because we
+ * don't know anything about the protocol spoken and don't want to
+ * block while writing/reading data. It is sufficient to just know
+ * that someone is listening.
+ */
+static int is_another_server_alive(const char *path,
+ const struct unix_stream_listen_opts *opts)
+{
+ int fd = unix_stream_connect(path, opts->disallow_chdir);
+ if (fd >= 0) {
+ close(fd);
+ return 1;
+ }
+
+ return 0;
+}
+
+int unix_ss_create(const char *path,
+ const struct unix_stream_listen_opts *opts,
+ long timeout_ms,
+ struct unix_ss_socket **new_server_socket)
+{
+ struct lock_file lock = LOCK_INIT;
+ int fd_socket;
+ struct unix_ss_socket *server_socket;
+
+ *new_server_socket = NULL;
+
+ if (timeout_ms < 0)
+ timeout_ms = DEFAULT_LOCK_TIMEOUT;
+
+ /*
+ * Create a lock at "<path>.lock" if we can.
+ */
+ if (hold_lock_file_for_update_timeout(&lock, path, 0, timeout_ms) < 0)
+ return -1;
+
+ /*
+ * If another server is listening on "<path>" give up. We do not
+ * want to create a socket and steal future connections from them.
+ */
+ if (is_another_server_alive(path, opts)) {
+ rollback_lock_file(&lock);
+ errno = EADDRINUSE;
+ return -2;
+ }
+
+ /*
+ * Create and bind to a Unix domain socket at "<path>".
+ */
+ fd_socket = unix_stream_listen(path, opts);
+ if (fd_socket < 0) {
+ int saved_errno = errno;
+ rollback_lock_file(&lock);
+ errno = saved_errno;
+ return -1;
+ }
+
+ server_socket = xcalloc(1, sizeof(*server_socket));
+ server_socket->path_socket = strdup(path);
+ server_socket->fd_socket = fd_socket;
+ lstat(path, &server_socket->st_socket);
+
+ *new_server_socket = server_socket;
+
+ /*
+ * Always rollback (just delete) "<path>.lock" because we already created
+ * "<path>" as a socket and do not want to commit_lock to do the atomic
+ * rename trick.
+ */
+ rollback_lock_file(&lock);
+
+ return 0;
+}
+
+void unix_ss_free(struct unix_ss_socket *server_socket)
+{
+ if (!server_socket)
+ return;
+
+ if (server_socket->fd_socket >= 0) {
+ if (!unix_ss_was_stolen(server_socket))
+ unlink(server_socket->path_socket);
+ close(server_socket->fd_socket);
+ }
+
+ free(server_socket->path_socket);
+ free(server_socket);
+}
+
+int unix_ss_was_stolen(struct unix_ss_socket *server_socket)
+{
+ struct stat st_now;
+
+ if (!server_socket)
+ return 0;
+
+ if (lstat(server_socket->path_socket, &st_now) == -1)
+ return 1;
+
+ if (st_now.st_ino != server_socket->st_socket.st_ino)
+ return 1;
+ if (st_now.st_dev != server_socket->st_socket.st_dev)
+ return 1;
+
+ if (!S_ISSOCK(st_now.st_mode))
+ return 1;
+
+ return 0;
+}
diff --git a/unix-stream-server.h b/unix-stream-server.h
new file mode 100644
index 0000000000..ae2712ba39
--- /dev/null
+++ b/unix-stream-server.h
@@ -0,0 +1,33 @@
+#ifndef UNIX_STREAM_SERVER_H
+#define UNIX_STREAM_SERVER_H
+
+#include "unix-socket.h"
+
+struct unix_ss_socket {
+ char *path_socket;
+ struct stat st_socket;
+ int fd_socket;
+};
+
+/*
+ * Create a Unix Domain Socket at the given path under the protection
+ * of a '.lock' lockfile.
+ *
+ * Returns 0 on success, -1 on error, -2 if socket is in use.
+ */
+int unix_ss_create(const char *path,
+ const struct unix_stream_listen_opts *opts,
+ long timeout_ms,
+ struct unix_ss_socket **server_socket);
+
+/*
+ * Close and delete the socket.
+ */
+void unix_ss_free(struct unix_ss_socket *server_socket);
+
+/*
+ * Return 1 if the inode of the pathname to our socket changes.
+ */
+int unix_ss_was_stolen(struct unix_ss_socket *server_socket);
+
+#endif /* UNIX_STREAM_SERVER_H */
diff --git a/unpack-trees.c b/unpack-trees.c
index eb8fcda31b..f88a69f8e7 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -16,6 +16,8 @@
#include "fsmonitor.h"
#include "object-store.h"
#include "promisor-remote.h"
+#include "entry.h"
+#include "parallel-checkout.h"
/*
* Error messages expected by scripts out of plumbing commands such as
@@ -397,7 +399,7 @@ static int check_updates(struct unpack_trees_options *o,
int errs = 0;
struct progress *progress;
struct checkout state = CHECKOUT_INIT;
- int i;
+ int i, pc_workers, pc_threshold;
trace_performance_enter();
state.force = 1;
@@ -440,7 +442,6 @@ static int check_updates(struct unpack_trees_options *o,
if (should_update_submodules())
load_gitmodules_file(index, &state);
- enable_delayed_checkout(&state);
if (has_promisor_remote()) {
/*
* Prefetch the objects that are to be checked out in the loop
@@ -463,18 +464,31 @@ static int check_updates(struct unpack_trees_options *o,
to_fetch.oid, to_fetch.nr);
oid_array_clear(&to_fetch);
}
+
+ get_parallel_checkout_configs(&pc_workers, &pc_threshold);
+
+ enable_delayed_checkout(&state);
+ if (pc_workers > 1)
+ init_parallel_checkout();
for (i = 0; i < index->cache_nr; i++) {
struct cache_entry *ce = index->cache[i];
if (ce->ce_flags & CE_UPDATE) {
+ size_t last_pc_queue_size = pc_queue_size();
+
if (ce->ce_flags & CE_WT_REMOVE)
BUG("both update and delete flags are set on %s",
ce->name);
- display_progress(progress, ++cnt);
ce->ce_flags &= ~CE_UPDATE;
errs |= checkout_entry(ce, &state, NULL, NULL);
+
+ if (last_pc_queue_size == pc_queue_size())
+ display_progress(progress, ++cnt);
}
}
+ if (pc_workers > 1)
+ errs |= run_parallel_checkout(&state, pc_workers, pc_threshold,
+ progress, &cnt);
stop_progress(&progress);
errs |= finish_delayed_checkout(&state, NULL);
git_attr_set_direction(GIT_ATTR_CHECKIN);
@@ -749,9 +763,13 @@ static int index_pos_by_traverse_info(struct name_entry *names,
strbuf_make_traverse_path(&name, info, names->path, names->pathlen);
strbuf_addch(&name, '/');
pos = index_name_pos(o->src_index, name.buf, name.len);
- if (pos >= 0)
- BUG("This is a directory and should not exist in index");
- pos = -pos - 1;
+ if (pos >= 0) {
+ if (!o->src_index->sparse_index ||
+ !(o->src_index->cache[pos]->ce_flags & CE_SKIP_WORKTREE))
+ BUG("This is a directory and should not exist in index");
+ } else {
+ pos = -pos - 1;
+ }
if (pos >= o->src_index->cache_nr ||
!starts_with(o->src_index->cache[pos]->name, name.buf) ||
(pos > 0 && starts_with(o->src_index->cache[pos-1]->name, name.buf)))
@@ -1020,7 +1038,7 @@ static struct cache_entry *create_ce_entry(const struct traverse_info *info,
size_t len = traverse_path_len(info, tree_entry_len(n));
struct cache_entry *ce =
is_transient ?
- make_empty_transient_cache_entry(len) :
+ make_empty_transient_cache_entry(len, NULL) :
make_empty_cache_entry(istate, len);
ce->ce_mode = create_ce_mode(n->mode);
@@ -1570,6 +1588,7 @@ static int verify_absent(const struct cache_entry *,
*/
int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o)
{
+ struct repository *repo = the_repository;
int i, ret;
static struct cache_entry *dfc;
struct pattern_list pl;
@@ -1581,6 +1600,12 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
trace_performance_enter();
trace2_region_enter("unpack_trees", "unpack_trees", the_repository);
+ prepare_repo_settings(repo);
+ if (repo->settings.command_requires_full_index) {
+ ensure_full_index(o->src_index);
+ ensure_full_index(o->dst_index);
+ }
+
if (!core_apply_sparse_checkout || !o->update)
o->skip_sparse_checkout = 1;
if (!o->skip_sparse_checkout && !o->pl) {
@@ -1612,8 +1637,8 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
o->merge_size = len;
mark_all_ce_unused(o->src_index);
- if (o->src_index->fsmonitor_last_update)
- o->result.fsmonitor_last_update = o->src_index->fsmonitor_last_update;
+ o->result.fsmonitor_last_update =
+ xstrdup_or_null(o->src_index->fsmonitor_last_update);
/*
* Sparse checkout loop #1: set NEW_SKIP_WORKTREE on existing entries
@@ -2097,7 +2122,7 @@ static int verify_absent_1(const struct cache_entry *ce,
if (o->index_only || o->reset || !o->update)
return 0;
- len = check_leading_path(ce->name, ce_namelen(ce));
+ len = check_leading_path(ce->name, ce_namelen(ce), 0);
if (!len)
return 0;
else if (len > 0) {
@@ -2563,3 +2588,25 @@ int oneway_merge(const struct cache_entry * const *src,
}
return merged_entry(a, old, o);
}
+
+/*
+ * Merge worktree and untracked entries in a stash entry.
+ *
+ * Ignore all index entries. Collapse remaining trees but make sure that they
+ * don't have any conflicting files.
+ */
+int stash_worktree_untracked_merge(const struct cache_entry * const *src,
+ struct unpack_trees_options *o)
+{
+ const struct cache_entry *worktree = src[1];
+ const struct cache_entry *untracked = src[2];
+
+ if (o->merge_size != 2)
+ BUG("invalid merge_size: %d", o->merge_size);
+
+ if (worktree && untracked)
+ return error(_("worktree and untracked commit have duplicate entries: %s"),
+ super_prefixed(worktree->name));
+
+ return merged_entry(worktree ? worktree : untracked, NULL, o);
+}
diff --git a/unpack-trees.h b/unpack-trees.h
index 2e87875b15..2d88b19dca 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -114,5 +114,7 @@ int bind_merge(const struct cache_entry * const *src,
struct unpack_trees_options *o);
int oneway_merge(const struct cache_entry * const *src,
struct unpack_trees_options *o);
+int stash_worktree_untracked_merge(const struct cache_entry * const *src,
+ struct unpack_trees_options *o);
#endif
diff --git a/upload-pack.c b/upload-pack.c
index e19583ae0f..297b76fcb4 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -103,6 +103,7 @@ struct upload_pack_data {
unsigned use_ofs_delta : 1;
unsigned no_progress : 1;
unsigned use_include_tag : 1;
+ unsigned wait_for_done : 1;
unsigned allow_filter : 1;
unsigned allow_filter_fallback : 1;
unsigned long tree_filter_max_depth;
@@ -1153,7 +1154,7 @@ static void receive_needs(struct upload_pack_data *data,
static int mark_our_ref(const char *refname, const char *refname_full,
const struct object_id *oid)
{
- struct object *o = lookup_unknown_object(oid);
+ struct object *o = lookup_unknown_object(the_repository, oid);
if (ref_is_hidden(refname, refname_full)) {
o->flags |= HIDDEN_REF;
@@ -1496,6 +1497,10 @@ static void process_args(struct packet_reader *request,
data->done = 1;
continue;
}
+ if (!strcmp(arg, "wait-for-done")) {
+ data->wait_for_done = 1;
+ continue;
+ }
/* Shallow related arguments */
if (process_shallow(arg, &data->shallows))
@@ -1578,7 +1583,7 @@ static int send_acks(struct upload_pack_data *data, struct oid_array *acks)
oid_to_hex(&acks->oid[i]));
}
- if (ok_to_give_up(data)) {
+ if (!data->wait_for_done && ok_to_give_up(data)) {
/* Send Ready */
packet_writer_write(&data->writer, "ready\n");
return 1;
@@ -1668,10 +1673,13 @@ int upload_pack_v2(struct repository *r, struct strvec *keys,
case FETCH_PROCESS_ARGS:
process_args(request, &data);
- if (!data.want_obj.nr) {
+ if (!data.want_obj.nr && !data.wait_for_done) {
/*
- * Request didn't contain any 'want' lines,
- * guess they didn't want anything.
+ * Request didn't contain any 'want' lines (and
+ * the request does not contain
+ * "wait-for-done", in which it is reasonable
+ * to just send 'have's without 'want's); guess
+ * they didn't want anything.
*/
state = FETCH_DONE;
} else if (data.haves.nr) {
@@ -1723,7 +1731,7 @@ int upload_pack_advertise(struct repository *r,
int allow_sideband_all_value;
char *str = NULL;
- strbuf_addstr(value, "shallow");
+ strbuf_addstr(value, "shallow wait-for-done");
if (!repo_config_get_bool(the_repository,
"uploadpack.allowfilter",
diff --git a/usage.c b/usage.c
index 1b206de36d..c7d233b0de 100644
--- a/usage.c
+++ b/usage.c
@@ -55,12 +55,13 @@ static NORETURN void usage_builtin(const char *err, va_list params)
exit(129);
}
+/*
+ * We call trace2_cmd_error_va() in the below functions first and
+ * expect it to va_copy 'params' before using it (because an 'ap' can
+ * only be walked once).
+ */
static NORETURN void die_builtin(const char *err, va_list params)
{
- /*
- * We call this trace2 function first and expect it to va_copy 'params'
- * before using it (because an 'ap' can only be walked once).
- */
trace2_cmd_error_va(err, params);
vreportf("fatal: ", err, params);
@@ -70,10 +71,6 @@ static NORETURN void die_builtin(const char *err, va_list params)
static void error_builtin(const char *err, va_list params)
{
- /*
- * We call this trace2 function first and expect it to va_copy 'params'
- * before using it (because an 'ap' can only be walked once).
- */
trace2_cmd_error_va(err, params);
vreportf("error: ", err, params);
@@ -81,10 +78,6 @@ static void error_builtin(const char *err, va_list params)
static void warn_builtin(const char *warn, va_list params)
{
- /*
- * We call this trace2 function first and expect it to va_copy 'params'
- * before using it (because an 'ap' can only be walked once).
- */
trace2_cmd_error_va(warn, params);
vreportf("warning: ", warn, params);
diff --git a/userdiff.c b/userdiff.c
index 3f81a2261c..3c3bbe38b0 100644
--- a/userdiff.c
+++ b/userdiff.c
@@ -44,6 +44,46 @@ PATTERNS("bash",
/* -- */
/* Characters not in the default $IFS value */
"[^ \t]+"),
+PATTERNS("bibtex",
+ "(@[a-zA-Z]{1,}[ \t]*\\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$",
+ /* -- */
+ "[={}\"]|[^={}\" \t]+"),
+PATTERNS("cpp",
+ /* Jump targets or access declarations */
+ "!^[ \t]*[A-Za-z_][A-Za-z_0-9]*:[[:space:]]*($|/[/*])\n"
+ /* functions/methods, variables, and compounds at top level */
+ "^((::[[:space:]]*)?[A-Za-z_].*)$",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lLuU]*"
+ "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->\\*?|\\.\\*"),
+PATTERNS("csharp",
+ /* Keywords */
+ "!^[ \t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\n"
+ /* Methods and constructors */
+ "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe|async)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[<>@._[:alnum:]]+[ \t]*\\(.*\\))[ \t]*$\n"
+ /* Properties */
+ "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[@._[:alnum:]]+)[ \t]*$\n"
+ /* Type definitions */
+ "^[ \t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \t]+)*(class|enum|interface|struct)[ \t]+.*)$\n"
+ /* Namespace */
+ "^[ \t]*(namespace[ \t]+.*)$",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
+ "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"),
+IPATTERN("css",
+ "![:;][[:space:]]*$\n"
+ "^[:[@.#]?[_a-z0-9].*$",
+ /* -- */
+ /*
+ * This regex comes from W3C CSS specs. Should theoretically also
+ * allow ISO 10646 characters U+00A0 and higher,
+ * but they are not handled in this regex.
+ */
+ "-?[_a-zA-Z][-_a-zA-Z0-9]*" /* identifiers */
+ "|-?[0-9]+|\\#[0-9a-fA-F]+" /* numbers */
+),
PATTERNS("dts",
"!;\n"
"!=\n"
@@ -83,7 +123,9 @@ IPATTERN("fortran",
* they would have been matched above as a variable anyway. */
"|[-+]?[0-9.]+([AaIiDdEeFfLlTtXx][Ss]?[-+]?[0-9.]*)?(_[a-zA-Z0-9][a-zA-Z0-9_]*)?"
"|//|\\*\\*|::|[/<>=]="),
-IPATTERN("fountain", "^((\\.[^.]|(int|ext|est|int\\.?/ext|i/e)[. ]).*)$",
+IPATTERN("fountain",
+ "^((\\.[^.]|(int|ext|est|int\\.?/ext|i/e)[. ]).*)$",
+ /* -- */
"[^ \t-]+"),
PATTERNS("golang",
/* Functions */
@@ -94,7 +136,9 @@ PATTERNS("golang",
"[a-zA-Z_][a-zA-Z0-9_]*"
"|[-+0-9.eE]+i?|0[xX]?[0-9a-fA-F]+i?"
"|[-+*/<>%&^|=!:]=|--|\\+\\+|<<=?|>>=?|&\\^=?|&&|\\|\\||<-|\\.{3}"),
-PATTERNS("html", "^[ \t]*(<[Hh][1-6]([ \t].*)?>.*)$",
+PATTERNS("html",
+ "^[ \t]*(<[Hh][1-6]([ \t].*)?>.*)$",
+ /* -- */
"[^<>= \t]+"),
PATTERNS("java",
"!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n"
@@ -106,6 +150,7 @@ PATTERNS("java",
"|--|\\+\\+|<<=?|>>>?=?|&&|\\|\\|"),
PATTERNS("markdown",
"^ {0,3}#{1,6}[ \t].*",
+ /* -- */
"[^<>= \t]+"),
PATTERNS("matlab",
/*
@@ -114,6 +159,7 @@ PATTERNS("matlab",
* that is understood by both.
*/
"^[[:space:]]*((classdef|function)[[:space:]].*)$|^(%%%?|##)[[:space:]].*$",
+ /* -- */
"[a-zA-Z_][a-zA-Z0-9_]*|[-+0-9.e]+|[=~<>]=|\\.[*/\\^']|\\|\\||&&"),
PATTERNS("objc",
/* Negate C statements that can look like functions */
@@ -129,9 +175,8 @@ PATTERNS("objc",
"|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
"|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"),
PATTERNS("pascal",
- "^(((class[ \t]+)?(procedure|function)|constructor|destructor|interface|"
- "implementation|initialization|finalization)[ \t]*.*)$"
- "\n"
+ "^(((class[ \t]+)?(procedure|function)|constructor|destructor|interface"
+ "|implementation|initialization|finalization)[ \t]*.*)$\n"
"^(.*=[ \t]*(class|record).*)$",
/* -- */
"[a-zA-Z_][a-zA-Z0-9_]*"
@@ -174,13 +219,15 @@ PATTERNS("php",
"[a-zA-Z_][a-zA-Z0-9_]*"
"|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+"
"|[-+*/<>%&^|=!.]=|--|\\+\\+|<<=?|>>=?|===|&&|\\|\\||::|->"),
-PATTERNS("python", "^[ \t]*((class|(async[ \t]+)?def)[ \t].*)$",
+PATTERNS("python",
+ "^[ \t]*((class|(async[ \t]+)?def)[ \t].*)$",
/* -- */
"[a-zA-Z_][a-zA-Z0-9_]*"
"|[-+0-9.e]+[jJlL]?|0[xX]?[0-9a-fA-F]+[lL]?"
"|[-+*/<>%&^|=!]=|//=?|<<=?|>>=?|\\*\\*=?"),
/* -- */
-PATTERNS("ruby", "^[ \t]*((class|module|def)[ \t].*)$",
+PATTERNS("ruby",
+ "^[ \t]*((class|module|def)[ \t].*)$",
/* -- */
"(@|@@|\\$)?[a-zA-Z_][a-zA-Z0-9_]*"
"|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+|\\?(\\\\C-)?(\\\\M-)?."
@@ -191,46 +238,17 @@ PATTERNS("rust",
"[a-zA-Z_][a-zA-Z0-9_]*"
"|[0-9][0-9_a-fA-Fiosuxz]*(\\.([0-9]*[eE][+-]?)?[0-9_fF]*)?"
"|[-+*\\/<>%&^|=!:]=|<<=?|>>=?|&&|\\|\\||->|=>|\\.{2}=|\\.{3}|::"),
-PATTERNS("bibtex", "(@[a-zA-Z]{1,}[ \t]*\\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$",
- "[={}\"]|[^={}\" \t]+"),
-PATTERNS("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$",
- "\\\\[a-zA-Z@]+|\\\\.|[a-zA-Z0-9\x80-\xff]+"),
-PATTERNS("cpp",
- /* Jump targets or access declarations */
- "!^[ \t]*[A-Za-z_][A-Za-z_0-9]*:[[:space:]]*($|/[/*])\n"
- /* functions/methods, variables, and compounds at top level */
- "^((::[[:space:]]*)?[A-Za-z_].*)$",
- /* -- */
- "[a-zA-Z_][a-zA-Z0-9_]*"
- "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lLuU]*"
- "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->\\*?|\\.\\*"),
-PATTERNS("csharp",
- /* Keywords */
- "!^[ \t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\n"
- /* Methods and constructors */
- "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe|async)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[<>@._[:alnum:]]+[ \t]*\\(.*\\))[ \t]*$\n"
- /* Properties */
- "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[@._[:alnum:]]+)[ \t]*$\n"
- /* Type definitions */
- "^[ \t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \t]+)*(class|enum|interface|struct)[ \t]+.*)$\n"
- /* Namespace */
- "^[ \t]*(namespace[ \t]+.*)$",
- /* -- */
- "[a-zA-Z_][a-zA-Z0-9_]*"
- "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
- "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"),
-IPATTERN("css",
- "![:;][[:space:]]*$\n"
- "^[:[@.#]?[_a-z0-9].*$",
- /* -- */
+PATTERNS("scheme",
+ "^[\t ]*(\\(((define|def(struct|syntax|class|method|rules|record|proto|alias)?)[-*/ \t]|(library|module|struct|class)[*+ \t]).*)$",
/*
- * This regex comes from W3C CSS specs. Should theoretically also
- * allow ISO 10646 characters U+00A0 and higher,
- * but they are not handled in this regex.
+ * R7RS valid identifiers include any sequence enclosed
+ * within vertical lines having no backslashes
*/
- "-?[_a-zA-Z][-_a-zA-Z0-9]*" /* identifiers */
- "|-?[0-9]+|\\#[0-9a-fA-F]+" /* numbers */
-),
+ "\\|([^\\\\]*)\\|"
+ /* All other words should be delimited by spaces or parentheses */
+ "|([^][)(}{[ \t])+"),
+PATTERNS("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$",
+ "\\\\[a-zA-Z@]+|\\\\.|[a-zA-Z0-9\x80-\xff]+"),
{ "default", NULL, -1, { NULL, 0 } },
};
#undef PATTERNS
@@ -250,20 +268,33 @@ static struct userdiff_driver driver_false = {
{ NULL, 0 }
};
-static struct userdiff_driver *userdiff_find_by_namelen(const char *k, size_t len)
+struct find_by_namelen_data {
+ const char *name;
+ size_t len;
+ struct userdiff_driver *driver;
+};
+
+static int userdiff_find_by_namelen_cb(struct userdiff_driver *driver,
+ enum userdiff_driver_type type, void *priv)
{
- int i;
- for (i = 0; i < ndrivers; i++) {
- struct userdiff_driver *drv = drivers + i;
- if (!strncmp(drv->name, k, len) && !drv->name[len])
- return drv;
- }
- for (i = 0; i < ARRAY_SIZE(builtin_drivers); i++) {
- struct userdiff_driver *drv = builtin_drivers + i;
- if (!strncmp(drv->name, k, len) && !drv->name[len])
- return drv;
+ struct find_by_namelen_data *cb_data = priv;
+
+ if (!strncmp(driver->name, cb_data->name, cb_data->len) &&
+ !driver->name[cb_data->len]) {
+ cb_data->driver = driver;
+ return 1; /* tell the caller to stop iterating */
}
- return NULL;
+ return 0;
+}
+
+static struct userdiff_driver *userdiff_find_by_namelen(const char *name, size_t len)
+{
+ struct find_by_namelen_data udcbdata = {
+ .name = name,
+ .len = len,
+ };
+ for_each_userdiff_driver(userdiff_find_by_namelen_cb, &udcbdata);
+ return udcbdata.driver;
}
static int parse_funcname(struct userdiff_funcname *f, const char *k,
@@ -370,3 +401,36 @@ struct userdiff_driver *userdiff_get_textconv(struct repository *r,
return driver;
}
+
+static int for_each_userdiff_driver_list(each_userdiff_driver_fn fn,
+ enum userdiff_driver_type type, void *cb_data,
+ struct userdiff_driver *drv,
+ int drv_size)
+{
+ int i;
+ int ret;
+ for (i = 0; i < drv_size; i++) {
+ struct userdiff_driver *item = drv + i;
+ if ((ret = fn(item, type, cb_data)))
+ return ret;
+ }
+ return 0;
+}
+
+int for_each_userdiff_driver(each_userdiff_driver_fn fn, void *cb_data)
+{
+ int ret;
+
+ ret = for_each_userdiff_driver_list(fn, USERDIFF_DRIVER_TYPE_CUSTOM,
+ cb_data, drivers, ndrivers);
+ if (ret)
+ return ret;
+
+ ret = for_each_userdiff_driver_list(fn, USERDIFF_DRIVER_TYPE_BUILTIN,
+ cb_data, builtin_drivers,
+ ARRAY_SIZE(builtin_drivers));
+ if (ret)
+ return ret;
+
+ return 0;
+}
diff --git a/userdiff.h b/userdiff.h
index 203057e13e..aee91bc77e 100644
--- a/userdiff.h
+++ b/userdiff.h
@@ -21,6 +21,12 @@ struct userdiff_driver {
struct notes_cache *textconv_cache;
int textconv_want_cache;
};
+enum userdiff_driver_type {
+ USERDIFF_DRIVER_TYPE_BUILTIN = 1<<0,
+ USERDIFF_DRIVER_TYPE_CUSTOM = 1<<1,
+};
+typedef int (*each_userdiff_driver_fn)(struct userdiff_driver *,
+ enum userdiff_driver_type, void *);
int userdiff_config(const char *k, const char *v);
struct userdiff_driver *userdiff_find_by_name(const char *name);
@@ -34,4 +40,11 @@ struct userdiff_driver *userdiff_find_by_path(struct index_state *istate,
struct userdiff_driver *userdiff_get_textconv(struct repository *r,
struct userdiff_driver *driver);
+/*
+ * Iterate over all userdiff drivers. The userdiff_driver_type
+ * argument to each_userdiff_driver_fn indicates their type. Return
+ * non-zero to exit early from the loop.
+ */
+int for_each_userdiff_driver(each_userdiff_driver_fn, void *);
+
#endif /* USERDIFF */
diff --git a/utf8.c b/utf8.c
index 5b39361ada..de4ce5c0e6 100644
--- a/utf8.c
+++ b/utf8.c
@@ -777,6 +777,11 @@ int is_hfs_dotgitattributes(const char *path)
return is_hfs_dot_str(path, "gitattributes");
}
+int is_hfs_dotmailmap(const char *path)
+{
+ return is_hfs_dot_str(path, "mailmap");
+}
+
const char utf8_bom[] = "\357\273\277";
int skip_utf8_bom(char **text, size_t len)
diff --git a/utf8.h b/utf8.h
index fcd5167baf..9a16c8679d 100644
--- a/utf8.h
+++ b/utf8.h
@@ -61,6 +61,7 @@ int is_hfs_dotgit(const char *path);
int is_hfs_dotgitmodules(const char *path);
int is_hfs_dotgitignore(const char *path);
int is_hfs_dotgitattributes(const char *path);
+int is_hfs_dotmailmap(const char *path);
typedef enum {
ALIGN_LEFT,
diff --git a/vcs-svn/fast_export.h b/vcs-svn/fast_export.h
deleted file mode 100644
index 9dcf9337c1..0000000000
--- a/vcs-svn/fast_export.h
+++ /dev/null
@@ -1,34 +0,0 @@
-#ifndef FAST_EXPORT_H
-#define FAST_EXPORT_H
-
-struct strbuf;
-struct line_buffer;
-
-void fast_export_init(int fd);
-void fast_export_deinit(void);
-
-void fast_export_delete(const char *path);
-void fast_export_modify(const char *path, uint32_t mode, const char *dataref);
-void fast_export_note(const char *committish, const char *dataref);
-void fast_export_begin_note(uint32_t revision, const char *author,
- const char *log, timestamp_t timestamp, const char *note_ref);
-void fast_export_begin_commit(uint32_t revision, const char *author,
- const struct strbuf *log, const char *uuid,const char *url,
- timestamp_t timestamp, const char *local_ref);
-void fast_export_end_commit(uint32_t revision);
-void fast_export_data(uint32_t mode, off_t len, struct line_buffer *input);
-void fast_export_buf_to_data(const struct strbuf *data);
-void fast_export_blob_delta(uint32_t mode,
- uint32_t old_mode, const char *old_data,
- off_t len, struct line_buffer *input);
-
-/* If there is no such file at that rev, returns -1, errno == ENOENT. */
-int fast_export_ls_rev(uint32_t rev, const char *path,
- uint32_t *mode_out, struct strbuf *dataref_out);
-int fast_export_ls(const char *path,
- uint32_t *mode_out, struct strbuf *dataref_out);
-
-void fast_export_copy(uint32_t revision, const char *src, const char *dst);
-const char *fast_export_read_path(const char *path, uint32_t *mode_out);
-
-#endif
diff --git a/vcs-svn/line_buffer.h b/vcs-svn/line_buffer.h
deleted file mode 100644
index e192aedea2..0000000000
--- a/vcs-svn/line_buffer.h
+++ /dev/null
@@ -1,30 +0,0 @@
-#ifndef LINE_BUFFER_H
-#define LINE_BUFFER_H
-
-#include "strbuf.h"
-
-#define LINE_BUFFER_LEN 10000
-
-struct line_buffer {
- char line_buffer[LINE_BUFFER_LEN];
- FILE *infile;
-};
-#define LINE_BUFFER_INIT { "", NULL }
-
-int buffer_init(struct line_buffer *buf, const char *filename);
-int buffer_fdinit(struct line_buffer *buf, int fd);
-int buffer_deinit(struct line_buffer *buf);
-
-int buffer_tmpfile_init(struct line_buffer *buf);
-FILE *buffer_tmpfile_rewind(struct line_buffer *buf); /* prepare to write. */
-long buffer_tmpfile_prepare_to_read(struct line_buffer *buf);
-
-int buffer_ferror(struct line_buffer *buf);
-char *buffer_read_line(struct line_buffer *buf);
-int buffer_read_char(struct line_buffer *buf);
-size_t buffer_read_binary(struct line_buffer *buf, struct strbuf *sb, size_t len);
-/* Returns number of bytes read (not necessarily written). */
-off_t buffer_copy_bytes(struct line_buffer *buf, off_t len);
-off_t buffer_skip_bytes(struct line_buffer *buf, off_t len);
-
-#endif
diff --git a/vcs-svn/sliding_window.h b/vcs-svn/sliding_window.h
deleted file mode 100644
index a7fc0999cb..0000000000
--- a/vcs-svn/sliding_window.h
+++ /dev/null
@@ -1,18 +0,0 @@
-#ifndef SLIDING_WINDOW_H
-#define SLIDING_WINDOW_H
-
-#include "strbuf.h"
-
-struct sliding_view {
- struct line_buffer *file;
- off_t off;
- size_t width;
- off_t max_off; /* -1 means unlimited */
- struct strbuf buf;
-};
-
-#define SLIDING_VIEW_INIT(input, len) { (input), 0, 0, (len), STRBUF_INIT }
-
-int move_window(struct sliding_view *view, off_t off, size_t width);
-
-#endif
diff --git a/vcs-svn/svndiff.h b/vcs-svn/svndiff.h
deleted file mode 100644
index 625d950bb8..0000000000
--- a/vcs-svn/svndiff.h
+++ /dev/null
@@ -1,10 +0,0 @@
-#ifndef SVNDIFF_H
-#define SVNDIFF_H
-
-struct line_buffer;
-struct sliding_view;
-
-int svndiff0_apply(struct line_buffer *delta, off_t delta_len,
- struct sliding_view *preimage, FILE *postimage);
-
-#endif
diff --git a/vcs-svn/svndump.h b/vcs-svn/svndump.h
deleted file mode 100644
index 26faed5968..0000000000
--- a/vcs-svn/svndump.h
+++ /dev/null
@@ -1,10 +0,0 @@
-#ifndef SVNDUMP_H
-#define SVNDUMP_H
-
-int svndump_init(const char *filename);
-int svndump_init_fd(int in_fd, int back_fd);
-void svndump_read(const char *url, const char *local_ref, const char *notes_ref);
-void svndump_deinit(void);
-void svndump_reset(void);
-
-#endif
diff --git a/walker.c b/walker.c
index 4984bf8b3d..c5e2921979 100644
--- a/walker.c
+++ b/walker.c
@@ -298,7 +298,7 @@ int walker_fetch(struct walker *walker, int targets, char **target,
error("Could not interpret response from server '%s' as something to pull", target[i]);
goto done;
}
- if (process(walker, lookup_unknown_object(&oids[i])))
+ if (process(walker, lookup_unknown_object(the_repository, &oids[i])))
goto done;
}
diff --git a/worktree.c b/worktree.c
index e00858540e..237517baee 100644
--- a/worktree.c
+++ b/worktree.c
@@ -53,7 +53,7 @@ static struct worktree *get_main_worktree(void)
strbuf_add_real_path(&worktree_path, get_git_common_dir());
strbuf_strip_suffix(&worktree_path, "/.git");
- worktree = xcalloc(1, sizeof(*worktree));
+ CALLOC_ARRAY(worktree, 1);
worktree->path = strbuf_detach(&worktree_path, NULL);
/*
* NEEDSWORK: If this function is called from a secondary worktree and
@@ -84,7 +84,7 @@ static struct worktree *get_linked_worktree(const char *id)
strbuf_rtrim(&worktree_path);
strbuf_strip_suffix(&worktree_path, "/.git");
- worktree = xcalloc(1, sizeof(*worktree));
+ CALLOC_ARRAY(worktree, 1);
worktree->path = strbuf_detach(&worktree_path, NULL);
worktree->id = xstrdup(id);
add_head_info(worktree);
@@ -128,10 +128,8 @@ struct worktree **get_worktrees(void)
dir = opendir(path.buf);
strbuf_release(&path);
if (dir) {
- while ((d = readdir(dir)) != NULL) {
+ while ((d = readdir_skip_dot_and_dotdot(dir)) != NULL) {
struct worktree *linked = NULL;
- if (is_dot_or_dotdot(d->d_name))
- continue;
if ((linked = get_linked_worktree(d->d_name))) {
ALLOC_GROW(list, counter + 1, alloc);
@@ -486,13 +484,9 @@ int submodule_uses_worktrees(const char *path)
if (!dir)
return 0;
- while ((d = readdir(dir)) != NULL) {
- if (is_dot_or_dotdot(d->d_name))
- continue;
-
+ d = readdir_skip_dot_and_dotdot(dir);
+ if (d != NULL)
ret = 1;
- break;
- }
closedir(dir);
return ret;
}
diff --git a/wrapper.c b/wrapper.c
index bcda41e374..563ad590df 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -678,3 +678,19 @@ int is_empty_or_missing_file(const char *filename)
return !st.st_size;
}
+
+int open_nofollow(const char *path, int flags)
+{
+#ifdef O_NOFOLLOW
+ return open(path, flags | O_NOFOLLOW);
+#else
+ struct stat st;
+ if (lstat(path, &st) < 0)
+ return -1;
+ if (S_ISLNK(st.st_mode)) {
+ errno = ELOOP;
+ return -1;
+ }
+ return open(path, flags);
+#endif
+}
diff --git a/wt-status.c b/wt-status.c
index 0c8287a023..42b6735716 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -456,7 +456,7 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q,
it = string_list_insert(&s->change, p->two->path);
d = it->util;
if (!d) {
- d = xcalloc(1, sizeof(*d));
+ CALLOC_ARRAY(d, 1);
it->util = d;
}
if (!d->worktree_status)
@@ -540,7 +540,7 @@ static void wt_status_collect_updated_cb(struct diff_queue_struct *q,
it = string_list_insert(&s->change, p->two->path);
d = it->util;
if (!d) {
- d = xcalloc(1, sizeof(*d));
+ CALLOC_ARRAY(d, 1);
it->util = d;
}
if (!d->index_status)
@@ -616,6 +616,7 @@ static void wt_status_collect_changes_worktree(struct wt_status *s)
rev.diffopt.rename_score = s->rename_score >= 0 ? s->rename_score : rev.diffopt.rename_score;
copy_pathspec(&rev.prune_data, &s->pathspec);
run_diff_files(&rev, 0);
+ clear_pathspec(&rev.prune_data);
}
static void wt_status_collect_changes_index(struct wt_status *s)
@@ -652,6 +653,8 @@ static void wt_status_collect_changes_index(struct wt_status *s)
rev.diffopt.rename_score = s->rename_score >= 0 ? s->rename_score : rev.diffopt.rename_score;
copy_pathspec(&rev.prune_data, &s->pathspec);
run_diff_index(&rev, 1);
+ object_array_clear(&rev.pending);
+ clear_pathspec(&rev.prune_data);
}
static void wt_status_collect_changes_initial(struct wt_status *s)
@@ -671,7 +674,7 @@ static void wt_status_collect_changes_initial(struct wt_status *s)
it = string_list_insert(&s->change, ce->name);
d = it->util;
if (!d) {
- d = xcalloc(1, sizeof(*d));
+ CALLOC_ARRAY(d, 1);
it->util = d;
}
if (ce_stage(ce)) {
@@ -1687,10 +1690,10 @@ void wt_status_get_state(struct repository *r,
if (!sequencer_get_last_command(r, &action)) {
if (action == REPLAY_PICK) {
state->cherry_pick_in_progress = 1;
- oidcpy(&state->cherry_pick_head_oid, &null_oid);
+ oidcpy(&state->cherry_pick_head_oid, null_oid());
} else {
state->revert_in_progress = 1;
- oidcpy(&state->revert_head_oid, &null_oid);
+ oidcpy(&state->revert_head_oid, null_oid());
}
}
if (get_detached_from)
@@ -2480,6 +2483,7 @@ int has_uncommitted_changes(struct repository *r,
diff_setup_done(&rev_info.diffopt);
result = run_diff_index(&rev_info, 1);
+ object_array_clear(&rev_info.pending);
return diff_result_code(&rev_info.diffopt, result);
}
diff --git a/xdiff-interface.c b/xdiff-interface.c
index 4d20069302..609615db2c 100644
--- a/xdiff-interface.c
+++ b/xdiff-interface.c
@@ -172,7 +172,7 @@ void read_mmblob(mmfile_t *ptr, const struct object_id *oid)
unsigned long size;
enum object_type type;
- if (oideq(oid, &null_oid)) {
+ if (oideq(oid, null_oid())) {
ptr->ptr = xstrdup("");
ptr->size = 0;
return;
diff --git a/xdiff/xpatience.c b/xdiff/xpatience.c
index 20699a6f60..c5d48e80ae 100644
--- a/xdiff/xpatience.c
+++ b/xdiff/xpatience.c
@@ -90,7 +90,7 @@ static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map,
{
xrecord_t **records = pass == 1 ?
map->env->xdf1.recs : map->env->xdf2.recs;
- xrecord_t *record = records[line - 1], *other;
+ xrecord_t *record = records[line - 1];
/*
* After xdl_prepare_env() (or more precisely, due to
* xdl_classify_record()), the "ha" member of the records (AKA lines)
@@ -104,11 +104,7 @@ static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map,
int index = (int)((record->ha << 1) % map->alloc);
while (map->entries[index].line1) {
- other = map->env->xdf1.recs[map->entries[index].line1 - 1];
- if (map->entries[index].hash != record->ha ||
- !xdl_recmatch(record->ptr, record->size,
- other->ptr, other->size,
- map->xpp->flags)) {
+ if (map->entries[index].hash != record->ha) {
if (++index >= map->alloc)
index = 0;
continue;
@@ -253,8 +249,7 @@ static int match(struct hashmap *map, int line1, int line2)
{
xrecord_t *record1 = map->env->xdf1.recs[line1 - 1];
xrecord_t *record2 = map->env->xdf2.recs[line2 - 1];
- return xdl_recmatch(record1->ptr, record1->size,
- record2->ptr, record2->size, map->xpp->flags);
+ return record1->ha == record2->ha;
}
static int patience_diff(mmfile_t *file1, mmfile_t *file2,
@@ -289,9 +284,6 @@ static int walk_common_sequence(struct hashmap *map, struct entry *first,
/* Recurse */
if (next1 > line1 || next2 > line2) {
- struct hashmap submap;
-
- memset(&submap, 0, sizeof(submap));
if (patience_diff(map->file1, map->file2,
map->xpp, map->env,
line1, next1 - line1,