diff options
214 files changed, 2668 insertions, 2502 deletions
diff --git a/.gitattributes b/.gitattributes index b0044cf272..158c3d45c4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,17 +1,17 @@ * whitespace=!indent,trail,space *.[ch] whitespace=indent,trail,space diff=cpp -*.sh whitespace=indent,trail,space eol=lf -*.perl eol=lf diff=perl -*.pl eof=lf diff=perl -*.pm eol=lf diff=perl -*.py eol=lf diff=python -*.bat eol=crlf +*.sh whitespace=indent,trail,space text eol=lf +*.perl text eol=lf diff=perl +*.pl text eof=lf diff=perl +*.pm text eol=lf diff=perl +*.py text eol=lf diff=python +*.bat text eol=crlf CODE_OF_CONDUCT.md -whitespace -/Documentation/**/*.txt eol=lf -/command-list.txt eol=lf -/GIT-VERSION-GEN eol=lf -/mergetools/* eol=lf -/t/oid-info/* eol=lf +/Documentation/**/*.txt text eol=lf +/command-list.txt text eol=lf +/GIT-VERSION-GEN text eol=lf +/mergetools/* text eol=lf +/t/oid-info/* text eol=lf /Documentation/git-merge.txt conflict-marker-size=32 /Documentation/gitk.txt conflict-marker-size=32 /Documentation/user-manual.txt conflict-marker-size=32 diff --git a/.gitignore b/.gitignore index 6782f3ceca..e875c59054 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,6 @@ /bin-wrappers/ /git /git-add -/git-add--interactive /git-am /git-annotate /git-apply diff --git a/Documentation/RelNotes/2.30.8.txt b/Documentation/RelNotes/2.30.8.txt new file mode 100644 index 0000000000..5ed3efbd6a --- /dev/null +++ b/Documentation/RelNotes/2.30.8.txt @@ -0,0 +1,51 @@ +Git v2.30.8 Release Notes +========================= + +This release addresses the security issues CVE-2023-22490 and +CVE-2023-23946. + + +Fixes since v2.30.7 +------------------- + + * CVE-2023-22490: + + Using a specially-crafted repository, Git can be tricked into using + its local clone optimization even when using a non-local transport. + Though Git will abort local clones whose source $GIT_DIR/objects + directory contains symbolic links (c.f., CVE-2022-39253), the objects + directory itself may still be a symbolic link. + + These two may be combined to include arbitrary files based on known + paths on the victim's filesystem within the malicious repository's + working copy, allowing for data exfiltration in a similar manner as + CVE-2022-39253. + + * CVE-2023-23946: + + By feeding a crafted input to "git apply", a path outside the + working tree can be overwritten as the user who is running "git + apply". + + * A mismatched type in `attr.c::read_attr_from_index()` which could + cause Git to errantly reject attributes on Windows and 32-bit Linux + has been corrected. + +Credit for finding CVE-2023-22490 goes to yvvdwf, and the fix was +developed by Taylor Blau, with additional help from others on the +Git security mailing list. + +Credit for finding CVE-2023-23946 goes to Joern Schneeweisz, and the +fix was developed by Patrick Steinhardt. + + +Johannes Schindelin (1): + attr: adjust a mismatched data type + +Patrick Steinhardt (1): + apply: fix writing behind newly created symbolic links + +Taylor Blau (3): + t5619: demonstrate clone_local() with ambiguous transport + clone: delay picking a transport until after get_repo_path() + dir-iterator: prevent top-level symlinks without FOLLOW_SYMLINKS diff --git a/Documentation/RelNotes/2.31.7.txt b/Documentation/RelNotes/2.31.7.txt new file mode 100644 index 0000000000..dd44d5bc62 --- /dev/null +++ b/Documentation/RelNotes/2.31.7.txt @@ -0,0 +1,6 @@ +Git v2.31.7 Release Notes +========================= + +This release merges up the fixes that appear in v2.30.8 to +address the security issues CVE-2023-22490 and CVE-2023-23946; +see the release notes for that version for details. diff --git a/Documentation/RelNotes/2.32.6.txt b/Documentation/RelNotes/2.32.6.txt new file mode 100644 index 0000000000..fd659612e3 --- /dev/null +++ b/Documentation/RelNotes/2.32.6.txt @@ -0,0 +1,6 @@ +Git v2.32.6 Release Notes +========================= + +This release merges up the fixes that appear in v2.30.8 and v2.31.7 +to address the security issues CVE-2023-22490 and CVE-2023-23946; +see the release notes for these versions for details. diff --git a/Documentation/RelNotes/2.33.7.txt b/Documentation/RelNotes/2.33.7.txt new file mode 100644 index 0000000000..078a837cb4 --- /dev/null +++ b/Documentation/RelNotes/2.33.7.txt @@ -0,0 +1,7 @@ +Git v2.33.7 Release Notes +========================= + +This release merges up the fixes that appear in v2.30.8, v2.31.7 +and v2.32.6 to address the security issues CVE-2023-22490 and +CVE-2023-23946; see the release notes for these versions for +details. diff --git a/Documentation/RelNotes/2.34.7.txt b/Documentation/RelNotes/2.34.7.txt new file mode 100644 index 0000000000..88898adacc --- /dev/null +++ b/Documentation/RelNotes/2.34.7.txt @@ -0,0 +1,7 @@ +Git v2.34.7 Release Notes +========================= + +This release merges up the fixes that appear in v2.30.8, v2.31.7, +v2.32.6 and v2.33.7 to address the security issues CVE-2023-22490 +and CVE-2023-23946; see the release notes for these versions +for details. diff --git a/Documentation/RelNotes/2.35.7.txt b/Documentation/RelNotes/2.35.7.txt new file mode 100644 index 0000000000..42baabfc3b --- /dev/null +++ b/Documentation/RelNotes/2.35.7.txt @@ -0,0 +1,7 @@ +Git v2.35.7 Release Notes +========================= + +This release merges up the fixes that appear in v2.30.8, v2.31.7, +v2.32.6, v2.33.7 and v2.34.7 to address the security issues +CVE-2023-22490 and CVE-2023-23946; see the release notes for +these versions for details. diff --git a/Documentation/RelNotes/2.36.5.txt b/Documentation/RelNotes/2.36.5.txt new file mode 100644 index 0000000000..8a098c7916 --- /dev/null +++ b/Documentation/RelNotes/2.36.5.txt @@ -0,0 +1,7 @@ +Git v2.36.5 Release Notes +========================= + +This release merges up the fixes that appear in v2.30.8, v2.31.7, +v2.32.6, v2.33.7, v2.34.7 and v2.35.7 to address the security +issues CVE-2023-22490 and CVE-2023-23946; see the release notes +for these versions for details. diff --git a/Documentation/RelNotes/2.37.6.txt b/Documentation/RelNotes/2.37.6.txt new file mode 100644 index 0000000000..51dc149711 --- /dev/null +++ b/Documentation/RelNotes/2.37.6.txt @@ -0,0 +1,7 @@ +Git v2.37.6 Release Notes +========================= + +This release merges up the fixes that appear in v2.30.8, v2.31.7, +v2.32.6, v2.33.7, v2.34.7, v2.35.7 and v2.36.5 to address the +security issues CVE-2023-22490 and CVE-2023-23946; see the release +notes for these versions for details. diff --git a/Documentation/RelNotes/2.38.4.txt b/Documentation/RelNotes/2.38.4.txt new file mode 100644 index 0000000000..fdfde22022 --- /dev/null +++ b/Documentation/RelNotes/2.38.4.txt @@ -0,0 +1,7 @@ +Git v2.38.4 Release Notes +========================= + +This release merges up the fixes that appear in v2.30.8, v2.31.7, +v2.32.6, v2.33.7, v2.34.7, v2.35.7, v2.36.5 and v2.37.6 to +address the security issues CVE-2023-22490 and CVE-2023-23946; +see the release notes for these versions for details. diff --git a/Documentation/RelNotes/2.39.2.txt b/Documentation/RelNotes/2.39.2.txt new file mode 100644 index 0000000000..ebb9900bc5 --- /dev/null +++ b/Documentation/RelNotes/2.39.2.txt @@ -0,0 +1,7 @@ +Git v2.39.2 Release Notes +========================= + +This release merges up the fixes that appear in v2.30.8, v2.31.7, +v2.32.6, v2.33.7, v2.34.7, v2.35.7, v2.36.5, v2.37.6 and v2.38.4 +to address the security issues CVE-2023-22490 and CVE-2023-23946; +see the release notes for these versions for details. diff --git a/Documentation/RelNotes/2.39.3.txt b/Documentation/RelNotes/2.39.3.txt new file mode 100644 index 0000000000..dddff53627 --- /dev/null +++ b/Documentation/RelNotes/2.39.3.txt @@ -0,0 +1,58 @@ +Git v2.39.3 Release Notes +========================= + +This release is primarily to merge fixes accumulated on the 'master' +front to prepare for 2.40 release that are still relevant to 2.39.x +maintenance track. + +Fixes since v2.39.2 +------------------- + + * Stop running win+VS build by default. + + * CI updates. We probably want a clean-up to move the long shell + script embedded in yaml file into a separate file, but that can + come later. + + * Avoid unnecessary builds in CI, with settings configured in + ci-config. + + * Redefining system functions for a few functions did not follow our + usual "implement git_foo() and #define foo(args) git_foo(args)" + pattern, which has broken build for some folks. + + * Deal with a few deprecation warning from cURL library. + + * Newer regex library macOS stopped enabling GNU-like enhanced BRE, + where '\(A\|B\)' works as alternation, unless explicitly asked with + the REG_ENHANCED flag. "git grep" now can be compiled to do so, to + retain the old behaviour. + + * When given a pattern that matches an empty string at the end of a + line, the code to parse the "git diff" line-ranges fell into an + infinite loop, which has been corrected. + + * Fix the sequence to fsync $GIT_DIR/packed-refs file that forgot to + flush its output to the disk.. + + * "git diff --relative" did not mix well with "git diff --ext-diff", + which has been corrected. + + * The logic to see if we are using the "cone" mode by checking the + sparsity patterns has been tightened to avoid mistaking a pattern + that names a single file as specifying a cone. + + * Doc update for environment variables set when hooks are invoked. + + * Document ORIG_HEAD a bit more. + + * "git ls-tree --format='%(path) %(path)' $tree $path" showed the + path three times, which has been corrected. + + * Document that "branch -f <branch>" disables only the safety to + avoid recreating an existing branch. + + * Clarify how "checkout -b/-B" and "git branch [-f]" are similar but + different in the documentation. + +Also contains minor documentation updates and code clean-ups. diff --git a/Documentation/RelNotes/2.40.0.txt b/Documentation/RelNotes/2.40.0.txt index edbbe7d049..922cf22af3 100644 --- a/Documentation/RelNotes/2.40.0.txt +++ b/Documentation/RelNotes/2.40.0.txt @@ -49,6 +49,14 @@ UI, Workflows & Features * "scalar" warns but continues when its periodic maintenance feature cannot be enabled. + * The bundle-URI subsystem adds support for creation-token heuristics + to help incremental fetches. + + * Userdiff regexp update for Java language. + + * "git fetch --jobs=0" used to hit a BUG(), which has been corrected + to use the available CPUs. + Performance, Internal Implementation, Development Support etc. @@ -69,12 +77,10 @@ Performance, Internal Implementation, Development Support etc. the submodule--helper. * Stop running win+VS build by default. - (merge a0da6deeec js/ci-disable-cmake-by-default later to maint). * CI updates. We probably want a clean-up to move the long shell script embedded in yaml file into a separate file, but that can come later. - (merge 4542582e59 cw/ci-whitespace later to maint). * Use `git diff --no-index` as a test_cmp on Windows. @@ -84,14 +90,21 @@ Performance, Internal Implementation, Development Support etc. * Avoid unnecessary builds in CI, with settings configured in ci-config. - (merge eb5b03a9c0 tb/ci-concurrency later to maint). + + * Plug leaks in sequencer subsystem and its users. + + * In-tree .gitattributes update to match the way we recommend our + users to mark a file as text. + (merge 1f34e0cd3d po/attributes-text later to maint). + + * Finally retire the scripted "git add -p/-i" implementation and have + everybody use the one reimplemented in C. Fixes since v2.39 ----------------- * Various leak fixes. - (merge ac95f5d36a ab/various-leak-fixes later to maint). * Fix a bug where `pack-objects` would not respect multiple `--filter` arguments when invoked directly. @@ -106,8 +119,6 @@ Fixes since v2.39 * Redefining system functions for a few functions did not follow our usual "implement git_foo() and #define foo(args) git_foo(args)" pattern, which has broken build for some folks. - (merge e1a95b78d8 jk/avoid-redef-system-functions-2.30 later to maint). - (merge 395bec6b39 jk/avoid-redef-system-functions later to maint). * The way the diff machinery prepares the options array for the parse_options API has been refactored to avoid resource leaks. @@ -137,36 +148,27 @@ Fixes since v2.39 * When given a pattern that matches an empty string at the end of a line, the code to parse the "git diff" line-ranges fell into an infinite loop, which has been corrected. - (merge 4e57c88e02 lk/line-range-parsing-fix later to maint). * Fix the sequence to fsync $GIT_DIR/packed-refs file that forgot to flush its output to the disk.. - (merge ce54672f9b ps/fsync-refs-fix later to maint). * Fix to a small regression in 2.38 days. - (merge 6d5e9e53aa ab/bundle-wo-args later to maint). * "git diff --relative" did not mix well with "git diff --ext-diff", which has been corrected. - (merge f034bb1cad jk/ext-diff-with-relative later to maint). * The logic to see if we are using the "cone" mode by checking the sparsity patterns has been tightened to avoid mistaking a pattern that names a single file as specifying a cone. - (merge 5842710dc2 ws/single-file-cone later to maint). * Deal with a few deprecation warning from cURL library. - (merge 6c065f72b8 jk/curl-avoid-deprecated-api later to maint). * Doc update for environment variables set when hooks are invoked. - (merge 772f8ff826 es/hooks-and-local-env later to maint). * Document ORIG_HEAD a bit more. - (merge f1c9243fc5 pb/doc-orig-head later to maint). * "git ls-tree --format='%(path) %(path)' $tree $path" showed the path three times, which has been corrected. - (merge c388fcda99 rs/ls-tree-path-expansion-fix later to maint). * Remove "git env--helper" and demote it to a test-tool subcommand. (merge 4a1baacd46 ab/test-env-helper later to maint). @@ -175,7 +177,6 @@ Fixes since v2.39 where '\(A\|B\)' works as alternation, unless explicitly asked with the REG_ENHANCED flag. "git grep" now can be compiled to do so, to retain the old behaviour. - (merge 54463d32ef rs/use-enhanced-bre-on-macos later to maint). * Pthread emulation on Win32 leaked thread handle when a thread is joined. @@ -188,7 +189,6 @@ Fixes since v2.39 * Document that "branch -f <branch>" disables only the safety to avoid recreating an existing branch. - (merge bf08abac56 jc/doc-branch-update-checked-out-branch later to maint). * "git fetch <group>", when "<group>" of remotes lists the same remote twice, unnecessarily failed when parallel fetching was @@ -197,7 +197,6 @@ Fixes since v2.39 * Clarify how "checkout -b/-B" and "git branch [-f]" are similar but different in the documentation. - (merge fedb8ea2df jc/doc-checkout-b later to maint). * "git hash-object" now checks that the resulting object is well formed with the same code as "git fsck". @@ -222,22 +221,37 @@ Fixes since v2.39 it is done using it, saving peak heap memory usage. (merge 647982bb71 ew/free-island-marks later to maint). + * In an environment where dynamically generated code is prohibited to + run (e.g. SELinux), failure to JIT pcre patterns is expected. Fall + back to interpreted execution in such a case. + (merge 50b6ad55b0 cb/grep-fallback-failing-jit later to maint). + + * "git name-rev" heuristics update. + (merge b2182a8730 en/name-rev-make-taggerdate-much-less-important later to maint). + + * Remove more remaining uses of macros that relies on the_index + singleton instance without explicitly spelling it out. + + * Remove unnecessary explicit sizing of strbuf. + (merge 93ea118bed rs/cache-tree-strbuf-growth-fix later to maint). + + * Doc update. + (merge d9ec3b0dc0 jk/doc-ls-remote-matching later to maint). + + * Error messages given upon a signature verification failure used to + discard the errors from underlying gpg program, which has been + corrected. + (merge ad6b320756 js/gpg-errors later to maint). + + * Update --date=default documentation. + (merge 9deef088ae rd/doc-default-date-format later to maint). + + * A test helper had a single write(2) of 256kB, which was too big for + some platforms (e.g. NonStop), which has been corrected by using + xwrite() wrapper appropriately. + (merge 58eab6ff13 jc/genzeros-avoid-raw-write later to maint). + * Other code cleanup, docfix, build fix, etc. - (merge 77e04b2ed4 rs/t4205-do-not-exit-in-test-script later to maint). - (merge faebba436e rs/plug-pattern-list-leak-in-lof later to maint). - (merge 243caa8982 ab/t5314-avoid-losing-exit-status later to maint). - (merge 4d81ce1b99 ab/t7600-avoid-losing-exit-status-of-git later to maint). - (merge 5f3bfdc4f3 ab/t4023-avoid-losing-exit-status-of-diff later to maint). - (merge 500317ae03 js/t3920-shell-and-or-fix later to maint). - (merge 86325d36e6 rs/t3920-crlf-eating-grep-fix later to maint). - (merge cfbd173ccb rj/branch-copy-and-rename later to maint). - (merge c25d9e529d jk/unused-post-2.39 later to maint). - (merge a31cfe3283 jk/server-supports-v2-cleanup later to maint). - (merge a658e881c1 rs/am-parse-options-cleanup later to maint). - (merge 4cb39fcf19 rs/clear-commit-marks-cleanup later to maint). - (merge b07a819c05 rs/reflog-expiry-cleanup later to maint). - (merge d422d06167 rs/clarify-error-in-write-loose-object later to maint). - (merge 92cb135855 sk/remove-duplicate-includes later to maint). (merge 4eb1ccecd4 dh/mingw-ownership-check-typofix later to maint). (merge f95526419b ar/typofix-gitattributes-doc later to maint). (merge 27875aeec9 km/doc-branch-start-point later to maint). @@ -258,3 +272,9 @@ Fixes since v2.39 (merge 4f542975d1 mh/doc-credential-cache-only-in-core later to maint). (merge 3a2ebaebc7 gc/index-format-doc later to maint). (merge b08edf709d jk/httpd-test-updates later to maint). + (merge d85e9448dd wl/new-command-doc later to maint). + (merge d912a603ed kf/t5000-modernise later to maint). + (merge e65b868d07 rs/size-t-fixes later to maint). + (merge 3eb1e1ca9a ab/config-h-remove-unused later to maint). + (merge d390e08076 cw/doc-pushurl-vs-url later to maint). + (merge 567342fc77 rs/ctype-test later to maint). diff --git a/Documentation/config/add.txt b/Documentation/config/add.txt index 3e859f3419..e0354ceaed 100644 --- a/Documentation/config/add.txt +++ b/Documentation/config/add.txt @@ -7,6 +7,7 @@ add.ignore-errors (deprecated):: variables. add.interactive.useBuiltin:: - Set to `false` to fall back to the original Perl implementation of - the interactive version of linkgit:git-add[1] instead of the built-in - version. Is `true` by default. + Unused configuration variable. Used in Git versions v2.25.0 to + v2.36.0 to enable the built-in version of linkgit:git-add[1]'s + interactive mode, which then became the default in Git + versions v2.37.0 to v2.39.0. diff --git a/Documentation/config/bundle.txt b/Documentation/config/bundle.txt index daa21eb674..3faae38685 100644 --- a/Documentation/config/bundle.txt +++ b/Documentation/config/bundle.txt @@ -15,6 +15,13 @@ bundle.mode:: complete understanding of the bundled information (`all`) or if any one of the listed bundle URIs is sufficient (`any`). +bundle.heuristic:: + If this string-valued key exists, then the bundle list is designed to + work well with incremental `git fetch` commands. The heuristic signals + that there are additional keys available for each bundle that help + determine which subset of bundles the client should download. The + only value currently understood is `creationToken`. + bundle.<id>.*:: The `bundle.<id>.*` keys are used to describe a single item in the bundle list, grouped under `<id>` for identification purposes. diff --git a/Documentation/config/fetch.txt b/Documentation/config/fetch.txt index cd65d236b4..568f0f75b3 100644 --- a/Documentation/config/fetch.txt +++ b/Documentation/config/fetch.txt @@ -96,3 +96,27 @@ fetch.writeCommitGraph:: merge and the write may take longer. Having an updated commit-graph file helps performance of many Git commands, including `git merge-base`, `git push -f`, and `git log --graph`. Defaults to false. + +fetch.bundleURI:: + This value stores a URI for downloading Git object data from a bundle + URI before performing an incremental fetch from the origin Git server. + This is similar to how the `--bundle-uri` option behaves in + linkgit:git-clone[1]. `git clone --bundle-uri` will set the + `fetch.bundleURI` value if the supplied bundle URI contains a bundle + list that is organized for incremental fetches. ++ +If you modify this value and your repository has a `fetch.bundleCreationToken` +value, then remove that `fetch.bundleCreationToken` value before fetching from +the new bundle URI. + +fetch.bundleCreationToken:: + When using `fetch.bundleURI` to fetch incrementally from a bundle + list that uses the "creationToken" heuristic, this config value + stores the maximum `creationToken` value of the downloaded bundles. + This value is used to prevent downloading bundles in the future + if the advertised `creationToken` is not strictly larger than this + value. ++ +The creation token values are chosen by the provider serving the specific +bundle URI. If you modify the URI at `fetch.bundleURI`, then be sure to +remove the value for the `fetch.bundleCreationToken` value before fetching. diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index a030d33c6e..ed44c1cb31 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -274,7 +274,7 @@ status:: ------------ staged unstaged path 1: binary nothing foo.png - 2: +403/-35 +1/-1 git-add--interactive.perl + 2: +403/-35 +1/-1 add-interactive.c ------------ + It shows that foo.png has differences from HEAD (but that is @@ -282,7 +282,7 @@ binary so line count cannot be shown) and there is no difference between indexed copy and the working tree version (if the working tree version were also different, 'binary' would have been shown in place of 'nothing'). The -other file, git-add{litdd}interactive.perl, has 403 lines added +other file, add-interactive.c, has 403 lines added and 35 lines deleted if you commit what is in the index, but working tree file has further modifications (one addition and one deletion). @@ -303,7 +303,7 @@ like this: ------------ staged unstaged path 1: binary nothing foo.png -* 2: +403/-35 +1/-1 git-add--interactive.perl +* 2: +403/-35 +1/-1 add-interactive.c ------------ + To remove selection, prefix the input with `-` diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt index 60c040988b..6bab201d37 100644 --- a/Documentation/git-archive.txt +++ b/Documentation/git-archive.txt @@ -86,6 +86,11 @@ cases, write an untracked file and use `--add-file` instead. Look for attributes in .gitattributes files in the working tree as well (see <<ATTRIBUTES>>). +--mtime=<time>:: + Set modification time of archive entries. Without this option + the committer time is used if `<tree-ish>` is a commit or tag, + and the current time if it is a tree. + <extra>:: This can be any options that the archiver backend understands. See next section. diff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt index 77c3a8ad90..3407f3c2c0 100644 --- a/Documentation/git-hook.txt +++ b/Documentation/git-hook.txt @@ -8,7 +8,7 @@ git-hook - Run git hooks SYNOPSIS -------- [verse] -'git hook' run [--ignore-missing] <hook-name> [-- <hook-args>] +'git hook' run [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>] DESCRIPTION ----------- @@ -31,6 +31,11 @@ linkgit:githooks[5] for arguments hooks might expect (if any). OPTIONS ------- +--to-stdin:: + For "run"; Specify a file which will be streamed into the + hook's stdin. The hook will receive the entire file from + beginning to EOF. + --ignore-missing:: Ignore any missing hook by quietly returning zero. Used for tools that want to do a blind one-shot run of a hook that may diff --git a/Documentation/git-ls-remote.txt b/Documentation/git-ls-remote.txt index 492e573856..ff3da547dd 100644 --- a/Documentation/git-ls-remote.txt +++ b/Documentation/git-ls-remote.txt @@ -11,7 +11,7 @@ SYNOPSIS [verse] 'git ls-remote' [--heads] [--tags] [--refs] [--upload-pack=<exec>] [-q | --quiet] [--exit-code] [--get-url] [--sort=<key>] - [--symref] [<repository> [<refs>...]] + [--symref] [<repository> [<patterns>...]] DESCRIPTION ----------- @@ -85,25 +85,32 @@ OPTIONS either a URL or the name of a remote (see the GIT URLS and REMOTES sections of linkgit:git-fetch[1]). -<refs>...:: +<patterns>...:: When unspecified, all references, after filtering done - with --heads and --tags, are shown. When <refs>... are - specified, only references matching the given patterns - are displayed. + with --heads and --tags, are shown. When <patterns>... are + specified, only references matching one or more of the given + patterns are displayed. Each pattern is interpreted as a glob + (see `glob` in linkgit:gitglossary[7]) which is matched against + the "tail" of a ref, starting either from the start of the ref + (so a full name like `refs/heads/foo` matches) or from a slash + separator (so `bar` matches `refs/heads/bar` but not + `refs/heads/foobar`). EXAMPLES -------- ---- -$ git ls-remote --tags ./. +$ git ls-remote --tags . d6602ec5194c87b0fc87103ca4d67251c76f233a refs/tags/v0.99 f25a265a342aed6041ab0cc484224d9ca54b6f41 refs/tags/v0.99.1 7ceca275d047c90c0c7d5afb13ab97efdf51bd6e refs/tags/v0.99.3 c5db5456ae3b0873fc659c19fafdde22313cc441 refs/tags/v0.99.2 0918385dbd9656cab0d1d81ba7453d49bbc16250 refs/tags/junio-gpg-pub + $ git ls-remote http://www.kernel.org/pub/scm/git/git.git master seen rc 5fe978a5381f1fbad26a80e682ddd2a401966740 refs/heads/master c781a84b5204fb294c9ccc79f8b3baceeb32c061 refs/heads/seen + $ git remote add korg http://www.kernel.org/pub/scm/git/git.git $ git ls-remote --tags korg v\* d6602ec5194c87b0fc87103ca4d67251c76f233a refs/tags/v0.99 diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index c19e64ea0e..39bfbca1ff 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -758,6 +758,37 @@ with the above configuration, i.e. `j-c-diff`, with 7 parameters, just like `GIT_EXTERNAL_DIFF` program is called. See linkgit:git[1] for details. +Setting the internal diff algorithm +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The diff algorithm can be set through the `diff.algorithm` config key, but +sometimes it may be helpful to set the diff algorithm per path. For example, +one may want to use the `minimal` diff algorithm for .json files, and the +`histogram` for .c files, and so on without having to pass in the algorithm +through the command line each time. + +First, in `.gitattributes`, assign the `diff` attribute for paths. + +------------------------ +*.json diff=<name> +------------------------ + +Then, define a "diff.<name>.algorithm" configuration to specify the diff +algorithm, choosing from `myers`, `patience`, `minimal`, or `histogram`. + +---------------------------------------------------------------- +[diff "<name>"] + algorithm = histogram +---------------------------------------------------------------- + +This diff algorithm applies to user facing diff output like git-diff(1), +git-show(1) and is used for the `--stat` output as well. The merge machinery +will not use the diff algorithm set through this method. + +NOTE: If `diff.<name>.command` is defined for path with the +`diff=<name>` attribute, it is executed as an external diff driver +(see above), and adding `diff.<name>.algorithm` has no effect, as the +algorithm is not passed to the external diff driver. Defining a custom hunk-header ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/Documentation/howto/new-command.txt b/Documentation/howto/new-command.txt index 15a4c8031f..880c51112b 100644 --- a/Documentation/howto/new-command.txt +++ b/Documentation/howto/new-command.txt @@ -1,13 +1,13 @@ From: Eric S. Raymond <esr@thyrsus.com> Abstract: This is how-to documentation for people who want to add extension - commands to Git. It should be read alongside api-builtin.txt. + commands to Git. It should be read alongside builtin.h. Content-type: text/asciidoc How to integrate new subcommands ================================ This is how-to documentation for people who want to add extension -commands to Git. It should be read alongside api-builtin.txt. +commands to Git. It should be read alongside builtin.h. Runtime environment ------------------- diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index ff68e48406..0d90d5b154 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -1100,12 +1100,12 @@ preferred format. See the `strftime` manual for a complete list of format placeholders. When using `-local`, the correct syntax is `--date=format-local:...`. -`--date=default` is the default format, and is similar to -`--date=rfc2822`, with a few exceptions: +`--date=default` is the default format, and is based on ctime(3) +output. It shows a single line with three-letter day of the week, +three-letter month, day-of-month, hour-minute-seconds in "HH:MM:SS" +format, followed by 4-digit year, plus timezone information, unless +the local time zone is used, e.g. `Thu Jan 1 00:00:00 1970 +0000`. -- - - there is no comma after the day-of-week - - - the time zone is omitted when the local time zone is used ifdef::git-rev-list[] --header:: diff --git a/Documentation/technical/bundle-uri.txt b/Documentation/technical/bundle-uri.txt index b78d01d9ad..91d3a13e32 100644 --- a/Documentation/technical/bundle-uri.txt +++ b/Documentation/technical/bundle-uri.txt @@ -479,14 +479,14 @@ outline for submitting these features: (This choice is an opt-in via a config option and a command-line option.) -4. Allow the client to understand the `bundle.flag=forFetch` configuration +4. Allow the client to understand the `bundle.heuristic` configuration key and the `bundle.<id>.creationToken` heuristic. When `git clone` - discovers a bundle URI with `bundle.flag=forFetch`, it configures the - client repository to check that bundle URI during later `git fetch <remote>` + discovers a bundle URI with `bundle.heuristic`, it configures the client + repository to check that bundle URI during later `git fetch <remote>` commands. 5. Allow clients to discover bundle URIs during `git fetch` and configure - a bundle URI for later fetches if `bundle.flag=forFetch`. + a bundle URI for later fetches if `bundle.heuristic` is set. 6. Implement the "inspect headers" heuristic to reduce data downloads when the `bundle.<id>.creationToken` heuristic is not available. diff --git a/Documentation/urls-remotes.txt b/Documentation/urls-remotes.txt index 86d0008f94..e410912fe5 100644 --- a/Documentation/urls-remotes.txt +++ b/Documentation/urls-remotes.txt @@ -33,7 +33,9 @@ config file would appear like this: ------------ The `<pushurl>` is used for pushes only. It is optional and defaults -to `<URL>`. +to `<URL>`. Pushing to a remote affects all defined pushurls or to all +defined urls if no pushurls are defined. Fetch, however, will only +fetch from the first defined url if muliple urls are defined. Named file in `$GIT_DIR/remotes` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index c4c2d3e022..dd62a3ea59 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.39.GIT +DEF_VER=v2.40.0-rc0 LF=' ' @@ -120,7 +120,7 @@ Issues of note: for everyday use (e.g. "bisect", "request-pull"). - "Perl" version 5.8 or later is needed to use some of the - features (e.g. preparing a partial commit using "git add -i/-p", + features (e.g. sending patches using "git send-email", interacting with svn repositories with "git svn"). If you can live without these, use NO_PERL. Note that recent releases of Redhat/Fedora are reported to ship Perl binary package with some @@ -708,7 +708,6 @@ SCRIPT_LIB += git-mergetool--lib SCRIPT_LIB += git-sh-i18n SCRIPT_LIB += git-sh-setup -SCRIPT_PERL += git-add--interactive.perl SCRIPT_PERL += git-archimport.perl SCRIPT_PERL += git-cvsexportcommit.perl SCRIPT_PERL += git-cvsimport.perl @@ -4418,6 +4418,33 @@ static int create_one_file(struct apply_state *state, if (state->cached) return 0; + /* + * We already try to detect whether files are beyond a symlink in our + * up-front checks. But in the case where symlinks are created by any + * of the intermediate hunks it can happen that our up-front checks + * didn't yet see the symlink, but at the point of arriving here there + * in fact is one. We thus repeat the check for symlinks here. + * + * Note that this does not make the up-front check obsolete as the + * failure mode is different: + * + * - The up-front checks cause us to abort before we have written + * anything into the working directory. So when we exit this way the + * working directory remains clean. + * + * - The checks here happen in the middle of the action where we have + * already started to apply the patch. The end result will be a dirty + * working directory. + * + * Ideally, we should update the up-front checks to catch what would + * happen when we apply the patch before we damage the working tree. + * We have all the information necessary to do so. But for now, as a + * part of embargoed security work, having this check would serve as a + * reasonable first step. + */ + if (path_is_beyond_symlink(state, path)) + return error(_("affected file '%s' is beyond a symbolic link"), path); + res = try_create_file(state, path, mode, buf, size); if (res < 0) return -1; @@ -472,6 +472,8 @@ static void parse_treeish_arg(const char **argv, commit_oid = NULL; archive_time = time(NULL); } + if (ar_args->mtime_option) + archive_time = approxidate(ar_args->mtime_option); tree = parse_tree_indirect(&oid); if (!tree) @@ -586,6 +588,7 @@ static int parse_archive_args(int argc, const char **argv, const char *remote = NULL; const char *exec = NULL; const char *output = NULL; + const char *mtime_option = NULL; int compression_level = -1; int verbose = 0; int i; @@ -607,6 +610,9 @@ static int parse_archive_args(int argc, const char **argv, OPT_BOOL(0, "worktree-attributes", &worktree_attributes, N_("read .gitattributes in working directory")), OPT__VERBOSE(&verbose, N_("report archived files on stderr")), + { OPTION_STRING, 0, "mtime", &mtime_option, N_("time"), + N_("set modification time of archive entries"), + PARSE_OPT_NONEG }, OPT_NUMBER_CALLBACK(&compression_level, N_("set compression level"), number_callback), OPT_GROUP(""), @@ -668,6 +674,7 @@ static int parse_archive_args(int argc, const char **argv, args->base = base; args->baselen = strlen(base); args->worktree_attributes = worktree_attributes; + args->mtime_option = mtime_option; return argc; } @@ -710,6 +717,7 @@ int write_archive(int argc, const char **argv, const char *prefix, string_list_clear_func(&args.extra_files, extra_file_info_clear); free(args.refname); + clear_pathspec(&args.pathspec); return rc; } @@ -16,6 +16,7 @@ struct archiver_args { struct tree *tree; const struct object_id *commit_oid; const struct commit *commit; + const char *mtime_option; timestamp_t time; struct pathspec pathspec; unsigned int verbose : 1; diff --git a/builtin/add.c b/builtin/add.c index 0c60402267..61dd386d10 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -238,58 +238,14 @@ static int refresh(int verbose, const struct pathspec *pathspec) return ret; } -int run_add_interactive(const char *revision, const char *patch_mode, - const struct pathspec *pathspec) -{ - int i; - struct child_process cmd = CHILD_PROCESS_INIT; - int use_builtin_add_i = - git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1); - - if (use_builtin_add_i < 0 && - git_config_get_bool("add.interactive.usebuiltin", - &use_builtin_add_i)) - use_builtin_add_i = 1; - - if (use_builtin_add_i != 0) { - enum add_p_mode mode; - - if (!patch_mode) - return !!run_add_i(the_repository, pathspec); - - if (!strcmp(patch_mode, "--patch")) - mode = ADD_P_ADD; - else if (!strcmp(patch_mode, "--patch=stash")) - mode = ADD_P_STASH; - else if (!strcmp(patch_mode, "--patch=reset")) - mode = ADD_P_RESET; - else if (!strcmp(patch_mode, "--patch=checkout")) - mode = ADD_P_CHECKOUT; - else if (!strcmp(patch_mode, "--patch=worktree")) - mode = ADD_P_WORKTREE; - else - die("'%s' not supported", patch_mode); - - return !!run_add_p(the_repository, mode, revision, pathspec); - } - - strvec_push(&cmd.args, "add--interactive"); - if (patch_mode) - strvec_push(&cmd.args, patch_mode); - if (revision) - strvec_push(&cmd.args, revision); - strvec_push(&cmd.args, "--"); - for (i = 0; i < pathspec->nr; i++) - /* pass original pathspec, to be re-parsed */ - strvec_push(&cmd.args, pathspec->items[i].original); - - cmd.git_cmd = 1; - return run_command(&cmd); -} - int interactive_add(const char **argv, const char *prefix, int patch) { struct pathspec pathspec; + int unused; + + if (!git_config_get_bool("add.interactive.usebuiltin", &unused)) + warning(_("the add.interactive.useBuiltin setting has been removed!\n" + "See its entry in 'git help config' for details.")); parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_FULL | @@ -297,9 +253,10 @@ int interactive_add(const char **argv, const char *prefix, int patch) PATHSPEC_PREFIX_ORIGIN, prefix, argv); - return run_add_interactive(NULL, - patch ? "--patch" : NULL, - &pathspec); + if (patch) + return !!run_add_p(the_repository, ADD_P_ADD, NULL, &pathspec); + else + return !!run_add_i(the_repository, &pathspec); } static int edit_patch(int argc, const char **argv, const char *prefix) diff --git a/builtin/am.c b/builtin/am.c index 82a41cbfc4..e0848ddadf 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -3,7 +3,7 @@ * * Based on git-am.sh by Junio C Hamano. */ -#define USE_THE_INDEX_COMPATIBILITY_MACROS +#define USE_THE_INDEX_VARIABLE #include "cache.h" #include "config.h" #include "builtin.h" @@ -495,24 +495,12 @@ static int run_applypatch_msg_hook(struct am_state *state) */ static int run_post_rewrite_hook(const struct am_state *state) { - struct child_process cp = CHILD_PROCESS_INIT; - const char *hook = find_hook("post-rewrite"); - int ret; - - if (!hook) - return 0; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; - strvec_push(&cp.args, hook); - strvec_push(&cp.args, "rebase"); + strvec_push(&opt.args, "rebase"); + opt.path_to_stdin = am_path(state, "rewritten"); - cp.in = xopen(am_path(state, "rewritten"), O_RDONLY); - cp.stdout_to_stderr = 1; - cp.trace2_hook_name = "post-rewrite"; - - ret = run_command(&cp); - - close(cp.in); - return ret; + return run_hooks_opt("post-rewrite", &opt); } /** @@ -1655,7 +1643,7 @@ static void do_commit(const struct am_state *state) if (!state->no_verify && run_hooks("pre-applypatch")) exit(1); - if (write_cache_as_tree(&tree, 0, NULL)) + if (write_index_as_tree(&tree, &the_index, get_index_file(), 0, NULL)) die(_("git write-tree failed to write a tree")); if (!get_oid_commit("HEAD", &parent)) { @@ -2063,7 +2051,7 @@ static int clean_index(const struct object_id *head, const struct object_id *rem if (fast_forward_to(head_tree, head_tree, 1)) return -1; - if (write_cache_as_tree(&index, 0, NULL)) + if (write_index_as_tree(&index, &the_index, get_index_file(), 0, NULL)) return -1; index_tree = parse_tree_indirect(&index); diff --git a/builtin/checkout.c b/builtin/checkout.c index 5963e1b74b..a5155cf55c 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -29,6 +29,7 @@ #include "xdiff-interface.h" #include "entry.h" #include "parallel-checkout.h" +#include "add-interactive.h" static const char * const checkout_usage[] = { N_("git checkout [<options>] <branch>"), @@ -499,7 +500,7 @@ static int checkout_paths(const struct checkout_opts *opts, "--merge", "--conflict", "--staged"); if (opts->patch_mode) { - const char *patch_mode; + enum add_p_mode patch_mode; const char *rev = new_branch_info->name; char rev_oid[GIT_MAX_HEXSZ + 1]; @@ -517,15 +518,16 @@ static int checkout_paths(const struct checkout_opts *opts, rev = oid_to_hex_r(rev_oid, &new_branch_info->commit->object.oid); if (opts->checkout_index && opts->checkout_worktree) - patch_mode = "--patch=checkout"; + patch_mode = ADD_P_CHECKOUT; else if (opts->checkout_index && !opts->checkout_worktree) - patch_mode = "--patch=reset"; + patch_mode = ADD_P_RESET; else if (!opts->checkout_index && opts->checkout_worktree) - patch_mode = "--patch=worktree"; + patch_mode = ADD_P_WORKTREE; else BUG("either flag must have been set, worktree=%d, index=%d", opts->checkout_worktree, opts->checkout_index); - return run_add_interactive(rev, patch_mode, &opts->pathspec); + return !!run_add_p(the_repository, patch_mode, rev, + &opts->pathspec); } repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR); diff --git a/builtin/clean.c b/builtin/clean.c index b2701a2815..10aaa8c603 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -560,7 +560,7 @@ static int parse_choice(struct menu_stuff *menu_stuff, /* * Implement a git-add-interactive compatible UI, which is borrowed - * from git-add--interactive.perl. + * from add-interactive.c. * * Return value: * @@ -1092,5 +1092,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix) strbuf_release(&buf); string_list_clear(&del_list, 0); string_list_clear(&exclude_list, 0); + clear_pathspec(&pathspec); return (errors != 0); } diff --git a/builtin/clone.c b/builtin/clone.c index 5453ba5277..65b5b7db6d 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -892,6 +892,7 @@ 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 *repo_to_free = NULL; char *path = NULL, *dir, *display_repo = NULL; int dest_exists, real_dest_exists = 0; const struct ref *refs, *remote_head; @@ -949,7 +950,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) path = get_repo_path(repo_name, &is_bundle); if (path) { FREE_AND_NULL(path); - repo = absolute_pathdup(repo_name); + repo = repo_to_free = absolute_pathdup(repo_name); } else if (strchr(repo_name, ':')) { repo = repo_name; display_repo = transport_anonymize_url(repo); @@ -1170,10 +1171,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) refspec_appendf(&remote->fetch, "+%s*:%s*", src_ref_prefix, branch_top.buf); - transport = transport_get(remote, remote->url[0]); - transport_set_verbosity(transport, option_verbosity, option_progress); - transport->family = family; - path = get_repo_path(remote->url[0], &is_bundle); is_local = option_local != 0 && path && !is_bundle; if (is_local) { @@ -1195,6 +1192,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } if (option_local > 0 && !is_local) warning(_("--local is ignored")); + + transport = transport_get(remote, path ? path : remote->url[0]); + transport_set_verbosity(transport, option_verbosity, option_progress); + transport->family = family; transport->cloning = 1; if (is_bundle) { @@ -1248,12 +1249,16 @@ int cmd_clone(int argc, const char **argv, const char *prefix) * data from the --bundle-uri option. */ if (bundle_uri) { + int has_heuristic = 0; + /* At this point, we need the_repository to match the cloned repo. */ if (repo_init(the_repository, git_dir, work_tree)) warning(_("failed to initialize the repo, skipping bundle URI")); - else if (fetch_bundle_uri(the_repository, bundle_uri)) + else if (fetch_bundle_uri(the_repository, bundle_uri, &has_heuristic)) warning(_("failed to fetch objects from bundle URI '%s'"), bundle_uri); + else if (has_heuristic) + git_config_set_gently("fetch.bundleuri", bundle_uri); } strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD"); @@ -1413,7 +1418,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) free(unborn_head); free(dir); free(path); - UNLEAK(repo); + free(repo_to_free); junk_mode = JUNK_LEAVE_ALL; transport_ls_refs_options_release(&transport_ls_refs_options); diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c index e8f77f535f..93704f95a9 100644 --- a/builtin/commit-graph.c +++ b/builtin/commit-graph.c @@ -67,6 +67,7 @@ static int graph_verify(int argc, const char **argv, const char *prefix) int fd; struct stat st; int flags = 0; + int ret; static struct option builtin_commit_graph_verify_options[] = { OPT_BOOL(0, "shallow", &opts.shallow, @@ -111,8 +112,9 @@ static int graph_verify(int argc, const char **argv, const char *prefix) if (!graph) return !!open_ok; - UNLEAK(graph); - return verify_commit_graph(the_repository, graph, flags); + ret = verify_commit_graph(the_repository, graph, flags); + free_commit_graph(graph); + return ret; } extern int read_replace_refs; @@ -267,8 +269,8 @@ static int graph_write(int argc, const char **argv, const char *prefix) if (opts.reachable) { if (write_commit_graph_reachable(odb, flags, &write_opts)) - return 1; - return 0; + result = 1; + goto cleanup; } if (opts.stdin_packs) { diff --git a/builtin/commit.c b/builtin/commit.c index 44b763d7cd..985a0445b7 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -5,7 +5,7 @@ * Based on git-commit.sh by Junio C Hamano and Linus Torvalds */ -#define USE_THE_INDEX_COMPATIBILITY_MACROS +#define USE_THE_INDEX_VARIABLE #include "cache.h" #include "config.h" #include "lockfile.h" @@ -414,7 +414,7 @@ static const char *prepare_index(const char **argv, const char *prefix, discard_index(&the_index); read_index_from(&the_index, get_lock_file_path(&index_lock), get_git_dir()); - if (update_main_cache_tree(WRITE_TREE_SILENT) == 0) { + if (cache_tree_update(&the_index, WRITE_TREE_SILENT) == 0) { if (reopen_lock_file(&index_lock) < 0) die(_("unable to write index file")); if (write_locked_index(&the_index, &index_lock, 0)) @@ -444,7 +444,7 @@ static const char *prepare_index(const char **argv, const char *prefix, LOCK_DIE_ON_ERROR); add_files_to_cache(also ? prefix : NULL, &pathspec, 0); refresh_cache_or_die(refresh_flags); - update_main_cache_tree(WRITE_TREE_SILENT); + cache_tree_update(&the_index, WRITE_TREE_SILENT); if (write_locked_index(&the_index, &index_lock, 0)) die(_("unable to write new_index file")); commit_style = COMMIT_NORMAL; @@ -467,7 +467,7 @@ static const char *prepare_index(const char **argv, const char *prefix, refresh_cache_or_die(refresh_flags); if (the_index.cache_changed || !cache_tree_fully_valid(the_index.cache_tree)) - update_main_cache_tree(WRITE_TREE_SILENT); + cache_tree_update(&the_index, WRITE_TREE_SILENT); if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK | SKIP_IF_UNCHANGED)) die(_("unable to write new_index file")); @@ -516,7 +516,7 @@ static const char *prepare_index(const char **argv, const char *prefix, repo_hold_locked_index(the_repository, &index_lock, LOCK_DIE_ON_ERROR); add_remove_files(&partial); refresh_index(&the_index, REFRESH_QUIET, NULL, NULL, NULL); - update_main_cache_tree(WRITE_TREE_SILENT); + cache_tree_update(&the_index, WRITE_TREE_SILENT); if (write_locked_index(&the_index, &index_lock, 0)) die(_("unable to write new_index file")); @@ -991,9 +991,9 @@ static int prepare_to_commit(const char *index_file, const char *prefix, struct object_id oid; const char *parent = "HEAD"; - if (!active_nr) { - discard_cache(); - if (read_cache() < 0) + if (!the_index.cache_nr) { + discard_index(&the_index); + if (repo_read_index(the_repository) < 0) die(_("Cannot read index")); } @@ -1079,7 +1079,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, } read_index_from(&the_index, index_file, get_git_dir()); - if (update_main_cache_tree(0)) { + if (cache_tree_update(&the_index, 0)) { error(_("Error building trees")); return 0; } diff --git a/builtin/fetch.c b/builtin/fetch.c index 12978622d5..a09606b472 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -29,6 +29,7 @@ #include "commit-graph.h" #include "shallow.h" #include "worktree.h" +#include "bundle-uri.h" #define FORCED_UPDATES_DELAY_WARNING_IN_MS (10 * 1000) @@ -2109,6 +2110,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv, int cmd_fetch(int argc, const char **argv, const char *prefix) { int i; + const char *bundle_uri; struct string_list list = STRING_LIST_INIT_DUP; struct remote *remote = NULL; int result = 0; @@ -2194,6 +2196,13 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (dry_run) write_fetch_head = 0; + if (!max_jobs) + max_jobs = online_cpus(); + + if (!git_config_get_string_tmp("fetch.bundleuri", &bundle_uri) && + fetch_bundle_uri(the_repository, bundle_uri, NULL)) + warning(_("failed to fetch bundles from '%s'"), bundle_uri); + if (all) { if (argc == 1) die(_("fetch --all does not take a repository argument")); diff --git a/builtin/hook.c b/builtin/hook.c index b6530d189a..f95b7965c5 100644 --- a/builtin/hook.c +++ b/builtin/hook.c @@ -7,7 +7,7 @@ #include "strvec.h" #define BUILTIN_HOOK_RUN_USAGE \ - N_("git hook run [--ignore-missing] <hook-name> [-- <hook-args>]") + N_("git hook run [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>]") static const char * const builtin_hook_usage[] = { BUILTIN_HOOK_RUN_USAGE, @@ -28,6 +28,8 @@ static int run(int argc, const char **argv, const char *prefix) struct option run_options[] = { OPT_BOOL(0, "ignore-missing", &ignore_missing, N_("silently ignore missing requested <hook-name>")), + OPT_STRING(0, "to-stdin", &opt.path_to_stdin, N_("path"), + N_("file to read into hooks' stdin")), OPT_END(), }; int ret; diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 5d5ac03871..6516177348 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -8,7 +8,7 @@ static const char * const ls_remote_usage[] = { N_("git ls-remote [--heads] [--tags] [--refs] [--upload-pack=<exec>]\n" " [-q | --quiet] [--exit-code] [--get-url] [--sort=<key>]\n" - " [--symref] [<repository> [<refs>...]]"), + " [--symref] [<repository> [<patterns>...]]"), NULL }; diff --git a/builtin/merge.c b/builtin/merge.c index 74de2ebd2b..0a3c10a096 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -6,7 +6,7 @@ * Based on git-merge.sh by Junio C Hamano. */ -#define USE_THE_INDEX_COMPATIBILITY_MACROS +#define USE_THE_INDEX_VARIABLE #include "cache.h" #include "config.h" #include "parse-options.h" @@ -390,8 +390,8 @@ static void restore_state(const struct object_id *head, run_command(&cmd); refresh_cache: - discard_cache(); - if (read_cache() < 0) + discard_index(&the_index); + if (repo_read_index(the_repository) < 0) die(_("could not read index")); } @@ -706,7 +706,7 @@ static int read_tree_trivial(struct object_id *common, struct object_id *head, static void write_tree_trivial(struct object_id *oid) { - if (write_cache_as_tree(oid, 0, NULL)) + if (write_index_as_tree(oid, &the_index, get_index_file(), 0, NULL)) die(_("git write-tree failed to write a tree")); } @@ -1560,7 +1560,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix) !common->next && oideq(&common->item->object.oid, &head_commit->object.oid)) { /* Again the most common case of merging one remote. */ - struct strbuf msg = STRBUF_INIT; + const char *msg = have_message ? + "Fast-forward (no commit created; -m option ignored)" : + "Fast-forward"; struct commit *commit; if (verbosity >= 0) { @@ -1570,10 +1572,6 @@ int cmd_merge(int argc, const char **argv, const char *prefix) find_unique_abbrev(&remoteheads->item->object.oid, DEFAULT_ABBREV)); } - strbuf_addstr(&msg, "Fast-forward"); - if (have_message) - strbuf_addstr(&msg, - " (no commit created; -m option ignored)"); commit = remoteheads->item; if (!commit) { ret = 1; @@ -1592,9 +1590,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) goto done; } - finish(head_commit, remoteheads, &commit->object.oid, msg.buf); + finish(head_commit, remoteheads, &commit->object.oid, msg); remove_merge_branch_state(the_repository); - strbuf_release(&msg); goto done; } else if (!remoteheads->next && common->next) ; @@ -1621,7 +1618,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) error(_("Your local changes to the following files would be overwritten by merge:\n %s"), sb.buf); strbuf_release(&sb); - return 2; + ret = 2; + goto done; } /* See if it is really trivial. */ diff --git a/builtin/mv.c b/builtin/mv.c index 19790ce38f..edd7b931fd 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -3,7 +3,7 @@ * * Copyright (C) 2006 Johannes Schindelin */ -#define USE_THE_INDEX_COMPATIBILITY_MACROS +#define USE_THE_INDEX_VARIABLE #include "builtin.h" #include "config.h" #include "pathspec.h" @@ -489,7 +489,8 @@ remove_entry: if ((mode & SPARSE) && path_in_sparse_checkout(dst, &the_index)) { /* from out-of-cone to in-cone */ - int dst_pos = cache_name_pos(dst, strlen(dst)); + int dst_pos = index_name_pos(&the_index, dst, + strlen(dst)); struct cache_entry *dst_ce = the_index.cache[dst_pos]; dst_ce->ce_flags &= ~CE_SKIP_WORKTREE; @@ -500,7 +501,8 @@ remove_entry: !(mode & SPARSE) && !path_in_sparse_checkout(dst, &the_index)) { /* from in-cone to out-of-cone */ - int dst_pos = cache_name_pos(dst, strlen(dst)); + int dst_pos = index_name_pos(&the_index, dst, + strlen(dst)); struct cache_entry *dst_ce = the_index.cache[dst_pos]; /* diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 15535e914a..97959bfaf9 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -108,19 +108,11 @@ static int is_better_name(struct rev_name *name, int name_distance = effective_distance(name->distance, name->generation); int new_distance = effective_distance(distance, generation); - /* - * When comparing names based on tags, prefer names - * based on the older tag, even if it is farther away. - */ + /* If both are tags, we prefer the nearer one. */ if (from_tag && name->from_tag) - return (name->taggerdate > taggerdate || - (name->taggerdate == taggerdate && - name_distance > new_distance)); + return name_distance > new_distance; - /* - * We know that at least one of them is a non-tag at this point. - * favor a tag over a non-tag. - */ + /* Favor a tag over a non-tag. */ if (name->from_tag != from_tag) return from_tag; @@ -273,17 +265,6 @@ static int subpath_matches(const char *path, const char *filter) return -1; } -static const char *name_ref_abbrev(const char *refname, int shorten_unambiguous) -{ - if (shorten_unambiguous) - refname = shorten_unambiguous_ref(refname, 0); - else if (skip_prefix(refname, "refs/heads/", &refname)) - ; /* refname already advanced */ - else - skip_prefix(refname, "refs/", &refname); - return refname; -} - struct name_ref_data { int tags_only; int name_only; @@ -309,11 +290,19 @@ static void add_to_tip_table(const struct object_id *oid, const char *refname, int shorten_unambiguous, struct commit *commit, timestamp_t taggerdate, int from_tag, int deref) { - refname = name_ref_abbrev(refname, shorten_unambiguous); + char *short_refname = NULL; + + if (shorten_unambiguous) + short_refname = shorten_unambiguous_ref(refname, 0); + else if (skip_prefix(refname, "refs/heads/", &refname)) + ; /* refname already advanced */ + else + skip_prefix(refname, "refs/", &refname); ALLOC_GROW(tip_table.table, tip_table.nr + 1, tip_table.alloc); oidcpy(&tip_table.table[tip_table.nr].oid, oid); - tip_table.table[tip_table.nr].refname = xstrdup(refname); + tip_table.table[tip_table.nr].refname = short_refname ? + short_refname : xstrdup(refname); tip_table.table[tip_table.nr].commit = commit; tip_table.table[tip_table.nr].taggerdate = taggerdate; tip_table.table[tip_table.nr].from_tag = from_tag; diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 3395f63aba..74a167a180 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -1710,17 +1710,14 @@ static void pbase_tree_put(struct pbase_tree_cache *cache) free(cache); } -static int name_cmp_len(const char *name) +static size_t name_cmp_len(const char *name) { - int i; - for (i = 0; name[i] && name[i] != '\n' && name[i] != '/'; i++) - ; - return i; + return strcspn(name, "\n/"); } static void add_pbase_object(struct tree_desc *tree, const char *name, - int cmplen, + size_t cmplen, const char *fullname) { struct name_entry entry; @@ -1745,7 +1742,7 @@ static void add_pbase_object(struct tree_desc *tree, struct tree_desc sub; struct pbase_tree_cache *tree; const char *down = name+cmplen+1; - int downlen = name_cmp_len(down); + size_t downlen = name_cmp_len(down); tree = pbase_tree_get(&entry.oid); if (!tree) @@ -1797,7 +1794,7 @@ static int check_pbase_path(unsigned hash) static void add_preferred_base_object(const char *name) { struct pbase_tree *it; - int cmplen; + size_t cmplen; unsigned hash = pack_name_hash(name); if (!num_preferred_base || check_pbase_path(hash)) diff --git a/builtin/push.c b/builtin/push.c index 60ac8017e5..8f7d326ab3 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -63,16 +63,9 @@ static struct refspec rs = REFSPEC_INIT_PUSH; static struct string_list push_options_config = STRING_LIST_INIT_DUP; static void refspec_append_mapped(struct refspec *refspec, const char *ref, - struct remote *remote, struct ref *local_refs) + struct remote *remote, struct ref *matched) { const char *branch_name; - struct ref *matched = NULL; - - /* Does "ref" uniquely name our ref? */ - if (count_refspec_match(ref, local_refs, &matched) != 1) { - refspec_append(refspec, ref); - return; - } if (remote->push.nr) { struct refspec_item query; @@ -120,15 +113,28 @@ static void set_refspecs(const char **refs, int nr, const char *repo) die(_("--delete only accepts plain target ref names")); refspec_appendf(&rs, ":%s", ref); } else if (!strchr(ref, ':')) { - if (!remote) { - /* lazily grab remote and local_refs */ - remote = remote_get(repo); + struct ref *matched = NULL; + + /* lazily grab local_refs */ + if (!local_refs) local_refs = get_local_heads(); + + /* Does "ref" uniquely name our ref? */ + if (count_refspec_match(ref, local_refs, &matched) != 1) { + refspec_append(&rs, ref); + } else { + /* lazily grab remote */ + if (!remote) + remote = remote_get(repo); + if (!remote) + BUG("must get a remote for repo '%s'", repo); + + refspec_append_mapped(&rs, ref, remote, matched); } - refspec_append_mapped(&rs, ref, remote, local_refs); } else refspec_append(&rs, ref); } + free_refs(local_refs); } static int push_url_of_remote(struct remote *remote, const char ***url_p) diff --git a/builtin/rebase.c b/builtin/rebase.c index 7171be40ee..6635f10d52 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -254,7 +254,7 @@ static int init_basic_state(struct replay_opts *opts, const char *head_name, static int do_interactive_rebase(struct rebase_options *opts, unsigned flags) { - int ret; + int ret = -1; char *revisions = NULL, *shortrevisions = NULL; struct strvec make_script_args = STRVEC_INIT; struct todo_list todo_list = TODO_LIST_INIT; @@ -262,16 +262,12 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags) if (get_revision_ranges(opts->upstream, opts->onto, &opts->orig_head->object.oid, &revisions, &shortrevisions)) - return -1; + goto cleanup; if (init_basic_state(&replay, opts->head_name ? opts->head_name : "detached HEAD", - opts->onto, &opts->orig_head->object.oid)) { - free(revisions); - free(shortrevisions); - - return -1; - } + opts->onto, &opts->orig_head->object.oid)) + goto cleanup; if (!opts->upstream && opts->squash_onto) write_file(path_squash_onto(), "%s\n", @@ -300,6 +296,8 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags) opts->autosquash, opts->update_refs, &todo_list); } +cleanup: + replay_opts_release(&replay); free(revisions); free(shortrevisions); todo_list_release(&todo_list); @@ -341,6 +339,7 @@ static int run_sequencer_rebase(struct rebase_options *opts) struct replay_opts replay_opts = get_replay_opts(opts); ret = sequencer_continue(the_repository, &replay_opts); + replay_opts_release(&replay_opts); break; } case ACTION_EDIT_TODO: @@ -556,6 +555,7 @@ static int finish_rebase(struct rebase_options *opts) replay.action = REPLAY_INTERACTIVE_REBASE; ret = sequencer_remove_state(&replay); + replay_opts_release(&replay); } else { strbuf_addstr(&dir, opts->state_dir); if (remove_dir_recursively(&dir, 0)) @@ -1039,6 +1039,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) struct string_list strategy_options = STRING_LIST_INIT_NODUP; struct object_id squash_onto; char *squash_onto_name = NULL; + char *keep_base_onto_name = NULL; int reschedule_failed_exec = -1; int allow_preemptive_ff = 1; int preserve_merges_selected = 0; @@ -1327,6 +1328,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) replay.action = REPLAY_INTERACTIVE_REBASE; ret = sequencer_remove_state(&replay); + replay_opts_release(&replay); } else { strbuf_reset(&buf); strbuf_addstr(&buf, options.state_dir); @@ -1674,7 +1676,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addstr(&buf, options.upstream_name); strbuf_addstr(&buf, "..."); strbuf_addstr(&buf, branch_name); - options.onto_name = xstrdup(buf.buf); + options.onto_name = keep_base_onto_name = xstrdup(buf.buf); } else if (!options.onto_name) options.onto_name = options.upstream_name; if (strstr(options.onto_name, "...")) { @@ -1848,8 +1850,10 @@ cleanup: free(options.gpg_sign_opt); string_list_clear(&options.exec, 0); free(options.strategy); + free(options.strategy_opts); strbuf_release(&options.git_format_patch_opt); free(squash_onto_name); + free(keep_base_onto_name); string_list_clear(&strategy_options, 0); return !!ret; } diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index a90af30363..cd5c7a28ef 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -2032,6 +2032,16 @@ static struct command **queue_command(struct command **tail, return &cmd->next; } +static void free_commands(struct command *commands) +{ + while (commands) { + struct command *next = commands->next; + + free(commands); + commands = next; + } +} + static void queue_commands_from_cert(struct command **tail, struct strbuf *push_cert) { @@ -2569,6 +2579,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) run_receive_hook(commands, "post-receive", 1, &push_options); run_update_post_hook(commands); + free_commands(commands); string_list_clear(&push_options, 0); if (auto_gc) { struct child_process proc = CHILD_PROCESS_INIT; diff --git a/builtin/repack.c b/builtin/repack.c index c1402ad038..f649379531 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -948,7 +948,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) ret = start_command(&cmd); if (ret) - return ret; + goto cleanup; if (geometry) { FILE *in = xfdopen(cmd.in, "w"); @@ -977,7 +977,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) fclose(out); ret = finish_command(&cmd); if (ret) - return ret; + goto cleanup; if (!names.nr && !po_args.quiet) printf_ln(_("Nothing new to pack.")); @@ -1007,7 +1007,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) &existing_nonkept_packs, &existing_kept_packs); if (ret) - return ret; + goto cleanup; if (delete_redundant && expire_to) { /* @@ -1039,7 +1039,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) &existing_nonkept_packs, &existing_kept_packs); if (ret) - return ret; + goto cleanup; } } @@ -1115,7 +1115,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) string_list_clear(&include, 0); if (ret) - return ret; + goto cleanup; } reprepare_packed_git(the_repository); @@ -1172,10 +1172,11 @@ int cmd_repack(int argc, const char **argv, const char *prefix) write_midx_file(get_object_directory(), NULL, NULL, flags); } +cleanup: string_list_clear(&names, 1); string_list_clear(&existing_nonkept_packs, 0); string_list_clear(&existing_kept_packs, 0); clear_pack_geometry(geometry); - return 0; + return ret; } diff --git a/builtin/reset.c b/builtin/reset.c index fea20a9ba0..0697fa89de 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -26,6 +26,7 @@ #include "submodule.h" #include "submodule-config.h" #include "dir.h" +#include "add-interactive.h" #define REFRESH_INDEX_DELAY_WARNING_IN_MS (2 * 1000) @@ -390,7 +391,9 @@ int cmd_reset(int argc, const char **argv, const char *prefix) if (reset_type != NONE) die(_("options '%s' and '%s' cannot be used together"), "--patch", "--{hard,mixed,soft}"); trace2_cmd_mode("patch-interactive"); - return run_add_interactive(rev, "--patch=reset", &pathspec); + update_ref_status = !!run_add_p(the_repository, ADD_P_RESET, rev, + &pathspec); + goto cleanup; } /* git reset tree [--] paths... can be used to @@ -439,8 +442,10 @@ int cmd_reset(int argc, const char **argv, const char *prefix) LOCK_DIE_ON_ERROR); if (reset_type == MIXED) { int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN; - if (read_from_tree(&pathspec, &oid, intent_to_add)) - return 1; + if (read_from_tree(&pathspec, &oid, intent_to_add)) { + update_ref_status = 1; + goto cleanup; + } the_index.updated_skipworktree = 1; if (!no_refresh && get_git_work_tree()) { uint64_t t_begin, t_delta_in_ms; @@ -488,5 +493,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) discard_index(&the_index); +cleanup: + clear_pathspec(&pathspec); return update_ref_status; } diff --git a/builtin/revert.c b/builtin/revert.c index f2d86d2a8f..77d2035616 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -248,9 +248,7 @@ int cmd_revert(int argc, const char **argv, const char *prefix) res = run_sequencer(argc, argv, &opts); if (res < 0) die(_("revert failed")); - if (opts.revs) - release_revisions(opts.revs); - free(opts.revs); + replay_opts_release(&opts); return res; } @@ -262,10 +260,8 @@ int cmd_cherry_pick(int argc, const char **argv, const char *prefix) opts.action = REPLAY_PICK; sequencer_init_config(&opts); res = run_sequencer(argc, argv, &opts); - if (opts.revs) - release_revisions(opts.revs); - free(opts.revs); if (res < 0) die(_("cherry-pick failed")); + replay_opts_release(&opts); return res; } diff --git a/builtin/rm.c b/builtin/rm.c index 4a4aec0d00..8844f90655 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -3,7 +3,7 @@ * * Copyright (C) Linus Torvalds 2006 */ -#define USE_THE_INDEX_COMPATIBILITY_MACROS +#define USE_THE_INDEX_VARIABLE #include "builtin.h" #include "advice.h" #include "config.h" diff --git a/builtin/show-branch.c b/builtin/show-branch.c index c013abaf94..358ac3e519 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -956,5 +956,6 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) if (shown_merge_point && --extra < 0) break; } + free(head); return 0; } diff --git a/builtin/stash.c b/builtin/stash.c index 839569a980..3a4f9fd566 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -1,4 +1,4 @@ -#define USE_THE_INDEX_COMPATIBILITY_MACROS +#define USE_THE_INDEX_VARIABLE #include "builtin.h" #include "config.h" #include "parse-options.h" @@ -18,6 +18,7 @@ #include "diffcore.h" #include "exec-cmd.h" #include "reflog.h" +#include "add-interactive.h" #define INCLUDE_ALL_FILES 2 @@ -528,7 +529,8 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, NULL, NULL, NULL)) return -1; - if (write_cache_as_tree(&c_tree, 0, NULL)) + if (write_index_as_tree(&c_tree, &the_index, get_index_file(), 0, + NULL)) return error(_("cannot apply a stash in the middle of a merge")); if (index) { @@ -552,7 +554,8 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, discard_index(&the_index); repo_read_index(the_repository); - if (write_cache_as_tree(&index_tree, 0, NULL)) + if (write_index_as_tree(&index_tree, &the_index, + get_index_file(), 0, NULL)) return error(_("could not save index tree")); reset_head(); @@ -1229,7 +1232,7 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps, old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT)); setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1); - ret = run_add_interactive(NULL, "--patch=stash", ps); + ret = !!run_add_p(the_repository, ADD_P_STASH, NULL, ps); the_repository->index_file = old_repo_index_file; if (old_index_env && *old_index_env) @@ -1377,7 +1380,8 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b strbuf_addf(&commit_tree_label, "index on %s\n", msg.buf); commit_list_insert(head_commit, &parents); - if (write_cache_as_tree(&info->i_tree, 0, NULL) || + if (write_index_as_tree(&info->i_tree, &the_index, get_index_file(), 0, + NULL) || commit_tree(commit_tree_label.buf, commit_tree_label.len, &info->i_tree, parents, &info->i_commit, NULL, NULL)) { if (!quiet) @@ -1727,6 +1731,7 @@ static int push_stash(int argc, const char **argv, const char *prefix, OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), OPT_END() }; + int ret; if (argc) { force_assume = !strcmp(argv[0], "-p"); @@ -1766,8 +1771,10 @@ static int push_stash(int argc, const char **argv, const char *prefix, die(_("the option '%s' requires '%s'"), "--pathspec-file-nul", "--pathspec-from-file"); } - return do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode, - include_untracked, only_staged); + ret = do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode, + include_untracked, only_staged); + clear_pathspec(&ps); + return ret; } static int push_stash_unassumed(int argc, const char **argv, const char *prefix) diff --git a/builtin/update-index.c b/builtin/update-index.c index 82d5902cc8..bf38885d54 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -3,7 +3,7 @@ * * Copyright (C) Linus Torvalds, 2005 */ -#define USE_THE_INDEX_COMPATIBILITY_MACROS +#define USE_THE_INDEX_VARIABLE #include "cache.h" #include "bulk-checkin.h" #include "config.h" @@ -381,7 +381,7 @@ static int process_path(const char *path, struct stat *st, int stat_errno) if (has_symlink_leading_path(path, len)) return error("'%s' is beyond a symbolic link", path); - pos = cache_name_pos(path, len); + pos = index_name_pos(&the_index, path, len); ce = pos < 0 ? NULL : the_index.cache[pos]; if (ce && ce_skip_worktree(ce)) { /* diff --git a/builtin/worktree.c b/builtin/worktree.c index f51c40f1e1..254283aa6f 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -173,7 +173,7 @@ static void prune_worktrees(void) { struct strbuf reason = STRBUF_INIT; struct strbuf main_path = STRBUF_INIT; - struct string_list kept = STRING_LIST_INIT_NODUP; + struct string_list kept = STRING_LIST_INIT_DUP; DIR *dir = opendir(git_path("worktrees")); struct dirent *d; if (!dir) @@ -184,14 +184,14 @@ static void prune_worktrees(void) if (should_prune_worktree(d->d_name, &reason, &path, expire)) prune_worktree(d->d_name, reason.buf); else if (path) - string_list_append(&kept, path)->util = xstrdup(d->d_name); + string_list_append_nodup(&kept, path)->util = xstrdup(d->d_name); } closedir(dir); strbuf_add_absolute_path(&main_path, get_git_common_dir()); /* massage main worktree absolute path to match 'gitdir' content */ strbuf_strip_suffix(&main_path, "/."); - string_list_append(&kept, strbuf_detach(&main_path, NULL)); + string_list_append_nodup(&kept, strbuf_detach(&main_path, NULL)); prune_dups(&kept); string_list_clear(&kept, 1); diff --git a/builtin/write-tree.c b/builtin/write-tree.c index 45d61707e7..078010315f 100644 --- a/builtin/write-tree.c +++ b/builtin/write-tree.c @@ -3,7 +3,7 @@ * * Copyright (C) Linus Torvalds, 2005 */ -#define USE_THE_INDEX_COMPATIBILITY_MACROS +#define USE_THE_INDEX_VARIABLE #include "builtin.h" #include "cache.h" #include "config.h" @@ -38,7 +38,8 @@ int cmd_write_tree(int argc, const char **argv, const char *cmd_prefix) argc = parse_options(argc, argv, cmd_prefix, write_tree_options, write_tree_usage, 0); - ret = write_cache_as_tree(&oid, flags, tree_prefix); + ret = write_index_as_tree(&oid, &the_index, get_index_file(), flags, + tree_prefix); switch (ret) { case 0: printf("%s\n", oid_to_hex(&oid)); diff --git a/bundle-uri.c b/bundle-uri.c index 6462ab6deb..8a3c39ce57 100644 --- a/bundle-uri.c +++ b/bundle-uri.c @@ -9,6 +9,14 @@ #include "config.h" #include "remote.h" +static struct { + enum bundle_list_heuristic heuristic; + const char *name; +} heuristics[BUNDLE_HEURISTIC__COUNT] = { + { BUNDLE_HEURISTIC_NONE, ""}, + { BUNDLE_HEURISTIC_CREATIONTOKEN, "creationToken" }, +}; + static int compare_bundles(const void *hashmap_cmp_fn_data, const struct hashmap_entry *he1, const struct hashmap_entry *he2, @@ -75,6 +83,9 @@ static int summarize_bundle(struct remote_bundle_info *info, void *data) FILE *fp = data; fprintf(fp, "[bundle \"%s\"]\n", info->id); fprintf(fp, "\turi = %s\n", info->uri); + + if (info->creationToken) + fprintf(fp, "\tcreationToken = %"PRIu64"\n", info->creationToken); return 0; } @@ -100,6 +111,17 @@ void print_bundle_list(FILE *fp, struct bundle_list *list) fprintf(fp, "\tversion = %d\n", list->version); fprintf(fp, "\tmode = %s\n", mode); + if (list->heuristic) { + int i; + for (i = 0; i < BUNDLE_HEURISTIC__COUNT; i++) { + if (heuristics[i].heuristic == list->heuristic) { + printf("\theuristic = %s\n", + heuristics[list->heuristic].name); + break; + } + } + } + for_all_bundles_in_list(list, summarize_bundle, fp); } @@ -142,6 +164,21 @@ static int bundle_list_update(const char *key, const char *value, return 0; } + if (!strcmp(subkey, "heuristic")) { + int i; + for (i = 0; i < BUNDLE_HEURISTIC__COUNT; i++) { + if (heuristics[i].heuristic && + heuristics[i].name && + !strcmp(value, heuristics[i].name)) { + list->heuristic = heuristics[i].heuristic; + return 0; + } + } + + /* Ignore unknown heuristics. */ + return 0; + } + /* Ignore other unknown global keys. */ return 0; } @@ -169,6 +206,13 @@ static int bundle_list_update(const char *key, const char *value, return 0; } + if (!strcmp(subkey, "creationtoken")) { + if (sscanf(value, "%"PRIu64, &bundle->creationToken) != 1) + warning(_("could not parse bundle list key %s with value '%s'"), + "creationToken", value); + return 0; + } + /* * At this point, we ignore any information that we don't * understand, assuming it to be hints for a heuristic the client @@ -403,6 +447,183 @@ static int download_bundle_to_file(struct remote_bundle_info *bundle, void *data return 0; } +struct bundles_for_sorting { + struct remote_bundle_info **items; + size_t alloc; + size_t nr; +}; + +static int append_bundle(struct remote_bundle_info *bundle, void *data) +{ + struct bundles_for_sorting *list = data; + list->items[list->nr++] = bundle; + return 0; +} + +/** + * For use in QSORT() to get a list sorted by creationToken + * in decreasing order. + */ +static int compare_creation_token_decreasing(const void *va, const void *vb) +{ + const struct remote_bundle_info * const *a = va; + const struct remote_bundle_info * const *b = vb; + + if ((*a)->creationToken > (*b)->creationToken) + return -1; + if ((*a)->creationToken < (*b)->creationToken) + return 1; + return 0; +} + +static int fetch_bundles_by_token(struct repository *r, + struct bundle_list *list) +{ + int cur; + int move_direction = 0; + const char *creationTokenStr; + uint64_t maxCreationToken = 0, newMaxCreationToken = 0; + struct bundle_list_context ctx = { + .r = r, + .list = list, + .mode = list->mode, + }; + struct bundles_for_sorting bundles = { + .alloc = hashmap_get_size(&list->bundles), + }; + + ALLOC_ARRAY(bundles.items, bundles.alloc); + + for_all_bundles_in_list(list, append_bundle, &bundles); + + if (!bundles.nr) { + free(bundles.items); + return 0; + } + + QSORT(bundles.items, bundles.nr, compare_creation_token_decreasing); + + /* + * If fetch.bundleCreationToken exists, parses to a uint64t, and + * is not strictly smaller than the maximum creation token in the + * bundle list, then do not download any bundles. + */ + if (!repo_config_get_value(r, + "fetch.bundlecreationtoken", + &creationTokenStr) && + sscanf(creationTokenStr, "%"PRIu64, &maxCreationToken) == 1 && + bundles.items[0]->creationToken <= maxCreationToken) { + free(bundles.items); + return 0; + } + + /* + * Attempt to download and unbundle the minimum number of bundles by + * creationToken in decreasing order. If we fail to unbundle (after + * a successful download) then move to the next non-downloaded bundle + * and attempt downloading. Once we succeed in applying a bundle, + * move to the previous unapplied bundle and attempt to unbundle it + * again. + * + * In the case of a fresh clone, we will likely download all of the + * bundles before successfully unbundling the oldest one, then the + * rest of the bundles unbundle successfully in increasing order + * of creationToken. + * + * If there are existing objects, then this process may terminate + * early when all required commits from "new" bundles exist in the + * repo's object store. + */ + cur = 0; + while (cur >= 0 && cur < bundles.nr) { + struct remote_bundle_info *bundle = bundles.items[cur]; + + /* + * If we need to dig into bundles below the previous + * creation token value, then likely we are in an erroneous + * state due to missing or invalid bundles. Halt the process + * instead of continuing to download extra data. + */ + if (bundle->creationToken <= maxCreationToken) + break; + + if (!bundle->file) { + /* + * Not downloaded yet. Try downloading. + * + * Note that bundle->file is non-NULL if a download + * was attempted, even if it failed to download. + */ + if (fetch_bundle_uri_internal(ctx.r, bundle, ctx.depth + 1, ctx.list)) { + /* Mark as unbundled so we do not retry. */ + bundle->unbundled = 1; + + /* Try looking deeper in the list. */ + move_direction = 1; + goto move; + } + + /* We expect bundles when using creationTokens. */ + if (!is_bundle(bundle->file, 1)) { + warning(_("file downloaded from '%s' is not a bundle"), + bundle->uri); + break; + } + } + + if (bundle->file && !bundle->unbundled) { + /* + * This was downloaded, but not successfully + * unbundled. Try unbundling again. + */ + if (unbundle_from_file(ctx.r, bundle->file)) { + /* Try looking deeper in the list. */ + move_direction = 1; + } else { + /* + * Succeeded in unbundle. Retry bundles + * that previously failed to unbundle. + */ + move_direction = -1; + bundle->unbundled = 1; + + if (bundle->creationToken > newMaxCreationToken) + newMaxCreationToken = bundle->creationToken; + } + } + + /* + * Else case: downloaded and unbundled successfully. + * Skip this by moving in the same direction as the + * previous step. + */ + +move: + /* Move in the specified direction and repeat. */ + cur += move_direction; + } + + /* + * We succeed if the loop terminates because 'cur' drops below + * zero. The other case is that we terminate because 'cur' + * reaches the end of the list, so we have a failure no matter + * which bundles we apply from the list. + */ + if (cur < 0) { + struct strbuf value = STRBUF_INIT; + strbuf_addf(&value, "%"PRIu64"", newMaxCreationToken); + if (repo_config_set_multivar_gently(ctx.r, + "fetch.bundleCreationToken", + value.buf, NULL, 0)) + warning(_("failed to store maximum creation token")); + + strbuf_release(&value); + } + + free(bundles.items); + return cur >= 0; +} + static int download_bundle_list(struct repository *r, struct bundle_list *local_list, struct bundle_list *global_list, @@ -440,7 +661,15 @@ static int fetch_bundle_list_in_config_format(struct repository *r, goto cleanup; } - if ((result = download_bundle_list(r, &list_from_bundle, + /* + * If this list uses the creationToken heuristic, then the URIs + * it advertises are expected to be bundles, not nested lists. + * We can drop 'global_list' and 'depth'. + */ + if (list_from_bundle.heuristic == BUNDLE_HEURISTIC_CREATIONTOKEN) { + result = fetch_bundles_by_token(r, &list_from_bundle); + global_list->heuristic = BUNDLE_HEURISTIC_CREATIONTOKEN; + } else if ((result = download_bundle_list(r, &list_from_bundle, global_list, depth))) goto cleanup; @@ -551,7 +780,8 @@ static int unlink_bundle(struct remote_bundle_info *info, void *data) return 0; } -int fetch_bundle_uri(struct repository *r, const char *uri) +int fetch_bundle_uri(struct repository *r, const char *uri, + int *has_heuristic) { int result; struct bundle_list list; @@ -571,6 +801,8 @@ int fetch_bundle_uri(struct repository *r, const char *uri) result = unbundle_all_bundles(r, &list); cleanup: + if (has_heuristic) + *has_heuristic = (list.heuristic != BUNDLE_HEURISTIC_NONE); for_all_bundles_in_list(&list, unlink_bundle, NULL); clear_bundle_list(&list); clear_remote_bundle_info(&bundle, NULL); @@ -582,6 +814,14 @@ int fetch_bundle_list(struct repository *r, struct bundle_list *list) int result; struct bundle_list global_list; + /* + * If the creationToken heuristic is used, then the URIs + * advertised by 'list' are not nested lists and instead + * direct bundles. We do not need to use global_list. + */ + if (list->heuristic == BUNDLE_HEURISTIC_CREATIONTOKEN) + return fetch_bundles_by_token(r, list); + init_bundle_list(&global_list); /* If a bundle is added to this global list, then it is required. */ @@ -590,7 +830,10 @@ int fetch_bundle_list(struct repository *r, struct bundle_list *list) if ((result = download_bundle_list(r, list, &global_list, 0))) goto cleanup; - result = unbundle_all_bundles(r, &global_list); + if (list->heuristic == BUNDLE_HEURISTIC_CREATIONTOKEN) + result = fetch_bundles_by_token(r, list); + else + result = unbundle_all_bundles(r, &global_list); cleanup: for_all_bundles_in_list(&global_list, unlink_bundle, NULL); diff --git a/bundle-uri.h b/bundle-uri.h index d5e89f1671..6dbc780f66 100644 --- a/bundle-uri.h +++ b/bundle-uri.h @@ -42,6 +42,12 @@ struct remote_bundle_info { * this boolean is true. */ unsigned unbundled:1; + + /** + * If the bundle is part of a list with the creationToken + * heuristic, then we use this member for sorting the bundles. + */ + uint64_t creationToken; }; #define REMOTE_BUNDLE_INFO_INIT { 0 } @@ -52,6 +58,14 @@ enum bundle_list_mode { BUNDLE_MODE_ANY }; +enum bundle_list_heuristic { + BUNDLE_HEURISTIC_NONE = 0, + BUNDLE_HEURISTIC_CREATIONTOKEN, + + /* Must be last. */ + BUNDLE_HEURISTIC__COUNT +}; + /** * A bundle_list contains an unordered set of remote_bundle_info structs, * as well as information about the bundle listing, such as version and @@ -75,6 +89,12 @@ struct bundle_list { * advertised by the bundle list at that location. */ char *baseURI; + + /** + * A list can have a heuristic, which helps reduce the number of + * downloaded bundles. + */ + enum bundle_list_heuristic heuristic; }; void init_bundle_list(struct bundle_list *list); @@ -104,8 +124,14 @@ int bundle_uri_parse_config_format(const char *uri, * based on that information. * * Returns non-zero if no bundle information is found at the given 'uri'. + * + * If the pointer 'has_heuristic' is non-NULL, then the value it points to + * will be set to be non-zero if and only if the fetched list has a + * heuristic value. Such a value indicates that the list was designed for + * incremental fetches. */ -int fetch_bundle_uri(struct repository *r, const char *uri); +int fetch_bundle_uri(struct repository *r, const char *uri, + int *has_heuristic); /** * Given a bundle list that was already advertised (likely by the @@ -12,6 +12,7 @@ #include "refs.h" #include "strvec.h" #include "list-objects-filter-options.h" +#include "connected.h" static const char v2_bundle_signature[] = "# v2 git bundle\n"; static const char v3_bundle_signature[] = "# v3 git bundle\n"; @@ -187,6 +188,21 @@ static int list_refs(struct string_list *r, int argc, const char **argv) /* Remember to update object flag allocation in object.h */ #define PREREQ_MARK (1u<<16) +struct string_list_iterator { + struct string_list *list; + size_t cur; +}; + +static const struct object_id *iterate_ref_map(void *cb_data) +{ + struct string_list_iterator *iter = cb_data; + + if (iter->cur >= iter->list->nr) + return NULL; + + return iter->list->items[iter->cur++].util; +} + int verify_bundle(struct repository *r, struct bundle_header *header, enum verify_bundle_flags flags) @@ -196,26 +212,25 @@ int verify_bundle(struct repository *r, * to be verbose about the errors */ struct string_list *p = &header->prerequisites; - struct rev_info revs = REV_INFO_INIT; - const char *argv[] = {NULL, "--all", NULL}; - struct commit *commit; - int i, ret = 0, req_nr; + int i, ret = 0; const char *message = _("Repository lacks these prerequisite commits:"); + struct string_list_iterator iter = { + .list = p, + }; + struct check_connected_options opts = { + .quiet = 1, + }; if (!r || !r->objects || !r->objects->odb) return error(_("need a repository to verify a bundle")); - repo_init_revisions(r, &revs, NULL); for (i = 0; i < p->nr; i++) { struct string_list_item *e = p->items + i; const char *name = e->string; struct object_id *oid = e->util; struct object *o = parse_object(r, oid); - if (o) { - o->flags |= PREREQ_MARK; - add_pending_object(&revs, o, name); + if (o) continue; - } ret++; if (flags & VERIFY_BUNDLE_QUIET) continue; @@ -223,37 +238,14 @@ int verify_bundle(struct repository *r, error("%s", message); error("%s %s", oid_to_hex(oid), name); } - if (revs.pending.nr != p->nr) + if (ret) goto cleanup; - req_nr = revs.pending.nr; - setup_revisions(2, argv, &revs, NULL); - list_objects_filter_copy(&revs.filter, &header->filter); - - if (prepare_revision_walk(&revs)) - die(_("revision walk setup failed")); - - i = req_nr; - while (i && (commit = get_revision(&revs))) - if (commit->object.flags & PREREQ_MARK) - i--; - - for (i = 0; i < p->nr; i++) { - struct string_list_item *e = p->items + i; - const char *name = e->string; - const struct object_id *oid = e->util; - struct object *o = parse_object(r, oid); - assert(o); /* otherwise we'd have returned early */ - if (o->flags & SHOWN) - continue; - ret++; - if (flags & VERIFY_BUNDLE_QUIET) - continue; - if (ret == 1) - error("%s", message); - error("%s %s", oid_to_hex(oid), name); - } + if ((ret = check_connected(iterate_ref_map, &iter, &opts))) + error(_("some prerequisite commits exist in the object store, " + "but are not connected to the repository's history")); + /* TODO: preserve this verbose language. */ if (flags & VERIFY_BUNDLE_VERBOSE) { struct string_list *r; @@ -282,15 +274,6 @@ int verify_bundle(struct repository *r, list_objects_filter_spec(&header->filter)); } cleanup: - /* Clean up objects used, as they will be reused. */ - for (i = 0; i < p->nr; i++) { - struct string_list_item *e = p->items + i; - struct object_id *oid = e->util; - commit = lookup_commit_reference_gently(r, oid, 1); - if (commit) - clear_commit_marks(commit, ALL_REV_FLAGS | PREREQ_MARK); - } - release_revisions(&revs); return ret; } @@ -627,6 +610,10 @@ int unbundle(struct repository *r, struct bundle_header *header, enum verify_bundle_flags flags) { struct child_process ip = CHILD_PROCESS_INIT; + + if (verify_bundle(r, header, flags)) + return -1; + strvec_pushl(&ip.args, "index-pack", "--fix-thin", "--stdin", NULL); /* If there is a filter, then we need to create the promisor pack. */ @@ -638,8 +625,6 @@ int unbundle(struct repository *r, struct bundle_header *header, strvec_clear(extra_index_pack_args); } - if (verify_bundle(r, header, flags)) - return -1; ip.in = bundle_fd; ip.no_stdout = 1; ip.git_cmd = 1; diff --git a/cache-tree.c b/cache-tree.c index 9af457f47c..88c2c04f87 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -760,7 +760,7 @@ static void prime_cache_tree_rec(struct repository *r, struct tree_desc desc; struct name_entry entry; int cnt; - int base_path_len = tree_path->len; + size_t base_path_len = tree_path->len; oidcpy(&it->oid, &tree->object.oid); @@ -785,7 +785,6 @@ static void prime_cache_tree_rec(struct repository *r, */ if (r->index->sparse_index) { strbuf_setlen(tree_path, base_path_len); - strbuf_grow(tree_path, base_path_len + entry.pathlen + 1); strbuf_add(tree_path, entry.path, entry.pathlen); strbuf_addch(tree_path, '/'); } diff --git a/cache-tree.h b/cache-tree.h index 8efeccebfc..bd97caa07b 100644 --- a/cache-tree.h +++ b/cache-tree.h @@ -53,19 +53,4 @@ int write_index_as_tree(struct object_id *oid, struct index_state *index_state, void prime_cache_tree(struct repository *, struct index_state *, struct tree *); int cache_tree_matches_traversal(struct cache_tree *, struct name_entry *ent, struct traverse_info *info); - -#ifdef USE_THE_INDEX_COMPATIBILITY_MACROS -static inline int write_cache_as_tree(struct object_id *oid, int flags, const char *prefix) -{ - return write_index_as_tree(oid, &the_index, get_index_file(), flags, prefix); -} - -static inline int update_main_cache_tree(int flags) -{ - if (!the_index.cache_tree) - the_index.cache_tree = cache_tree(); - return cache_tree_update(&the_index, flags); -} -#endif - #endif @@ -449,18 +449,8 @@ typedef int (*must_prefetch_predicate)(const struct cache_entry *); void prefetch_cache_entries(const struct index_state *istate, must_prefetch_predicate must_prefetch); -#if defined(USE_THE_INDEX_COMPATIBILITY_MACROS) || defined(USE_THE_INDEX_VARIABLE) +#ifdef USE_THE_INDEX_VARIABLE extern struct index_state the_index; - -#ifndef USE_THE_INDEX_VARIABLE -#ifdef USE_THE_INDEX_COMPATIBILITY_MACROS -#define active_nr (the_index.cache_nr) - -#define read_cache() repo_read_index(the_repository) -#define discard_cache() discard_index(&the_index) -#define cache_name_pos(name, namelen) index_name_pos(&the_index,(name),(namelen)) -#endif -#endif #endif #define TYPE_BITS 3 @@ -1623,8 +1613,10 @@ int repo_interpret_branch_name(struct repository *r, int validate_headref(const char *ref); -int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2); -int df_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2); +int base_name_compare(const char *name1, size_t len1, int mode1, + const char *name2, size_t len2, int mode2); +int df_name_compare(const char *name1, size_t len1, int mode1, + const char *name2, size_t len2, int mode2); int name_compare(const char *name1, size_t len1, const char *name2, size_t len2); int cache_name_stage_compare(const char *name1, int len1, int stage1, const char *name2, int len2, int stage2); diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh index 8ebff42596..b098e10f52 100755 --- a/ci/run-build-and-tests.sh +++ b/ci/run-build-and-tests.sh @@ -26,7 +26,6 @@ linux-TEST-vars) export GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=1 export GIT_TEST_MULTI_PACK_INDEX=1 export GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=1 - export GIT_TEST_ADD_I_USE_BUILTIN=0 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master export GIT_TEST_WRITE_REV_INDEX=1 export GIT_TEST_CHECKOUT_WORKERS=2 @@ -1033,6 +1033,7 @@ struct commit *get_fork_point(const char *refname, struct commit *commit) ret = bases->item; cleanup_return: + free(revs.commit); free_commit_list(bases); free(full_refname); return ret; @@ -274,8 +274,6 @@ struct ref; int for_each_commit_graft(each_commit_graft_fn, void *); int interactive_add(const char **argv, const char *prefix, int patch); -int run_add_interactive(const char *revision, const char *patch_mode, - const struct pathspec *pathspec); struct commit_extra_header { struct commit_extra_header *next; @@ -448,15 +448,6 @@ void git_configset_init(struct config_set *cs); int git_configset_add_file(struct config_set *cs, const char *filename); /** - * Parses command line options and environment variables, and adds the - * variable-value pairs to the `config_set`. Returns 0 on success, or -1 - * if there is an error in parsing. The caller decides whether to free - * the incomplete configset or continue using it when the function - * returns -1. - */ -int git_configset_add_parameters(struct config_set *cs); - -/** * Finds and returns the value list, sorted in order of increasing priority * for the configuration variable `key` and config set `cs`. When the * configuration variable `key` is not found, returns NULL. The caller diff --git a/contrib/coccinelle/index-compatibility.cocci b/contrib/coccinelle/index-compatibility.cocci index 8520f03128..31e36cf3c4 100644 --- a/contrib/coccinelle/index-compatibility.cocci +++ b/contrib/coccinelle/index-compatibility.cocci @@ -1,6 +1,7 @@ // the_index.* variables @@ identifier AC = active_cache; +identifier AN = active_nr; identifier ACC = active_cache_changed; identifier ACT = active_cache_tree; @@ @@ -8,6 +9,9 @@ identifier ACT = active_cache_tree; - AC + the_index.cache | +- AN ++ the_index.cache_nr +| - ACC + the_index.cache_changed | @@ -15,19 +19,13 @@ identifier ACT = active_cache_tree; + the_index.cache_tree ) -@@ -identifier AN = active_nr; -identifier f != prepare_to_commit; -@@ - f(...) {<... -- AN -+ the_index.cache_nr - ...>} - // "the_repository" simple cases @@ @@ ( +- read_cache ++ repo_read_index +| - read_cache_unmerged + repo_read_index_unmerged | @@ -96,6 +94,15 @@ identifier f != prepare_to_commit; | - resolve_undo_clear + resolve_undo_clear_index +| +- cache_name_pos ++ index_name_pos +| +- update_main_cache_tree ++ cache_tree_update +| +- discard_cache ++ discard_index ) ( + &the_index, @@ -137,3 +144,14 @@ identifier f != prepare_to_commit; ... + , NULL, NULL, NULL ) + +@@ +expression O; +@@ +- write_cache_as_tree ++ write_index_as_tree + ( +- O, ++ O, &the_index, get_index_file(), + ... + ) diff --git a/contrib/coccinelle/index-compatibility.pending.cocci b/contrib/coccinelle/index-compatibility.pending.cocci deleted file mode 100644 index 01f875d006..0000000000 --- a/contrib/coccinelle/index-compatibility.pending.cocci +++ /dev/null @@ -1,24 +0,0 @@ -// "the_repository" simple cases -@@ -@@ -( -- read_cache -+ repo_read_index -) - ( -+ the_repository, - ...) - -// "the_index" simple cases -@@ -@@ -( -- discard_cache -+ discard_index -| -- cache_name_pos -+ index_name_pos -) - ( -+ &the_index, - ...) diff --git a/delta-islands.c b/delta-islands.c index 8b234cb85b..afdec0a878 100644 --- a/delta-islands.c +++ b/delta-islands.c @@ -517,11 +517,13 @@ void free_island_marks(void) { struct island_bitmap *bitmap; - kh_foreach_value(island_marks, bitmap, { - if (!--bitmap->refcount) - free(bitmap); - }); - kh_destroy_oid_map(island_marks); + if (island_marks) { + kh_foreach_value(island_marks, bitmap, { + if (!--bitmap->refcount) + free(bitmap); + }); + kh_destroy_oid_map(island_marks); + } /* detect use-after-free with a an address which is never valid: */ island_marks = (void *)-1; @@ -3437,6 +3437,22 @@ static int diff_filepair_is_phoney(struct diff_filespec *one, return !DIFF_FILE_VALID(one) && !DIFF_FILE_VALID(two); } +static int set_diff_algorithm(struct diff_options *opts, + const char *alg) +{ + long value = parse_algorithm_value(alg); + + if (value < 0) + return -1; + + /* clear out previous settings */ + DIFF_XDL_CLR(opts, NEED_MINIMAL); + opts->xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK; + opts->xdl_opts |= value; + + return 0; +} + static void builtin_diff(const char *name_a, const char *name_b, struct diff_filespec *one, @@ -4440,15 +4456,13 @@ static void run_diff_cmd(const char *pgm, const char *xfrm_msg = NULL; int complete_rewrite = (p->status == DIFF_STATUS_MODIFIED) && p->score; int must_show_header = 0; + struct userdiff_driver *drv = NULL; - - if (o->flags.allow_external) { - struct userdiff_driver *drv; - + if (o->flags.allow_external || !o->ignore_driver_algorithm) drv = userdiff_find_by_path(o->repo->index, attr_path); - if (drv && drv->external) - pgm = drv->external; - } + + if (o->flags.allow_external && drv && drv->external) + pgm = drv->external; if (msg) { /* @@ -4465,12 +4479,16 @@ static void run_diff_cmd(const char *pgm, run_external_diff(pgm, name, other, one, two, xfrm_msg, o); return; } - if (one && two) + if (one && two) { + if (!o->ignore_driver_algorithm && drv && drv->algorithm) + set_diff_algorithm(o, drv->algorithm); + builtin_diff(name, other ? other : name, one, two, xfrm_msg, must_show_header, o, complete_rewrite); - else + } else { fprintf(o->file, "* Unmerged path %s\n", name); + } } static void diff_fill_oid_info(struct diff_filespec *one, struct index_state *istate) @@ -4567,6 +4585,14 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o, const char *name; const char *other; + if (!o->ignore_driver_algorithm) { + struct userdiff_driver *drv = userdiff_find_by_path(o->repo->index, + p->one->path); + + if (drv && drv->algorithm) + set_diff_algorithm(o, drv->algorithm); + } + if (DIFF_PAIR_UNMERGED(p)) { /* unmerged */ builtin_diffstat(p->one->path, NULL, NULL, NULL, @@ -5107,17 +5133,32 @@ static int diff_opt_diff_algorithm(const struct option *opt, const char *arg, int unset) { struct diff_options *options = opt->value; - long value = parse_algorithm_value(arg); BUG_ON_OPT_NEG(unset); - if (value < 0) + + if (set_diff_algorithm(options, arg)) return error(_("option diff-algorithm accepts \"myers\", " "\"minimal\", \"patience\" and \"histogram\"")); - /* clear out previous settings */ - DIFF_XDL_CLR(options, NEED_MINIMAL); - options->xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK; - options->xdl_opts |= value; + options->ignore_driver_algorithm = 1; + + return 0; +} + +static int diff_opt_diff_algorithm_no_arg(const struct option *opt, + const char *arg, int unset) +{ + struct diff_options *options = opt->value; + + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); + + if (set_diff_algorithm(options, opt->long_name)) + BUG("available diff algorithms include \"myers\", " + "\"minimal\", \"patience\" and \"histogram\""); + + options->ignore_driver_algorithm = 1; + return 0; } @@ -5250,7 +5291,6 @@ static int diff_opt_patience(const struct option *opt, BUG_ON_OPT_NEG(unset); BUG_ON_OPT_ARG(arg); - options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF); /* * Both --patience and --anchored use PATIENCE_DIFF * internally, so remove any anchors previously @@ -5259,7 +5299,9 @@ static int diff_opt_patience(const struct option *opt, for (i = 0; i < options->anchors_nr; i++) free(options->anchors[i]); options->anchors_nr = 0; - return 0; + options->ignore_driver_algorithm = 1; + + return set_diff_algorithm(options, "patience"); } static int diff_opt_ignore_regex(const struct option *opt, @@ -5562,9 +5604,10 @@ struct option *add_diff_options(const struct option *opts, N_("prevent rename/copy detection if the number of rename/copy targets exceeds given limit")), OPT_GROUP(N_("Diff algorithm options")), - OPT_BIT(0, "minimal", &options->xdl_opts, - N_("produce the smallest possible diff"), - XDF_NEED_MINIMAL), + OPT_CALLBACK_F(0, "minimal", options, NULL, + N_("produce the smallest possible diff"), + PARSE_OPT_NONEG | PARSE_OPT_NOARG, + diff_opt_diff_algorithm_no_arg), OPT_BIT_F('w', "ignore-all-space", &options->xdl_opts, N_("ignore whitespace when comparing lines"), XDF_IGNORE_WHITESPACE, PARSE_OPT_NONEG), @@ -5590,9 +5633,10 @@ struct option *add_diff_options(const struct option *opts, N_("generate diff using the \"patience diff\" algorithm"), PARSE_OPT_NONEG | PARSE_OPT_NOARG, diff_opt_patience), - OPT_BITOP(0, "histogram", &options->xdl_opts, - N_("generate diff using the \"histogram diff\" algorithm"), - XDF_HISTOGRAM_DIFF, XDF_DIFF_ALGORITHM_MASK), + OPT_CALLBACK_F(0, "histogram", options, NULL, + N_("generate diff using the \"histogram diff\" algorithm"), + PARSE_OPT_NONEG | PARSE_OPT_NOARG, + diff_opt_diff_algorithm_no_arg), OPT_CALLBACK_F(0, "diff-algorithm", options, N_("<algorithm>"), N_("choose a diff algorithm"), PARSE_OPT_NONEG, diff_opt_diff_algorithm), @@ -333,6 +333,7 @@ struct diff_options { int prefix_length; const char *stat_sep; int xdl_opts; + int ignore_driver_algorithm; /* see Documentation/diff-options.txt */ char **anchors; diff --git a/dir-iterator.c b/dir-iterator.c index b17e9f970a..cedd304759 100644 --- a/dir-iterator.c +++ b/dir-iterator.c @@ -112,10 +112,7 @@ static int prepare_next_entry_data(struct dir_iterator_int *iter, iter->base.basename = iter->base.path.buf + iter->levels[iter->levels_nr - 1].prefix_len; - if (iter->flags & DIR_ITERATOR_FOLLOW_SYMLINKS) - err = stat(iter->base.path.buf, &iter->base.st); - else - err = lstat(iter->base.path.buf, &iter->base.st); + err = lstat(iter->base.path.buf, &iter->base.st); saved_errno = errno; if (err && errno != ENOENT) @@ -203,7 +200,7 @@ struct dir_iterator *dir_iterator_begin(const char *path, unsigned int flags) { struct dir_iterator_int *iter = xcalloc(1, sizeof(*iter)); struct dir_iterator *dir_iterator = &iter->base; - int saved_errno; + int saved_errno, err; strbuf_init(&iter->base.path, PATH_MAX); strbuf_addstr(&iter->base.path, path); @@ -213,10 +210,12 @@ struct dir_iterator *dir_iterator_begin(const char *path, unsigned int flags) iter->flags = flags; /* - * Note: stat already checks for NULL or empty strings and - * inexistent paths. + * Note: lstat already checks for NULL or empty strings and + * nonexistent paths. */ - if (stat(iter->base.path.buf, &iter->base.st) < 0) { + err = lstat(iter->base.path.buf, &iter->base.st); + + if (err < 0) { saved_errno = errno; goto error_out; } diff --git a/dir-iterator.h b/dir-iterator.h index 08229157c6..479e1ec784 100644 --- a/dir-iterator.h +++ b/dir-iterator.h @@ -54,19 +54,8 @@ * and ITER_ERROR is returned immediately. In both cases, a meaningful * warning is emitted. Note: ENOENT errors are always ignored so that * the API users may remove files during iteration. - * - * - DIR_ITERATOR_FOLLOW_SYMLINKS: make dir-iterator follow symlinks. - * i.e., linked directories' contents will be iterated over and - * iter->base.st will contain information on the referred files, - * not the symlinks themselves, which is the default behavior. Broken - * symlinks are ignored. - * - * Warning: circular symlinks are also followed when - * DIR_ITERATOR_FOLLOW_SYMLINKS is set. The iteration may end up with - * an ELOOP if they happen and DIR_ITERATOR_PEDANTIC is set. */ #define DIR_ITERATOR_PEDANTIC (1 << 0) -#define DIR_ITERATOR_FOLLOW_SYMLINKS (1 << 1) struct dir_iterator { /* The current path: */ @@ -83,9 +72,7 @@ struct dir_iterator { const char *basename; /* - * The result of calling lstat() on path; or stat(), if the - * DIR_ITERATOR_FOLLOW_SYMLINKS flag was set at - * dir_iterator's initialization. + * The result of calling lstat() on path. */ struct stat st; }; diff --git a/git-add--interactive.perl b/git-add--interactive.perl deleted file mode 100755 index 95887fd8e5..0000000000 --- a/git-add--interactive.perl +++ /dev/null @@ -1,1920 +0,0 @@ -#!/usr/bin/perl - -use 5.008; -use strict; -use warnings; -use Git qw(unquote_path); -use Git::I18N; - -binmode(STDOUT, ":raw"); - -my $repo = Git->repository(); - -my $menu_use_color = $repo->get_colorbool('color.interactive'); -my ($prompt_color, $header_color, $help_color) = - $menu_use_color ? ( - $repo->get_color('color.interactive.prompt', 'bold blue'), - $repo->get_color('color.interactive.header', 'bold'), - $repo->get_color('color.interactive.help', 'red bold'), - ) : (); -my $error_color = (); -if ($menu_use_color) { - my $help_color_spec = ($repo->config('color.interactive.help') or - 'red bold'); - $error_color = $repo->get_color('color.interactive.error', - $help_color_spec); -} - -my $diff_use_color = $repo->get_colorbool('color.diff'); -my ($fraginfo_color) = - $diff_use_color ? ( - $repo->get_color('color.diff.frag', 'cyan'), - ) : (); -my ($diff_context_color) = - $diff_use_color ? ( - $repo->get_color($repo->config('color.diff.context') ? 'color.diff.context' : 'color.diff.plain', ''), - ) : (); -my ($diff_old_color) = - $diff_use_color ? ( - $repo->get_color('color.diff.old', 'red'), - ) : (); -my ($diff_new_color) = - $diff_use_color ? ( - $repo->get_color('color.diff.new', 'green'), - ) : (); - -my $normal_color = $repo->get_color("", "reset"); - -my $diff_algorithm = $repo->config('diff.algorithm'); -my $diff_filter = $repo->config('interactive.difffilter'); - -my $use_readkey = 0; -my $use_termcap = 0; -my %term_escapes; - -sub ReadMode; -sub ReadKey; -if ($repo->config_bool("interactive.singlekey")) { - eval { - require Term::ReadKey; - Term::ReadKey->import; - $use_readkey = 1; - }; - if (!$use_readkey) { - print STDERR "missing Term::ReadKey, disabling interactive.singlekey\n"; - } - eval { - require Term::Cap; - my $termcap = Term::Cap->Tgetent; - foreach (values %$termcap) { - $term_escapes{$_} = 1 if /^\e/; - } - $use_termcap = 1; - }; -} - -sub colored { - my $color = shift; - my $string = join("", @_); - - if (defined $color) { - # Put a color code at the beginning of each line, a reset at the end - # color after newlines that are not at the end of the string - $string =~ s/(\n+)(.)/$1$color$2/g; - # reset before newlines - $string =~ s/(\n+)/$normal_color$1/g; - # codes at beginning and end (if necessary): - $string =~ s/^/$color/; - $string =~ s/$/$normal_color/ unless $string =~ /\n$/; - } - return $string; -} - -# command line options -my $patch_mode_only; -my $patch_mode; -my $patch_mode_revision; - -sub apply_patch; -sub apply_patch_for_checkout_commit; -sub apply_patch_for_stash; - -my %patch_modes = ( - 'stage' => { - DIFF => 'diff-files -p', - APPLY => sub { apply_patch 'apply --cached', @_; }, - APPLY_CHECK => 'apply --cached', - FILTER => 'file-only', - IS_REVERSE => 0, - }, - 'stash' => { - DIFF => 'diff-index -p HEAD', - APPLY => sub { apply_patch 'apply --cached', @_; }, - APPLY_CHECK => 'apply --cached', - FILTER => undef, - IS_REVERSE => 0, - }, - 'reset_head' => { - DIFF => 'diff-index -p --cached', - APPLY => sub { apply_patch 'apply -R --cached', @_; }, - APPLY_CHECK => 'apply -R --cached', - FILTER => 'index-only', - IS_REVERSE => 1, - }, - 'reset_nothead' => { - DIFF => 'diff-index -R -p --cached', - APPLY => sub { apply_patch 'apply --cached', @_; }, - APPLY_CHECK => 'apply --cached', - FILTER => 'index-only', - IS_REVERSE => 0, - }, - 'checkout_index' => { - DIFF => 'diff-files -p', - APPLY => sub { apply_patch 'apply -R', @_; }, - APPLY_CHECK => 'apply -R', - FILTER => 'file-only', - IS_REVERSE => 1, - }, - 'checkout_head' => { - DIFF => 'diff-index -p', - APPLY => sub { apply_patch_for_checkout_commit '-R', @_ }, - APPLY_CHECK => 'apply -R', - FILTER => undef, - IS_REVERSE => 1, - }, - 'checkout_nothead' => { - DIFF => 'diff-index -R -p', - APPLY => sub { apply_patch_for_checkout_commit '', @_ }, - APPLY_CHECK => 'apply', - FILTER => undef, - IS_REVERSE => 0, - }, - 'worktree_head' => { - DIFF => 'diff-index -p', - APPLY => sub { apply_patch 'apply -R', @_ }, - APPLY_CHECK => 'apply -R', - FILTER => undef, - IS_REVERSE => 1, - }, - 'worktree_nothead' => { - DIFF => 'diff-index -R -p', - APPLY => sub { apply_patch 'apply', @_ }, - APPLY_CHECK => 'apply', - FILTER => undef, - IS_REVERSE => 0, - }, -); - -$patch_mode = 'stage'; -my %patch_mode_flavour = %{$patch_modes{$patch_mode}}; - -sub run_cmd_pipe { - if ($^O eq 'MSWin32') { - my @invalid = grep {m/[":*]/} @_; - die "$^O does not support: @invalid\n" if @invalid; - my @args = map { m/ /o ? "\"$_\"": $_ } @_; - return qx{@args}; - } else { - my $fh = undef; - open($fh, '-|', @_) or die; - my @out = <$fh>; - close $fh || die "Cannot close @_ ($!)"; - return @out; - } -} - -my ($GIT_DIR) = run_cmd_pipe(qw(git rev-parse --git-dir)); - -if (!defined $GIT_DIR) { - exit(1); # rev-parse would have already said "not a git repo" -} -chomp($GIT_DIR); - -sub refresh { - my $fh; - open $fh, 'git update-index --refresh |' - or die; - while (<$fh>) { - ;# ignore 'needs update' - } - close $fh; -} - -sub list_untracked { - map { - chomp $_; - unquote_path($_); - } - run_cmd_pipe(qw(git ls-files --others --exclude-standard --), @ARGV); -} - -# TRANSLATORS: you can adjust this to align "git add -i" status menu -my $status_fmt = __('%12s %12s %s'); -my $status_head = sprintf($status_fmt, __('staged'), __('unstaged'), __('path')); - -{ - my $initial; - sub is_initial_commit { - $initial = system('git rev-parse HEAD -- >/dev/null 2>&1') != 0 - unless defined $initial; - return $initial; - } -} - -{ - my $empty_tree; - sub get_empty_tree { - return $empty_tree if defined $empty_tree; - - ($empty_tree) = run_cmd_pipe(qw(git hash-object -t tree /dev/null)); - chomp $empty_tree; - return $empty_tree; - } -} - -sub get_diff_reference { - my $ref = shift; - if (defined $ref and $ref ne 'HEAD') { - return $ref; - } elsif (is_initial_commit()) { - return get_empty_tree(); - } else { - return 'HEAD'; - } -} - -# Returns list of hashes, contents of each of which are: -# VALUE: pathname -# BINARY: is a binary path -# INDEX: is index different from HEAD? -# FILE: is file different from index? -# INDEX_ADDDEL: is it add/delete between HEAD and index? -# FILE_ADDDEL: is it add/delete between index and file? -# UNMERGED: is the path unmerged - -sub list_modified { - my ($only) = @_; - my (%data, @return); - my ($add, $del, $adddel, $file); - - my $reference = get_diff_reference($patch_mode_revision); - for (run_cmd_pipe(qw(git diff-index --cached - --numstat --summary), $reference, - '--', @ARGV)) { - if (($add, $del, $file) = - /^([-\d]+) ([-\d]+) (.*)/) { - my ($change, $bin); - $file = unquote_path($file); - if ($add eq '-' && $del eq '-') { - $change = __('binary'); - $bin = 1; - } - else { - $change = "+$add/-$del"; - } - $data{$file} = { - INDEX => $change, - BINARY => $bin, - FILE => __('nothing'), - } - } - elsif (($adddel, $file) = - /^ (create|delete) mode [0-7]+ (.*)$/) { - $file = unquote_path($file); - $data{$file}{INDEX_ADDDEL} = $adddel; - } - } - - for (run_cmd_pipe(qw(git diff-files --ignore-submodules=dirty --numstat --summary --raw --), @ARGV)) { - if (($add, $del, $file) = - /^([-\d]+) ([-\d]+) (.*)/) { - $file = unquote_path($file); - my ($change, $bin); - if ($add eq '-' && $del eq '-') { - $change = __('binary'); - $bin = 1; - } - else { - $change = "+$add/-$del"; - } - $data{$file}{FILE} = $change; - if ($bin) { - $data{$file}{BINARY} = 1; - } - } - elsif (($adddel, $file) = - /^ (create|delete) mode [0-7]+ (.*)$/) { - $file = unquote_path($file); - $data{$file}{FILE_ADDDEL} = $adddel; - } - elsif (/^:[0-7]+ [0-7]+ [0-9a-f]+ [0-9a-f]+ (.) (.*)$/) { - $file = unquote_path($2); - if (!exists $data{$file}) { - $data{$file} = +{ - INDEX => __('unchanged'), - BINARY => 0, - }; - } - if ($1 eq 'U') { - $data{$file}{UNMERGED} = 1; - } - } - } - - for (sort keys %data) { - my $it = $data{$_}; - - if ($only) { - if ($only eq 'index-only') { - next if ($it->{INDEX} eq __('unchanged')); - } - if ($only eq 'file-only') { - next if ($it->{FILE} eq __('nothing')); - } - } - push @return, +{ - VALUE => $_, - %$it, - }; - } - return @return; -} - -sub find_unique { - my ($string, @stuff) = @_; - my $found = undef; - for (my $i = 0; $i < @stuff; $i++) { - my $it = $stuff[$i]; - my $hit = undef; - if (ref $it) { - if ((ref $it) eq 'ARRAY') { - $it = $it->[0]; - } - else { - $it = $it->{VALUE}; - } - } - eval { - if ($it =~ /^$string/) { - $hit = 1; - }; - }; - if (defined $hit && defined $found) { - return undef; - } - if ($hit) { - $found = $i + 1; - } - } - return $found; -} - -# inserts string into trie and updates count for each character -sub update_trie { - my ($trie, $string) = @_; - foreach (split //, $string) { - $trie = $trie->{$_} ||= {COUNT => 0}; - $trie->{COUNT}++; - } -} - -# returns an array of tuples (prefix, remainder) -sub find_unique_prefixes { - my @stuff = @_; - my @return = (); - - # any single prefix exceeding the soft limit is omitted - # if any prefix exceeds the hard limit all are omitted - # 0 indicates no limit - my $soft_limit = 0; - my $hard_limit = 3; - - # build a trie modelling all possible options - my %trie; - foreach my $print (@stuff) { - if ((ref $print) eq 'ARRAY') { - $print = $print->[0]; - } - elsif ((ref $print) eq 'HASH') { - $print = $print->{VALUE}; - } - update_trie(\%trie, $print); - push @return, $print; - } - - # use the trie to find the unique prefixes - for (my $i = 0; $i < @return; $i++) { - my $ret = $return[$i]; - my @letters = split //, $ret; - my %search = %trie; - my ($prefix, $remainder); - my $j; - for ($j = 0; $j < @letters; $j++) { - my $letter = $letters[$j]; - if ($search{$letter}{COUNT} == 1) { - $prefix = substr $ret, 0, $j + 1; - $remainder = substr $ret, $j + 1; - last; - } - else { - my $prefix = substr $ret, 0, $j; - return () - if ($hard_limit && $j + 1 > $hard_limit); - } - %search = %{$search{$letter}}; - } - if (ord($letters[0]) > 127 || - ($soft_limit && $j + 1 > $soft_limit)) { - $prefix = undef; - $remainder = $ret; - } - $return[$i] = [$prefix, $remainder]; - } - return @return; -} - -# filters out prefixes which have special meaning to list_and_choose() -sub is_valid_prefix { - my $prefix = shift; - return (defined $prefix) && - !($prefix =~ /[\s,]/) && # separators - !($prefix =~ /^-/) && # deselection - !($prefix =~ /^\d+/) && # selection - ($prefix ne '*') && # "all" wildcard - ($prefix ne '?'); # prompt help -} - -# given a prefix/remainder tuple return a string with the prefix highlighted -# for now use square brackets; later might use ANSI colors (underline, bold) -sub highlight_prefix { - my $prefix = shift; - my $remainder = shift; - - if (!defined $prefix) { - return $remainder; - } - - if (!is_valid_prefix($prefix)) { - return "$prefix$remainder"; - } - - if (!$menu_use_color) { - return "[$prefix]$remainder"; - } - - return "$prompt_color$prefix$normal_color$remainder"; -} - -sub error_msg { - print STDERR colored $error_color, @_; -} - -sub list_and_choose { - my ($opts, @stuff) = @_; - my (@chosen, @return); - if (!@stuff) { - return @return; - } - my $i; - my @prefixes = find_unique_prefixes(@stuff) unless $opts->{LIST_ONLY}; - - TOPLOOP: - while (1) { - my $last_lf = 0; - - if ($opts->{HEADER}) { - my $indent = $opts->{LIST_FLAT} ? "" : " "; - print colored $header_color, "$indent$opts->{HEADER}\n"; - } - for ($i = 0; $i < @stuff; $i++) { - my $chosen = $chosen[$i] ? '*' : ' '; - my $print = $stuff[$i]; - my $ref = ref $print; - my $highlighted = highlight_prefix(@{$prefixes[$i]}) - if @prefixes; - if ($ref eq 'ARRAY') { - $print = $highlighted || $print->[0]; - } - elsif ($ref eq 'HASH') { - my $value = $highlighted || $print->{VALUE}; - $print = sprintf($status_fmt, - $print->{INDEX}, - $print->{FILE}, - $value); - } - else { - $print = $highlighted || $print; - } - printf("%s%2d: %s", $chosen, $i+1, $print); - if (($opts->{LIST_FLAT}) && - (($i + 1) % ($opts->{LIST_FLAT}))) { - print "\t"; - $last_lf = 0; - } - else { - print "\n"; - $last_lf = 1; - } - } - if (!$last_lf) { - print "\n"; - } - - return if ($opts->{LIST_ONLY}); - - print colored $prompt_color, $opts->{PROMPT}; - if ($opts->{SINGLETON}) { - print "> "; - } - else { - print ">> "; - } - my $line = <STDIN>; - if (!$line) { - print "\n"; - $opts->{ON_EOF}->() if $opts->{ON_EOF}; - last; - } - chomp $line; - last if $line eq ''; - if ($line eq '?') { - $opts->{SINGLETON} ? - singleton_prompt_help_cmd() : - prompt_help_cmd(); - next TOPLOOP; - } - for my $choice (split(/[\s,]+/, $line)) { - my $choose = 1; - my ($bottom, $top); - - # Input that begins with '-'; unchoose - if ($choice =~ s/^-//) { - $choose = 0; - } - # A range can be specified like 5-7 or 5-. - if ($choice =~ /^(\d+)-(\d*)$/) { - ($bottom, $top) = ($1, length($2) ? $2 : 1 + @stuff); - } - elsif ($choice =~ /^\d+$/) { - $bottom = $top = $choice; - } - elsif ($choice eq '*') { - $bottom = 1; - $top = 1 + @stuff; - } - else { - $bottom = $top = find_unique($choice, @stuff); - if (!defined $bottom) { - error_msg sprintf(__("Huh (%s)?\n"), $choice); - next TOPLOOP; - } - } - if ($opts->{SINGLETON} && $bottom != $top) { - error_msg sprintf(__("Huh (%s)?\n"), $choice); - next TOPLOOP; - } - for ($i = $bottom-1; $i <= $top-1; $i++) { - next if (@stuff <= $i || $i < 0); - $chosen[$i] = $choose; - } - } - last if ($opts->{IMMEDIATE} || $line eq '*'); - } - for ($i = 0; $i < @stuff; $i++) { - if ($chosen[$i]) { - push @return, $stuff[$i]; - } - } - return @return; -} - -sub singleton_prompt_help_cmd { - print colored $help_color, __ <<'EOF' ; -Prompt help: -1 - select a numbered item -foo - select item based on unique prefix - - (empty) select nothing -EOF -} - -sub prompt_help_cmd { - print colored $help_color, __ <<'EOF' ; -Prompt help: -1 - select a single item -3-5 - select a range of items -2-3,6-9 - select multiple ranges -foo - select item based on unique prefix --... - unselect specified items -* - choose all items - - (empty) finish selecting -EOF -} - -sub status_cmd { - list_and_choose({ LIST_ONLY => 1, HEADER => $status_head }, - list_modified()); - print "\n"; -} - -sub say_n_paths { - my $did = shift @_; - my $cnt = scalar @_; - if ($did eq 'added') { - printf(__n("added %d path\n", "added %d paths\n", - $cnt), $cnt); - } elsif ($did eq 'updated') { - printf(__n("updated %d path\n", "updated %d paths\n", - $cnt), $cnt); - } elsif ($did eq 'reverted') { - printf(__n("reverted %d path\n", "reverted %d paths\n", - $cnt), $cnt); - } else { - printf(__n("touched %d path\n", "touched %d paths\n", - $cnt), $cnt); - } -} - -sub update_cmd { - my @mods = list_modified('file-only'); - return if (!@mods); - - my @update = list_and_choose({ PROMPT => __('Update'), - HEADER => $status_head, }, - @mods); - if (@update) { - system(qw(git update-index --add --remove --), - map { $_->{VALUE} } @update); - say_n_paths('updated', @update); - } - print "\n"; -} - -sub revert_cmd { - my @update = list_and_choose({ PROMPT => __('Revert'), - HEADER => $status_head, }, - list_modified()); - if (@update) { - if (is_initial_commit()) { - system(qw(git rm --cached), - map { $_->{VALUE} } @update); - } - else { - my @lines = run_cmd_pipe(qw(git ls-tree HEAD --), - map { $_->{VALUE} } @update); - my $fh; - open $fh, '| git update-index --index-info' - or die; - for (@lines) { - print $fh $_; - } - close($fh); - for (@update) { - if ($_->{INDEX_ADDDEL} && - $_->{INDEX_ADDDEL} eq 'create') { - system(qw(git update-index --force-remove --), - $_->{VALUE}); - printf(__("note: %s is untracked now.\n"), $_->{VALUE}); - } - } - } - refresh(); - say_n_paths('reverted', @update); - } - print "\n"; -} - -sub add_untracked_cmd { - my @add = list_and_choose({ PROMPT => __('Add untracked') }, - list_untracked()); - if (@add) { - system(qw(git update-index --add --), @add); - say_n_paths('added', @add); - } else { - print __("No untracked files.\n"); - } - print "\n"; -} - -sub run_git_apply { - my $cmd = shift; - my $fh; - open $fh, '| git ' . $cmd . " --allow-overlap"; - print $fh @_; - return close $fh; -} - -sub parse_diff { - my ($path) = @_; - my @diff_cmd = split(" ", $patch_mode_flavour{DIFF}); - if (defined $diff_algorithm) { - splice @diff_cmd, 1, 0, "--diff-algorithm=${diff_algorithm}"; - } - if (defined $patch_mode_revision) { - push @diff_cmd, get_diff_reference($patch_mode_revision); - } - my @diff = run_cmd_pipe("git", @diff_cmd, qw(--no-color --), $path); - my @colored = (); - if ($diff_use_color) { - my @display_cmd = ("git", @diff_cmd, qw(--color --), $path); - if (defined $diff_filter) { - # quotemeta is overkill, but sufficient for shell-quoting - my $diff = join(' ', map { quotemeta } @display_cmd); - @display_cmd = ("$diff | $diff_filter"); - } - - @colored = run_cmd_pipe(@display_cmd); - } - my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' }; - - if (@colored && @colored != @diff) { - print STDERR - "fatal: mismatched output from interactive.diffFilter\n", - "hint: Your filter must maintain a one-to-one correspondence\n", - "hint: between its input and output lines.\n"; - exit 1; - } - - for (my $i = 0; $i < @diff; $i++) { - if ($diff[$i] =~ /^@@ /) { - push @hunk, { TEXT => [], DISPLAY => [], - TYPE => 'hunk' }; - } - push @{$hunk[-1]{TEXT}}, $diff[$i]; - push @{$hunk[-1]{DISPLAY}}, - (@colored ? $colored[$i] : $diff[$i]); - } - return @hunk; -} - -sub parse_diff_header { - my $src = shift; - - my $head = { TEXT => [], DISPLAY => [], TYPE => 'header' }; - my $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' }; - my $deletion = { TEXT => [], DISPLAY => [], TYPE => 'deletion' }; - my $addition; - - for (my $i = 0; $i < @{$src->{TEXT}}; $i++) { - if ($src->{TEXT}->[$i] =~ /^new file/) { - $addition = 1; - $head->{TYPE} = 'addition'; - } - my $dest = - $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode : - $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion : - $head; - push @{$dest->{TEXT}}, $src->{TEXT}->[$i]; - push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i]; - } - return ($head, $mode, $deletion, $addition); -} - -sub hunk_splittable { - my ($text) = @_; - - my @s = split_hunk($text); - return (1 < @s); -} - -sub parse_hunk_header { - my ($line) = @_; - my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = - $line =~ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/; - $o_cnt = 1 unless defined $o_cnt; - $n_cnt = 1 unless defined $n_cnt; - return ($o_ofs, $o_cnt, $n_ofs, $n_cnt); -} - -sub format_hunk_header { - my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = @_; - return ("@@ -$o_ofs" . - (($o_cnt != 1) ? ",$o_cnt" : '') . - " +$n_ofs" . - (($n_cnt != 1) ? ",$n_cnt" : '') . - " @@\n"); -} - -sub split_hunk { - my ($text, $display) = @_; - my @split = (); - if (!defined $display) { - $display = $text; - } - # If there are context lines in the middle of a hunk, - # it can be split, but we would need to take care of - # overlaps later. - - my ($o_ofs, undef, $n_ofs) = parse_hunk_header($text->[0]); - my $hunk_start = 1; - - OUTER: - while (1) { - my $next_hunk_start = undef; - my $i = $hunk_start - 1; - my $this = +{ - TEXT => [], - DISPLAY => [], - TYPE => 'hunk', - OLD => $o_ofs, - NEW => $n_ofs, - OCNT => 0, - NCNT => 0, - ADDDEL => 0, - POSTCTX => 0, - USE => undef, - }; - - while (++$i < @$text) { - my $line = $text->[$i]; - my $display = $display->[$i]; - if ($line =~ /^\\/) { - push @{$this->{TEXT}}, $line; - push @{$this->{DISPLAY}}, $display; - next; - } - if ($line =~ /^ /) { - if ($this->{ADDDEL} && - !defined $next_hunk_start) { - # We have seen leading context and - # adds/dels and then here is another - # context, which is trailing for this - # split hunk and leading for the next - # one. - $next_hunk_start = $i; - } - push @{$this->{TEXT}}, $line; - push @{$this->{DISPLAY}}, $display; - $this->{OCNT}++; - $this->{NCNT}++; - if (defined $next_hunk_start) { - $this->{POSTCTX}++; - } - next; - } - - # add/del - if (defined $next_hunk_start) { - # We are done with the current hunk and - # this is the first real change for the - # next split one. - $hunk_start = $next_hunk_start; - $o_ofs = $this->{OLD} + $this->{OCNT}; - $n_ofs = $this->{NEW} + $this->{NCNT}; - $o_ofs -= $this->{POSTCTX}; - $n_ofs -= $this->{POSTCTX}; - push @split, $this; - redo OUTER; - } - push @{$this->{TEXT}}, $line; - push @{$this->{DISPLAY}}, $display; - $this->{ADDDEL}++; - if ($line =~ /^-/) { - $this->{OCNT}++; - } - else { - $this->{NCNT}++; - } - } - - push @split, $this; - last; - } - - for my $hunk (@split) { - $o_ofs = $hunk->{OLD}; - $n_ofs = $hunk->{NEW}; - my $o_cnt = $hunk->{OCNT}; - my $n_cnt = $hunk->{NCNT}; - - my $head = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt); - my $display_head = $head; - unshift @{$hunk->{TEXT}}, $head; - if ($diff_use_color) { - $display_head = colored($fraginfo_color, $head); - } - unshift @{$hunk->{DISPLAY}}, $display_head; - } - return @split; -} - -sub find_last_o_ctx { - my ($it) = @_; - my $text = $it->{TEXT}; - my ($o_ofs, $o_cnt) = parse_hunk_header($text->[0]); - my $i = @{$text}; - my $last_o_ctx = $o_ofs + $o_cnt; - while (0 < --$i) { - my $line = $text->[$i]; - if ($line =~ /^ /) { - $last_o_ctx--; - next; - } - last; - } - return $last_o_ctx; -} - -sub merge_hunk { - my ($prev, $this) = @_; - my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) = - parse_hunk_header($prev->{TEXT}[0]); - my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) = - parse_hunk_header($this->{TEXT}[0]); - - my (@line, $i, $ofs, $o_cnt, $n_cnt); - $ofs = $o0_ofs; - $o_cnt = $n_cnt = 0; - for ($i = 1; $i < @{$prev->{TEXT}}; $i++) { - my $line = $prev->{TEXT}[$i]; - if ($line =~ /^\+/) { - $n_cnt++; - push @line, $line; - next; - } elsif ($line =~ /^\\/) { - push @line, $line; - next; - } - - last if ($o1_ofs <= $ofs); - - $o_cnt++; - $ofs++; - if ($line =~ /^ /) { - $n_cnt++; - } - push @line, $line; - } - - for ($i = 1; $i < @{$this->{TEXT}}; $i++) { - my $line = $this->{TEXT}[$i]; - if ($line =~ /^\+/) { - $n_cnt++; - push @line, $line; - next; - } elsif ($line =~ /^\\/) { - push @line, $line; - next; - } - $ofs++; - $o_cnt++; - if ($line =~ /^ /) { - $n_cnt++; - } - push @line, $line; - } - my $head = format_hunk_header($o0_ofs, $o_cnt, $n0_ofs, $n_cnt); - @{$prev->{TEXT}} = ($head, @line); -} - -sub coalesce_overlapping_hunks { - my (@in) = @_; - my @out = (); - - my ($last_o_ctx, $last_was_dirty); - my $ofs_delta = 0; - - for (@in) { - if ($_->{TYPE} ne 'hunk') { - push @out, $_; - next; - } - my $text = $_->{TEXT}; - my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = - parse_hunk_header($text->[0]); - unless ($_->{USE}) { - $ofs_delta += $o_cnt - $n_cnt; - # If this hunk has been edited then subtract - # the delta that is due to the edit. - if ($_->{OFS_DELTA}) { - $ofs_delta -= $_->{OFS_DELTA}; - } - next; - } - if ($ofs_delta) { - if ($patch_mode_flavour{IS_REVERSE}) { - $o_ofs -= $ofs_delta; - } else { - $n_ofs += $ofs_delta; - } - $_->{TEXT}->[0] = format_hunk_header($o_ofs, $o_cnt, - $n_ofs, $n_cnt); - } - # If this hunk was edited then adjust the offset delta - # to reflect the edit. - if ($_->{OFS_DELTA}) { - $ofs_delta += $_->{OFS_DELTA}; - } - if (defined $last_o_ctx && - $o_ofs <= $last_o_ctx && - !$_->{DIRTY} && - !$last_was_dirty) { - merge_hunk($out[-1], $_); - } - else { - push @out, $_; - } - $last_o_ctx = find_last_o_ctx($out[-1]); - $last_was_dirty = $_->{DIRTY}; - } - return @out; -} - -sub reassemble_patch { - my $head = shift; - my @patch; - - # Include everything in the header except the beginning of the diff. - push @patch, (grep { !/^[-+]{3}/ } @$head); - - # Then include any headers from the hunk lines, which must - # come before any actual hunk. - while (@_ && $_[0] !~ /^@/) { - push @patch, shift; - } - - # Then begin the diff. - push @patch, grep { /^[-+]{3}/ } @$head; - - # And then the actual hunks. - push @patch, @_; - - return @patch; -} - -sub color_diff { - return map { - colored((/^@/ ? $fraginfo_color : - /^\+/ ? $diff_new_color : - /^-/ ? $diff_old_color : - $diff_context_color), - $_); - } @_; -} - -my %edit_hunk_manually_modes = ( - stage => N__( -"If the patch applies cleanly, the edited hunk will immediately be -marked for staging."), - stash => N__( -"If the patch applies cleanly, the edited hunk will immediately be -marked for stashing."), - reset_head => N__( -"If the patch applies cleanly, the edited hunk will immediately be -marked for unstaging."), - reset_nothead => N__( -"If the patch applies cleanly, the edited hunk will immediately be -marked for applying."), - checkout_index => N__( -"If the patch applies cleanly, the edited hunk will immediately be -marked for discarding."), - checkout_head => N__( -"If the patch applies cleanly, the edited hunk will immediately be -marked for discarding."), - checkout_nothead => N__( -"If the patch applies cleanly, the edited hunk will immediately be -marked for applying."), - worktree_head => N__( -"If the patch applies cleanly, the edited hunk will immediately be -marked for discarding."), - worktree_nothead => N__( -"If the patch applies cleanly, the edited hunk will immediately be -marked for applying."), -); - -sub recount_edited_hunk { - local $_; - my ($oldtext, $newtext) = @_; - my ($o_cnt, $n_cnt) = (0, 0); - for (@{$newtext}[1..$#{$newtext}]) { - my $mode = substr($_, 0, 1); - if ($mode eq '-') { - $o_cnt++; - } elsif ($mode eq '+') { - $n_cnt++; - } elsif ($mode eq ' ' or $mode eq "\n") { - $o_cnt++; - $n_cnt++; - } - } - my ($o_ofs, undef, $n_ofs, undef) = - parse_hunk_header($newtext->[0]); - $newtext->[0] = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt); - my (undef, $orig_o_cnt, undef, $orig_n_cnt) = - parse_hunk_header($oldtext->[0]); - # Return the change in the number of lines inserted by this hunk - return $orig_o_cnt - $orig_n_cnt - $o_cnt + $n_cnt; -} - -sub edit_hunk_manually { - my ($oldtext) = @_; - - my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff"; - my $fh; - open $fh, '>', $hunkfile - or die sprintf(__("failed to open hunk edit file for writing: %s"), $!); - print $fh Git::comment_lines __("Manual hunk edit mode -- see bottom for a quick guide.\n"); - print $fh @$oldtext; - my $is_reverse = $patch_mode_flavour{IS_REVERSE}; - my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-'); - my $comment_line_char = Git::get_comment_line_char; - print $fh Git::comment_lines sprintf(__ <<EOF, $remove_minus, $remove_plus, $comment_line_char), ---- -To remove '%s' lines, make them ' ' lines (context). -To remove '%s' lines, delete them. -Lines starting with %s will be removed. -EOF -__($edit_hunk_manually_modes{$patch_mode}), -# TRANSLATORS: 'it' refers to the patch mentioned in the previous messages. -__ <<EOF2 ; -If it does not apply cleanly, you will be given an opportunity to -edit again. If all lines of the hunk are removed, then the edit is -aborted and the hunk is left unchanged. -EOF2 - close $fh; - - chomp(my ($editor) = run_cmd_pipe(qw(git var GIT_EDITOR))); - system('sh', '-c', $editor.' "$@"', $editor, $hunkfile); - - if ($? != 0) { - return undef; - } - - open $fh, '<', $hunkfile - or die sprintf(__("failed to open hunk edit file for reading: %s"), $!); - my @newtext = grep { !/^\Q$comment_line_char\E/ } <$fh>; - close $fh; - unlink $hunkfile; - - # Abort if nothing remains - if (!grep { /\S/ } @newtext) { - return undef; - } - - # Reinsert the first hunk header if the user accidentally deleted it - if ($newtext[0] !~ /^@/) { - unshift @newtext, $oldtext->[0]; - } - return \@newtext; -} - -sub diff_applies { - return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --check', - map { @{$_->{TEXT}} } @_); -} - -sub _restore_terminal_and_die { - ReadMode 'restore'; - print "\n"; - exit 1; -} - -sub prompt_single_character { - if ($use_readkey) { - local $SIG{TERM} = \&_restore_terminal_and_die; - local $SIG{INT} = \&_restore_terminal_and_die; - ReadMode 'cbreak'; - my $key = ReadKey 0; - ReadMode 'restore'; - if (defined $key) { - if ($use_termcap and $key eq "\e") { - while (!defined $term_escapes{$key}) { - my $next = ReadKey 0.5; - last if (!defined $next); - $key .= $next; - } - $key =~ s/\e/^[/; - } - print "$key"; - } - print "\n"; - return $key; - } else { - return <STDIN>; - } -} - -sub prompt_yesno { - my ($prompt) = @_; - while (1) { - print colored $prompt_color, $prompt; - my $line = prompt_single_character; - return undef unless defined $line; - return 0 if $line =~ /^n/i; - return 1 if $line =~ /^y/i; - } -} - -sub edit_hunk_loop { - my ($head, $hunks, $ix) = @_; - my $hunk = $hunks->[$ix]; - my $text = $hunk->{TEXT}; - - while (1) { - my $newtext = edit_hunk_manually($text); - if (!defined $newtext) { - return undef; - } - my $newhunk = { - TEXT => $newtext, - TYPE => $hunk->{TYPE}, - USE => 1, - DIRTY => 1, - }; - $newhunk->{OFS_DELTA} = recount_edited_hunk($text, $newtext); - # If this hunk has already been edited then add the - # offset delta of the previous edit to get the real - # delta from the original unedited hunk. - $hunk->{OFS_DELTA} and - $newhunk->{OFS_DELTA} += $hunk->{OFS_DELTA}; - if (diff_applies($head, - @{$hunks}[0..$ix-1], - $newhunk, - @{$hunks}[$ix+1..$#{$hunks}])) { - $newhunk->{DISPLAY} = [color_diff(@{$newtext})]; - return $newhunk; - } - else { - prompt_yesno( - # TRANSLATORS: do not translate [y/n] - # The program will only accept that input - # at this point. - # Consider translating (saying "no" discards!) as - # (saying "n" for "no" discards!) if the translation - # of the word "no" does not start with n. - __('Your edited hunk does not apply. Edit again ' - . '(saying "no" discards!) [y/n]? ') - ) or return undef; - } - } -} - -my %help_patch_modes = ( - stage => N__( -"y - stage this hunk -n - do not stage this hunk -q - quit; do not stage this hunk or any of the remaining ones -a - stage this hunk and all later hunks in the file -d - do not stage this hunk or any of the later hunks in the file"), - stash => N__( -"y - stash this hunk -n - do not stash this hunk -q - quit; do not stash this hunk or any of the remaining ones -a - stash this hunk and all later hunks in the file -d - do not stash this hunk or any of the later hunks in the file"), - reset_head => N__( -"y - unstage this hunk -n - do not unstage this hunk -q - quit; do not unstage this hunk or any of the remaining ones -a - unstage this hunk and all later hunks in the file -d - do not unstage this hunk or any of the later hunks in the file"), - reset_nothead => N__( -"y - apply this hunk to index -n - do not apply this hunk to index -q - quit; do not apply this hunk or any of the remaining ones -a - apply this hunk and all later hunks in the file -d - do not apply this hunk or any of the later hunks in the file"), - checkout_index => N__( -"y - discard this hunk from worktree -n - do not discard this hunk from worktree -q - quit; do not discard this hunk or any of the remaining ones -a - discard this hunk and all later hunks in the file -d - do not discard this hunk or any of the later hunks in the file"), - checkout_head => N__( -"y - discard this hunk from index and worktree -n - do not discard this hunk from index and worktree -q - quit; do not discard this hunk or any of the remaining ones -a - discard this hunk and all later hunks in the file -d - do not discard this hunk or any of the later hunks in the file"), - checkout_nothead => N__( -"y - apply this hunk to index and worktree -n - do not apply this hunk to index and worktree -q - quit; do not apply this hunk or any of the remaining ones -a - apply this hunk and all later hunks in the file -d - do not apply this hunk or any of the later hunks in the file"), - worktree_head => N__( -"y - discard this hunk from worktree -n - do not discard this hunk from worktree -q - quit; do not discard this hunk or any of the remaining ones -a - discard this hunk and all later hunks in the file -d - do not discard this hunk or any of the later hunks in the file"), - worktree_nothead => N__( -"y - apply this hunk to worktree -n - do not apply this hunk to worktree -q - quit; do not apply this hunk or any of the remaining ones -a - apply this hunk and all later hunks in the file -d - do not apply this hunk or any of the later hunks in the file"), -); - -sub help_patch_cmd { - local $_; - my $other = $_[0] . ",?"; - print colored $help_color, __($help_patch_modes{$patch_mode}), "\n", - map { "$_\n" } grep { - my $c = quotemeta(substr($_, 0, 1)); - $other =~ /,$c/ - } split "\n", __ <<EOF ; -g - select a hunk to go to -/ - search for a hunk matching the given regex -j - leave this hunk undecided, see next undecided hunk -J - leave this hunk undecided, see next hunk -k - leave this hunk undecided, see previous undecided hunk -K - leave this hunk undecided, see previous hunk -s - split the current hunk into smaller hunks -e - manually edit the current hunk -? - print help -EOF -} - -sub apply_patch { - my $cmd = shift; - my $ret = run_git_apply $cmd, @_; - if (!$ret) { - print STDERR @_; - } - return $ret; -} - -sub apply_patch_for_checkout_commit { - my $reverse = shift; - my $applies_index = run_git_apply 'apply '.$reverse.' --cached --check', @_; - my $applies_worktree = run_git_apply 'apply '.$reverse.' --check', @_; - - if ($applies_worktree && $applies_index) { - run_git_apply 'apply '.$reverse.' --cached', @_; - run_git_apply 'apply '.$reverse, @_; - return 1; - } elsif (!$applies_index) { - print colored $error_color, __("The selected hunks do not apply to the index!\n"); - if (prompt_yesno __("Apply them to the worktree anyway? ")) { - return run_git_apply 'apply '.$reverse, @_; - } else { - print colored $error_color, __("Nothing was applied.\n"); - return 0; - } - } else { - print STDERR @_; - return 0; - } -} - -sub patch_update_cmd { - my @all_mods = list_modified($patch_mode_flavour{FILTER}); - error_msg sprintf(__("ignoring unmerged: %s\n"), $_->{VALUE}) - for grep { $_->{UNMERGED} } @all_mods; - @all_mods = grep { !$_->{UNMERGED} } @all_mods; - - my @mods = grep { !($_->{BINARY}) } @all_mods; - my @them; - - if (!@mods) { - if (@all_mods) { - print STDERR __("Only binary files changed.\n"); - } else { - print STDERR __("No changes.\n"); - } - return 0; - } - if ($patch_mode_only) { - @them = @mods; - } - else { - @them = list_and_choose({ PROMPT => __('Patch update'), - HEADER => $status_head, }, - @mods); - } - for (@them) { - return 0 if patch_update_file($_->{VALUE}); - } -} - -# Generate a one line summary of a hunk. -sub summarize_hunk { - my $rhunk = shift; - my $summary = $rhunk->{TEXT}[0]; - - # Keep the line numbers, discard extra context. - $summary =~ s/@@(.*?)@@.*/$1 /s; - $summary .= " " x (20 - length $summary); - - # Add some user context. - for my $line (@{$rhunk->{TEXT}}) { - if ($line =~ m/^[+-].*\w/) { - $summary .= $line; - last; - } - } - - chomp $summary; - return substr($summary, 0, 80) . "\n"; -} - - -# Print a one-line summary of each hunk in the array ref in -# the first argument, starting with the index in the 2nd. -sub display_hunks { - my ($hunks, $i) = @_; - my $ctr = 0; - $i ||= 0; - for (; $i < @$hunks && $ctr < 20; $i++, $ctr++) { - my $status = " "; - if (defined $hunks->[$i]{USE}) { - $status = $hunks->[$i]{USE} ? "+" : "-"; - } - printf "%s%2d: %s", - $status, - $i + 1, - summarize_hunk($hunks->[$i]); - } - return $i; -} - -my %patch_update_prompt_modes = ( - stage => { - mode => N__("Stage mode change [y,n,q,a,d%s,?]? "), - deletion => N__("Stage deletion [y,n,q,a,d%s,?]? "), - addition => N__("Stage addition [y,n,q,a,d%s,?]? "), - hunk => N__("Stage this hunk [y,n,q,a,d%s,?]? "), - }, - stash => { - mode => N__("Stash mode change [y,n,q,a,d%s,?]? "), - deletion => N__("Stash deletion [y,n,q,a,d%s,?]? "), - addition => N__("Stash addition [y,n,q,a,d%s,?]? "), - hunk => N__("Stash this hunk [y,n,q,a,d%s,?]? "), - }, - reset_head => { - mode => N__("Unstage mode change [y,n,q,a,d%s,?]? "), - deletion => N__("Unstage deletion [y,n,q,a,d%s,?]? "), - addition => N__("Unstage addition [y,n,q,a,d%s,?]? "), - hunk => N__("Unstage this hunk [y,n,q,a,d%s,?]? "), - }, - reset_nothead => { - mode => N__("Apply mode change to index [y,n,q,a,d%s,?]? "), - deletion => N__("Apply deletion to index [y,n,q,a,d%s,?]? "), - addition => N__("Apply addition to index [y,n,q,a,d%s,?]? "), - hunk => N__("Apply this hunk to index [y,n,q,a,d%s,?]? "), - }, - checkout_index => { - mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "), - deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "), - addition => N__("Discard addition from worktree [y,n,q,a,d%s,?]? "), - hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "), - }, - checkout_head => { - mode => N__("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "), - deletion => N__("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "), - addition => N__("Discard addition from index and worktree [y,n,q,a,d%s,?]? "), - hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "), - }, - checkout_nothead => { - mode => N__("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "), - deletion => N__("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "), - addition => N__("Apply addition to index and worktree [y,n,q,a,d%s,?]? "), - hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "), - }, - worktree_head => { - mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "), - deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "), - addition => N__("Discard addition from worktree [y,n,q,a,d%s,?]? "), - hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "), - }, - worktree_nothead => { - mode => N__("Apply mode change to worktree [y,n,q,a,d%s,?]? "), - deletion => N__("Apply deletion to worktree [y,n,q,a,d%s,?]? "), - addition => N__("Apply addition to worktree [y,n,q,a,d%s,?]? "), - hunk => N__("Apply this hunk to worktree [y,n,q,a,d%s,?]? "), - }, -); - -sub patch_update_file { - my $quit = 0; - my ($ix, $num); - my $path = shift; - my ($head, @hunk) = parse_diff($path); - ($head, my $mode, my $deletion, my $addition) = parse_diff_header($head); - for (@{$head->{DISPLAY}}) { - print; - } - - if (@{$mode->{TEXT}}) { - unshift @hunk, $mode; - } - if (@{$deletion->{TEXT}}) { - foreach my $hunk (@hunk) { - push @{$deletion->{TEXT}}, @{$hunk->{TEXT}}; - push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}}; - } - @hunk = ($deletion); - } - - $num = scalar @hunk; - $ix = 0; - - while (1) { - my ($prev, $next, $other, $undecided, $i); - $other = ''; - - last if ($ix and !$num); - if ($num <= $ix) { - $ix = 0; - } - for ($i = 0; $i < $ix; $i++) { - if (!defined $hunk[$i]{USE}) { - $prev = 1; - $other .= ',k'; - last; - } - } - if ($ix) { - $other .= ',K'; - } - for ($i = $ix + 1; $i < $num; $i++) { - if (!defined $hunk[$i]{USE}) { - $next = 1; - $other .= ',j'; - last; - } - } - if ($ix < $num - 1) { - $other .= ',J'; - } - if ($num > 1) { - $other .= ',g,/'; - } - for ($i = 0; $i < $num; $i++) { - if (!defined $hunk[$i]{USE}) { - $undecided = 1; - last; - } - } - last if (!$undecided && ($num || !$addition)); - - if ($num) { - if ($hunk[$ix]{TYPE} eq 'hunk' && - hunk_splittable($hunk[$ix]{TEXT})) { - $other .= ',s'; - } - if ($hunk[$ix]{TYPE} eq 'hunk') { - $other .= ',e'; - } - for (@{$hunk[$ix]{DISPLAY}}) { - print; - } - } - my $type = $num ? $hunk[$ix]{TYPE} : $head->{TYPE}; - print colored $prompt_color, "(", ($ix+1), "/", ($num ? $num : 1), ") ", - sprintf(__($patch_update_prompt_modes{$patch_mode}{$type}), $other); - - my $line = prompt_single_character; - last unless defined $line; - if ($line) { - if ($line =~ /^y/i) { - if ($num) { - $hunk[$ix]{USE} = 1; - } else { - $head->{USE} = 1; - } - } - elsif ($line =~ /^n/i) { - if ($num) { - $hunk[$ix]{USE} = 0; - } else { - $head->{USE} = 0; - } - } - elsif ($line =~ /^a/i) { - if ($num) { - while ($ix < $num) { - if (!defined $hunk[$ix]{USE}) { - $hunk[$ix]{USE} = 1; - } - $ix++; - } - } else { - $head->{USE} = 1; - $ix++; - } - next; - } - elsif ($line =~ /^g(.*)/) { - my $response = $1; - unless ($other =~ /g/) { - error_msg __("No other hunks to goto\n"); - next; - } - my $no = $ix > 10 ? $ix - 10 : 0; - while ($response eq '') { - $no = display_hunks(\@hunk, $no); - if ($no < $num) { - print __("go to which hunk (<ret> to see more)? "); - } else { - print __("go to which hunk? "); - } - $response = <STDIN>; - if (!defined $response) { - $response = ''; - } - chomp $response; - } - if ($response !~ /^\s*\d+\s*$/) { - error_msg sprintf(__("Invalid number: '%s'\n"), - $response); - } elsif (0 < $response && $response <= $num) { - $ix = $response - 1; - } else { - error_msg sprintf(__n("Sorry, only %d hunk available.\n", - "Sorry, only %d hunks available.\n", $num), $num); - } - next; - } - elsif ($line =~ /^d/i) { - if ($num) { - while ($ix < $num) { - if (!defined $hunk[$ix]{USE}) { - $hunk[$ix]{USE} = 0; - } - $ix++; - } - } else { - $head->{USE} = 0; - $ix++; - } - next; - } - elsif ($line =~ /^q/i) { - if ($num) { - for ($i = 0; $i < $num; $i++) { - if (!defined $hunk[$i]{USE}) { - $hunk[$i]{USE} = 0; - } - } - } elsif (!defined $head->{USE}) { - $head->{USE} = 0; - } - $quit = 1; - last; - } - elsif ($line =~ m|^/(.*)|) { - my $regex = $1; - unless ($other =~ m|/|) { - error_msg __("No other hunks to search\n"); - next; - } - if ($regex eq "") { - print colored $prompt_color, __("search for regex? "); - $regex = <STDIN>; - if (defined $regex) { - chomp $regex; - } - } - my $search_string; - eval { - $search_string = qr{$regex}m; - }; - if ($@) { - my ($err,$exp) = ($@, $1); - $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//; - error_msg sprintf(__("Malformed search regexp %s: %s\n"), $exp, $err); - next; - } - my $iy = $ix; - while (1) { - my $text = join ("", @{$hunk[$iy]{TEXT}}); - last if ($text =~ $search_string); - $iy++; - $iy = 0 if ($iy >= $num); - if ($ix == $iy) { - error_msg __("No hunk matches the given pattern\n"); - last; - } - } - $ix = $iy; - next; - } - elsif ($line =~ /^K/) { - if ($other =~ /K/) { - $ix--; - } - else { - error_msg __("No previous hunk\n"); - } - next; - } - elsif ($line =~ /^J/) { - if ($other =~ /J/) { - $ix++; - } - else { - error_msg __("No next hunk\n"); - } - next; - } - elsif ($line =~ /^k/) { - if ($other =~ /k/) { - while (1) { - $ix--; - last if (!$ix || - !defined $hunk[$ix]{USE}); - } - } - else { - error_msg __("No previous hunk\n"); - } - next; - } - elsif ($line =~ /^j/) { - if ($other !~ /j/) { - error_msg __("No next hunk\n"); - next; - } - } - elsif ($line =~ /^s/) { - unless ($other =~ /s/) { - error_msg __("Sorry, cannot split this hunk\n"); - next; - } - my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY}); - if (1 < @split) { - print colored $header_color, sprintf( - __n("Split into %d hunk.\n", - "Split into %d hunks.\n", - scalar(@split)), scalar(@split)); - } - splice (@hunk, $ix, 1, @split); - $num = scalar @hunk; - next; - } - elsif ($line =~ /^e/) { - unless ($other =~ /e/) { - error_msg __("Sorry, cannot edit this hunk\n"); - next; - } - my $newhunk = edit_hunk_loop($head, \@hunk, $ix); - if (defined $newhunk) { - splice @hunk, $ix, 1, $newhunk; - } - } - else { - help_patch_cmd($other); - next; - } - # soft increment - while (1) { - $ix++; - last if ($ix >= $num || - !defined $hunk[$ix]{USE}); - } - } - } - - @hunk = coalesce_overlapping_hunks(@hunk) if ($num); - - my $n_lofs = 0; - my @result = (); - for (@hunk) { - if ($_->{USE}) { - push @result, @{$_->{TEXT}}; - } - } - - if (@result or $head->{USE}) { - my @patch = reassemble_patch($head->{TEXT}, @result); - my $apply_routine = $patch_mode_flavour{APPLY}; - &$apply_routine(@patch); - refresh(); - } - - print "\n"; - return $quit; -} - -sub diff_cmd { - my @mods = list_modified('index-only'); - @mods = grep { !($_->{BINARY}) } @mods; - return if (!@mods); - my (@them) = list_and_choose({ PROMPT => __('Review diff'), - IMMEDIATE => 1, - HEADER => $status_head, }, - @mods); - return if (!@them); - my $reference = (is_initial_commit()) ? get_empty_tree() : 'HEAD'; - system(qw(git diff -p --cached), $reference, '--', - map { $_->{VALUE} } @them); -} - -sub quit_cmd { - print __("Bye.\n"); - exit(0); -} - -sub help_cmd { -# TRANSLATORS: please do not translate the command names -# 'status', 'update', 'revert', etc. - print colored $help_color, __ <<'EOF' ; -status - show paths with changes -update - add working tree state to the staged set of changes -revert - revert staged set of changes back to the HEAD version -patch - pick hunks and update selectively -diff - view diff between HEAD and index -add untracked - add contents of untracked files to the staged set of changes -EOF -} - -sub process_args { - return unless @ARGV; - my $arg = shift @ARGV; - if ($arg =~ /--patch(?:=(.*))?/) { - if (defined $1) { - if ($1 eq 'reset') { - $patch_mode = 'reset_head'; - $patch_mode_revision = 'HEAD'; - $arg = shift @ARGV or die __("missing --"); - if ($arg ne '--') { - $patch_mode_revision = $arg; - - # NEEDSWORK: Instead of comparing to the literal "HEAD", - # compare the commit objects instead so that other ways of - # saying the same thing (such as "@") are also handled - # appropriately. - # - # This applies to the cases below too. - $patch_mode = ($arg eq 'HEAD' ? - 'reset_head' : 'reset_nothead'); - $arg = shift @ARGV or die __("missing --"); - } - } elsif ($1 eq 'checkout') { - $arg = shift @ARGV or die __("missing --"); - if ($arg eq '--') { - $patch_mode = 'checkout_index'; - } else { - $patch_mode_revision = $arg; - $patch_mode = ($arg eq 'HEAD' ? - 'checkout_head' : 'checkout_nothead'); - $arg = shift @ARGV or die __("missing --"); - } - } elsif ($1 eq 'worktree') { - $arg = shift @ARGV or die __("missing --"); - if ($arg eq '--') { - $patch_mode = 'checkout_index'; - } else { - $patch_mode_revision = $arg; - $patch_mode = ($arg eq 'HEAD' ? - 'worktree_head' : 'worktree_nothead'); - $arg = shift @ARGV or die __("missing --"); - } - } elsif ($1 eq 'stage' or $1 eq 'stash') { - $patch_mode = $1; - $arg = shift @ARGV or die __("missing --"); - } else { - die sprintf(__("unknown --patch mode: %s"), $1); - } - } else { - $patch_mode = 'stage'; - $arg = shift @ARGV or die __("missing --"); - } - die sprintf(__("invalid argument %s, expecting --"), - $arg) unless $arg eq "--"; - %patch_mode_flavour = %{$patch_modes{$patch_mode}}; - $patch_mode_only = 1; - } - elsif ($arg ne "--") { - die sprintf(__("invalid argument %s, expecting --"), $arg); - } -} - -sub main_loop { - my @cmd = ([ 'status', \&status_cmd, ], - [ 'update', \&update_cmd, ], - [ 'revert', \&revert_cmd, ], - [ 'add untracked', \&add_untracked_cmd, ], - [ 'patch', \&patch_update_cmd, ], - [ 'diff', \&diff_cmd, ], - [ 'quit', \&quit_cmd, ], - [ 'help', \&help_cmd, ], - ); - while (1) { - my ($it) = list_and_choose({ PROMPT => __('What now'), - SINGLETON => 1, - LIST_FLAT => 4, - HEADER => __('*** Commands ***'), - ON_EOF => \&quit_cmd, - IMMEDIATE => 1 }, @cmd); - if ($it) { - eval { - $it->[1]->(); - }; - if ($@) { - print "$@"; - } - } - } -} - -process_args(); -refresh(); -if ($patch_mode_only) { - patch_update_cmd(); -} -else { - status_cmd(); - main_loop(); -} diff --git a/gpg-interface.c b/gpg-interface.c index 687236430b..5cd66d3a78 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -977,9 +977,13 @@ static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature, break; /* found */ } ret |= !cp; + if (ret) { + error(_("gpg failed to sign the data:\n%s"), + gpg_status.len ? gpg_status.buf : "(no gpg output)"); + strbuf_release(&gpg_status); + return -1; + } strbuf_release(&gpg_status); - if (ret) - return error(_("gpg failed to sign the data")); /* Strip CR from the line endings, in case we are on Windows. */ remove_cr_after(signature, bottom); @@ -262,6 +262,31 @@ static void pcre2_free(void *pointer, MAYBE_UNUSED void *memory_data) free(pointer); } +static int pcre2_jit_functional(void) +{ + static int jit_working = -1; + pcre2_code *code; + size_t off; + int err; + + if (jit_working != -1) + return jit_working; + + /* + * Try to JIT compile a simple pattern to probe if the JIT is + * working in general. It might fail for systems where creating + * memory mappings for runtime code generation is restricted. + */ + code = pcre2_compile((PCRE2_SPTR)".", 1, 0, &err, &off, NULL); + if (!code) + return 0; + + jit_working = pcre2_jit_compile(code, PCRE2_JIT_COMPLETE) == 0; + pcre2_code_free(code); + + return jit_working; +} + static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt) { int error; @@ -317,8 +342,29 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt pcre2_config(PCRE2_CONFIG_JIT, &p->pcre2_jit_on); if (p->pcre2_jit_on) { jitret = pcre2_jit_compile(p->pcre2_pattern, PCRE2_JIT_COMPLETE); - if (jitret) - die("Couldn't JIT the PCRE2 pattern '%s', got '%d'\n", p->pattern, jitret); + if (jitret == PCRE2_ERROR_NOMEMORY && !pcre2_jit_functional()) { + /* + * Even though pcre2_config(PCRE2_CONFIG_JIT, ...) + * indicated JIT support, the library might still + * fail to generate JIT code for various reasons, + * e.g. when SELinux's 'deny_execmem' or PaX's + * MPROTECT prevent creating W|X memory mappings. + * + * Instead of faling hard, fall back to interpreter + * mode, just as if the pattern was prefixed with + * '(*NO_JIT)'. + */ + p->pcre2_jit_on = 0; + return; + } else if (jitret) { + int need_clip = p->patternlen > 64; + int clip_len = need_clip ? 64 : p->patternlen; + die("Couldn't JIT the PCRE2 pattern '%.*s'%s, got '%d'%s", + clip_len, p->pattern, need_clip ? "..." : "", jitret, + pcre2_jit_functional() + ? "\nPerhaps prefix (*NO_JIT) to your pattern?" + : ""); + } /* * The pcre2_config(PCRE2_CONFIG_JIT, ...) call just @@ -769,11 +815,11 @@ static void free_pattern_expr(struct grep_expr *x) free(x); } -void free_grep_patterns(struct grep_opt *opt) +static void free_grep_pat(struct grep_pat *pattern) { struct grep_pat *p, *n; - for (p = opt->pattern_list; p; p = n) { + for (p = pattern; p; p = n) { n = p->next; switch (p->token) { case GREP_PATTERN: /* atom */ @@ -790,10 +836,15 @@ void free_grep_patterns(struct grep_opt *opt) } free(p); } +} - if (!opt->pattern_expression) - return; - free_pattern_expr(opt->pattern_expression); +void free_grep_patterns(struct grep_opt *opt) +{ + free_grep_pat(opt->pattern_list); + free_grep_pat(opt->header_list); + + if (opt->pattern_expression) + free_pattern_expr(opt->pattern_expression); } static const char *end_of_line(const char *cp, unsigned long *left) @@ -55,6 +55,11 @@ static int pick_next_hook(struct child_process *cp, cp->no_stdin = 1; strvec_pushv(&cp->env, hook_cb->options->env.v); + /* reopen the file for stdin; run_command closes it. */ + if (hook_cb->options->path_to_stdin) { + cp->no_stdin = 0; + cp->in = xopen(hook_cb->options->path_to_stdin, O_RDONLY); + } cp->stdout_to_stderr = 1; cp->trace2_hook_name = hook_cb->hook_name; cp->dir = hook_cb->options->dir; @@ -30,6 +30,11 @@ struct run_hooks_opt * was invoked. */ int *invoked_hook; + + /** + * Path to file which should be piped to stdin for each hook. + */ + const char *path_to_stdin; }; #define RUN_HOOKS_OPT_INIT { \ diff --git a/http-backend.c b/http-backend.c index 6eb3b2fe51..8ab58e55f8 100644 --- a/http-backend.c +++ b/http-backend.c @@ -759,10 +759,14 @@ int cmd_main(int argc, const char **argv) struct service_cmd *c = &services[i]; regex_t re; regmatch_t out[1]; + int ret; if (regcomp(&re, c->pattern, REG_EXTENDED)) die("Bogus regex in service table: %s", c->pattern); - if (!regexec(&re, dir, 1, out, 0)) { + ret = regexec(&re, dir, 1, out, 0); + regfree(&re); + + if (!ret) { size_t n; if (strcmp(method, c->method)) @@ -774,7 +778,6 @@ int cmd_main(int argc, const char **argv) dir[out[0].rm_so] = 0; break; } - regfree(&re); } if (!cmd) @@ -786,6 +789,7 @@ int cmd_main(int argc, const char **argv) if (!getenv("GIT_HTTP_EXPORT_ALL") && access("git-daemon-export-ok", F_OK) ) not_found(&hdr, "Repository not exported: '%s'", dir); + free(dir); http_config(); max_request_buffer = git_env_ulong("GIT_HTTP_MAX_REQUEST_BUFFER", @@ -795,5 +799,6 @@ int cmd_main(int argc, const char **argv) setenv(GIT_PROTOCOL_ENVIRONMENT, proto_header, 0); cmd->imp(&hdr, cmd_arg); + free(cmd_arg); return 0; } diff --git a/pathspec.c b/pathspec.c index dbcfe7b321..ab70fcbe61 100644 --- a/pathspec.c +++ b/pathspec.c @@ -545,7 +545,7 @@ static void NORETURN unsupported_magic(const char *pattern, } /* * We may want to substitute "this command" with a command - * name. E.g. when add--interactive dies when running + * name. E.g. when "git add -p" or "git add -i" dies when running * "checkout -p" */ die(_("%s: pathspec magic not supported by this command: %s"), diff --git a/range-diff.c b/range-diff.c index 8255ab4349..086365dffb 100644 --- a/range-diff.c +++ b/range-diff.c @@ -383,11 +383,14 @@ static void output_pair_header(struct diff_options *diffopt, const char *color_new = diff_get_color_opt(diffopt, DIFF_FILE_NEW); const char *color_commit = diff_get_color_opt(diffopt, DIFF_COMMIT); const char *color; + char abbrev = diffopt->abbrev; + + if (abbrev < 0) + abbrev = DEFAULT_ABBREV; if (!dashes->len) strbuf_addchars(dashes, '-', - strlen(find_unique_abbrev(oid, - DEFAULT_ABBREV))); + strlen(find_unique_abbrev(oid, abbrev))); if (!b_util) { color = color_old; @@ -409,7 +412,7 @@ static void output_pair_header(struct diff_options *diffopt, strbuf_addf(buf, "%*s: %s ", patch_no_width, "-", dashes->buf); else strbuf_addf(buf, "%*d: %s ", patch_no_width, a_util->i + 1, - find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV)); + find_unique_abbrev(&a_util->oid, abbrev)); if (status == '!') strbuf_addf(buf, "%s%s", color_reset, color); @@ -421,7 +424,7 @@ static void output_pair_header(struct diff_options *diffopt, strbuf_addf(buf, " %*s: %s", patch_no_width, "-", dashes->buf); else strbuf_addf(buf, " %*d: %s", patch_no_width, b_util->i + 1, - find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV)); + find_unique_abbrev(&b_util->oid, abbrev)); commit = lookup_commit_reference(the_repository, oid); if (commit) { diff --git a/read-cache.c b/read-cache.c index 7bd12afb38..35e5657877 100644 --- a/read-cache.c +++ b/read-cache.c @@ -488,11 +488,11 @@ int ie_modified(struct index_state *istate, return 0; } -int base_name_compare(const char *name1, int len1, int mode1, - const char *name2, int len2, int mode2) +int base_name_compare(const char *name1, size_t len1, int mode1, + const char *name2, size_t len2, int mode2) { unsigned char c1, c2; - int len = len1 < len2 ? len1 : len2; + size_t len = len1 < len2 ? len1 : len2; int cmp; cmp = memcmp(name1, name2, len); @@ -517,11 +517,12 @@ int base_name_compare(const char *name1, int len1, int mode1, * This is used by routines that want to traverse the git namespace * but then handle conflicting entries together when possible. */ -int df_name_compare(const char *name1, int len1, int mode1, - const char *name2, int len2, int mode2) +int df_name_compare(const char *name1, size_t len1, int mode1, + const char *name2, size_t len2, int mode2) { - int len = len1 < len2 ? len1 : len2, cmp; unsigned char c1, c2; + size_t len = len1 < len2 ? len1 : len2; + int cmp; cmp = memcmp(name1, name2, len); if (cmp) diff --git a/run-command.c b/run-command.c index 50cc011654..6bd16acb06 100644 --- a/run-command.c +++ b/run-command.c @@ -1586,6 +1586,14 @@ static int pp_start_one(struct parallel_processes *pp, if (i == opts->processes) BUG("bookkeeping is hard"); + /* + * By default, do not inherit stdin from the parent process - otherwise, + * all children would share stdin! Users may overwrite this to provide + * something to the child's stdin by having their 'get_next_task' + * callback assign 0 to .no_stdin and an appropriate integer to .in. + */ + pp->children[i].process.no_stdin = 1; + code = opts->get_next_task(&pp->children[i].process, opts->ungroup ? NULL : &pp->children[i].err, opts->data, @@ -1601,7 +1609,6 @@ static int pp_start_one(struct parallel_processes *pp, pp->children[i].process.err = -1; pp->children[i].process.stdout_to_stderr = 1; } - pp->children[i].process.no_stdin = 1; if (start_command(&pp->children[i].process)) { if (opts->start_failure) @@ -1632,9 +1639,7 @@ static void pp_buffer_stderr(struct parallel_processes *pp, const struct run_process_parallel_opts *opts, int output_timeout) { - int i; - - while ((i = poll(pp->pfd, opts->processes, output_timeout) < 0)) { + while (poll(pp->pfd, opts->processes, output_timeout) < 0) { if (errno == EINTR) continue; pp_cleanup(pp, opts); diff --git a/sequencer.c b/sequencer.c index 3e4a197289..a7e6db4f78 100644 --- a/sequencer.c +++ b/sequencer.c @@ -351,10 +351,25 @@ static const char *gpg_sign_opt_quoted(struct replay_opts *opts) return buf.buf; } +void replay_opts_release(struct replay_opts *opts) +{ + free(opts->gpg_sign); + free(opts->reflog_action); + free(opts->default_strategy); + free(opts->strategy); + for (size_t i = 0; i < opts->xopts_nr; i++) + free(opts->xopts[i]); + free(opts->xopts); + strbuf_release(&opts->current_fixups); + if (opts->revs) + release_revisions(opts->revs); + free(opts->revs); +} + int sequencer_remove_state(struct replay_opts *opts) { struct strbuf buf = STRBUF_INIT; - int i, ret = 0; + int ret = 0; if (is_rebase_i(opts) && strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) { @@ -373,15 +388,6 @@ int sequencer_remove_state(struct replay_opts *opts) } } - free(opts->gpg_sign); - free(opts->reflog_action); - free(opts->default_strategy); - free(opts->strategy); - for (i = 0; i < opts->xopts_nr; i++) - free(opts->xopts[i]); - free(opts->xopts); - strbuf_release(&opts->current_fixups); - strbuf_reset(&buf); strbuf_addstr(&buf, get_dir(opts)); if (remove_dir_recursively(&buf, 0)) @@ -2271,8 +2277,10 @@ static int do_pick_commit(struct repository *r, reword = 1; else if (is_fixup(command)) { if (update_squash_messages(r, command, commit, - opts, item->flags)) - return -1; + opts, item->flags)) { + res = -1; + goto leave; + } flags |= AMEND_MSG; if (!final_fixup) msg_file = rebase_path_squash_msg(); @@ -2282,9 +2290,11 @@ static int do_pick_commit(struct repository *r, } else { const char *dest = git_path_squash_msg(r); unlink(dest); - if (copy_file(dest, rebase_path_squash_msg(), 0666)) - return error(_("could not rename '%s' to '%s'"), - rebase_path_squash_msg(), dest); + if (copy_file(dest, rebase_path_squash_msg(), 0666)) { + res = error(_("could not rename '%s' to '%s'"), + rebase_path_squash_msg(), dest); + goto leave; + } unlink(git_path_merge_msg(r)); msg_file = dest; flags |= EDIT_MSG; @@ -2322,7 +2332,6 @@ static int do_pick_commit(struct repository *r, free_commit_list(common); free_commit_list(remotes); } - strbuf_release(&msgbuf); /* * If the merge was clean or if it failed due to conflict, we write @@ -2396,6 +2405,7 @@ fast_forward_edit: leave: free_message(commit, &msg); free(author); + strbuf_release(&msgbuf); update_abort_safety_file(); return res; @@ -2477,6 +2487,34 @@ static int is_command(enum todo_command command, const char **bol) (*bol = p)); } +static int check_label_or_ref_arg(enum todo_command command, const char *arg) +{ + switch (command) { + case TODO_LABEL: + /* + * '#' is not a valid label as the merge command uses it to + * separate merge parents from the commit subject. + */ + if (!strcmp(arg, "#") || + check_refname_format(arg, REFNAME_ALLOW_ONELEVEL)) + return error(_("'%s' is not a valid label"), arg); + break; + + case TODO_UPDATE_REF: + if (check_refname_format(arg, REFNAME_ALLOW_ONELEVEL)) + return error(_("'%s' is not a valid refname"), arg); + if (check_refname_format(arg, 0)) + return error(_("update-ref requires a fully qualified " + "refname e.g. refs/heads/%s"), arg); + break; + + default: + BUG("unexpected todo_command"); + } + + return 0; +} + static int parse_insn_line(struct repository *r, struct todo_item *item, const char *buf, const char *bol, char *eol) { @@ -2525,10 +2563,19 @@ static int parse_insn_line(struct repository *r, struct todo_item *item, if (item->command == TODO_EXEC || item->command == TODO_LABEL || item->command == TODO_RESET || item->command == TODO_UPDATE_REF) { + int ret = 0; + item->commit = NULL; item->arg_offset = bol - buf; item->arg_len = (int)(eol - bol); - return 0; + if (item->command == TODO_LABEL || + item->command == TODO_UPDATE_REF) { + saved = *eol; + *eol = '\0'; + ret = check_label_or_ref_arg(item->command, bol); + *eol = saved; + } + return ret; } if (item->command == TODO_FIXUP) { @@ -4834,8 +4881,7 @@ cleanup_head_ref: if (!stat(rebase_path_rewritten_list(), &st) && st.st_size > 0) { struct child_process child = CHILD_PROCESS_INIT; - const char *post_rewrite_hook = - find_hook("post-rewrite"); + struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT; child.in = open(rebase_path_rewritten_list(), O_RDONLY); child.git_cmd = 1; @@ -4845,18 +4891,9 @@ cleanup_head_ref: /* we don't care if this copying failed */ run_command(&child); - if (post_rewrite_hook) { - struct child_process hook = CHILD_PROCESS_INIT; - - hook.in = open(rebase_path_rewritten_list(), - O_RDONLY); - hook.stdout_to_stderr = 1; - hook.trace2_hook_name = "post-rewrite"; - strvec_push(&hook.args, post_rewrite_hook); - strvec_push(&hook.args, "rebase"); - /* we don't care if this hook failed */ - run_command(&hook); - } + hook_opt.path_to_stdin = rebase_path_rewritten_list(); + strvec_push(&hook_opt.args, "rebase"); + run_hooks_opt("post-rewrite", &hook_opt); } apply_autostash(rebase_path_autostash()); diff --git a/sequencer.h b/sequencer.h index 888c18aad7..3bcdfa1b58 100644 --- a/sequencer.h +++ b/sequencer.h @@ -158,6 +158,7 @@ int sequencer_pick_revisions(struct repository *repo, int sequencer_continue(struct repository *repo, struct replay_opts *opts); int sequencer_rollback(struct repository *repo, struct replay_opts *opts); int sequencer_skip(struct repository *repo, struct replay_opts *opts); +void replay_opts_release(struct replay_opts *opts); int sequencer_remove_state(struct replay_opts *opts); #define TODO_LIST_KEEP_EMPTY (1U << 0) @@ -449,10 +449,6 @@ the --sparse command-line argument. GIT_TEST_PRELOAD_INDEX=<boolean> exercises the preload-index code path by overriding the minimum number of cache entries required per thread. -GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when false, disables the -built-in version of git add -i. See 'add.interactive.useBuiltin' in -git-config(1). - GIT_TEST_INDEX_THREADS=<n> enables exercising the multi-threaded loading of the index for the whole test suite by bypassing the default number of cache entries and thread minimums. Setting this to 1 will make the diff --git a/t/helper/test-ctype.c b/t/helper/test-ctype.c index 92c4c2313e..b21bd672d9 100644 --- a/t/helper/test-ctype.c +++ b/t/helper/test-ctype.c @@ -11,9 +11,14 @@ static void report_error(const char *class, int ch) static int is_in(const char *s, int ch) { - /* We can't find NUL using strchr. It's classless anyway. */ + /* + * We can't find NUL using strchr. Accept it as the first + * character in the spec -- there are no empty classes. + */ if (ch == '\0') - return 0; + return ch == *s; + if (*s == '\0') + s++; return !!strchr(s, ch); } @@ -28,6 +33,20 @@ static int is_in(const char *s, int ch) #define DIGIT "0123456789" #define LOWER "abcdefghijklmnopqrstuvwxyz" #define UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +#define PUNCT "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" +#define ASCII \ + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" \ + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" \ + "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f" \ + "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f" \ + "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f" \ + "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f" \ + "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f" \ + "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f" +#define CNTRL \ + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" \ + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" \ + "\x7f" int cmd__ctype(int argc, const char **argv) { @@ -38,6 +57,13 @@ int cmd__ctype(int argc, const char **argv) TEST_CLASS(is_glob_special, "*?[\\"); TEST_CLASS(is_regex_special, "$()*+.?[\\^{|"); TEST_CLASS(is_pathspec_magic, "!\"#%&',-/:;<=>@_`~"); + TEST_CLASS(isascii, ASCII); + TEST_CLASS(islower, LOWER); + TEST_CLASS(isupper, UPPER); + TEST_CLASS(iscntrl, CNTRL); + TEST_CLASS(ispunct, PUNCT); + TEST_CLASS(isxdigit, DIGIT "abcdefABCDEF"); + TEST_CLASS(isprint, LOWER UPPER DIGIT PUNCT " "); return rc; } diff --git a/t/helper/test-dir-iterator.c b/t/helper/test-dir-iterator.c index 659b6bfa81..6b297bd753 100644 --- a/t/helper/test-dir-iterator.c +++ b/t/helper/test-dir-iterator.c @@ -15,7 +15,7 @@ static const char *error_name(int error_number) /* * usage: - * tool-test dir-iterator [--follow-symlinks] [--pedantic] directory_path + * tool-test dir-iterator [--pedantic] directory_path */ int cmd__dir_iterator(int argc, const char **argv) { @@ -24,9 +24,7 @@ int cmd__dir_iterator(int argc, const char **argv) int iter_status; for (++argv, --argc; *argv && starts_with(*argv, "--"); ++argv, --argc) { - if (strcmp(*argv, "--follow-symlinks") == 0) - flags |= DIR_ITERATOR_FOLLOW_SYMLINKS; - else if (strcmp(*argv, "--pedantic") == 0) + if (strcmp(*argv, "--pedantic") == 0) flags |= DIR_ITERATOR_PEDANTIC; else die("invalid option '%s'", *argv); diff --git a/t/helper/test-genzeros.c b/t/helper/test-genzeros.c index 8ca988d621..47af843b68 100644 --- a/t/helper/test-genzeros.c +++ b/t/helper/test-genzeros.c @@ -17,15 +17,16 @@ int cmd__genzeros(int argc, const char **argv) /* Writing out individual NUL bytes is slow... */ while (count < 0) - if (write(1, zeros, ARRAY_SIZE(zeros)) < 0) - return -1; + if (xwrite(1, zeros, ARRAY_SIZE(zeros)) < 0) + die_errno("write error"); while (count > 0) { - n = write(1, zeros, count < ARRAY_SIZE(zeros) ? - count : ARRAY_SIZE(zeros)); + n = xwrite(1, zeros, + count < ARRAY_SIZE(zeros) + ? count : ARRAY_SIZE(zeros)); if (n < 0) - return -1; + die_errno("write error"); count -= n; } diff --git a/t/lib-diff-alternative.sh b/t/lib-diff-alternative.sh index 8d1e408bb5..a8f5d3274a 100644 --- a/t/lib-diff-alternative.sh +++ b/t/lib-diff-alternative.sh @@ -105,10 +105,46 @@ index $file1..$file2 100644 } EOF + cat >expect_diffstat <<EOF + file1 => file2 | 21 ++++++++++----------- + 1 file changed, 10 insertions(+), 11 deletions(-) +EOF + STRATEGY=$1 + test_expect_success "$STRATEGY diff from attributes" ' + echo "file* diff=driver" >.gitattributes && + git config diff.driver.algorithm "$STRATEGY" && + test_must_fail git diff --no-index file1 file2 > output && + cat expect && + cat output && + test_cmp expect output + ' + + test_expect_success "$STRATEGY diff from attributes has valid diffstat" ' + echo "file* diff=driver" >.gitattributes && + git config diff.driver.algorithm "$STRATEGY" && + test_must_fail git diff --stat --no-index file1 file2 > output && + test_cmp expect_diffstat output + ' + test_expect_success "$STRATEGY diff" ' - test_must_fail git diff --no-index "--$STRATEGY" file1 file2 > output && + test_must_fail git diff --no-index "--diff-algorithm=$STRATEGY" file1 file2 > output && + test_cmp expect output + ' + + test_expect_success "$STRATEGY diff command line precedence before attributes" ' + echo "file* diff=driver" >.gitattributes && + git config diff.driver.algorithm myers && + test_must_fail git diff --no-index "--diff-algorithm=$STRATEGY" file1 file2 > output && + test_cmp expect output + ' + + test_expect_success "$STRATEGY diff attributes precedence before config" ' + git config diff.algorithm default && + echo "file* diff=driver" >.gitattributes && + git config diff.driver.algorithm "$STRATEGY" && + test_must_fail git diff --no-index file1 file2 > output && test_cmp expect output ' diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh index 5d2d56c445..059fd74adb 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh @@ -25,6 +25,7 @@ # LIB_HTTPD_DAV enable DAV # LIB_HTTPD_SVN enable SVN at given location (e.g. "svn") # LIB_HTTPD_SSL enable SSL +# LIB_HTTPD_PROXY enable proxy # # Copyright (c) 2008 Clemens Buchacher <drizzd@aon.at> # @@ -133,6 +134,7 @@ install_script () { prepare_httpd() { mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH" cp "$TEST_PATH"/passwd "$HTTPD_ROOT_PATH" + cp "$TEST_PATH"/proxy-passwd "$HTTPD_ROOT_PATH" install_script incomplete-length-upload-pack-v2-http.sh install_script incomplete-body-upload-pack-v2-http.sh install_script error-no-report.sh @@ -176,6 +178,11 @@ prepare_httpd() { export LIB_HTTPD_SVN LIB_HTTPD_SVNPATH fi fi + + if test -n "$LIB_HTTPD_PROXY" + then + HTTPD_PARA="$HTTPD_PARA -DPROXY" + fi } enable_http2 () { diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 51a4fbcf62..e31293a45f 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -47,6 +47,22 @@ Protocols h2c LoadModule authz_host_module modules/mod_authz_host.so </IfModule> +<IfDefine PROXY> +<IfModule !mod_proxy.c> + LoadModule proxy_module modules/mod_proxy.so +</IfModule> +<IfModule !mod_proxy_http.c> + LoadModule proxy_http_module modules/mod_proxy_http.so +</IfModule> +ProxyRequests On +<Proxy "*"> + AuthType Basic + AuthName "proxy-auth" + AuthUserFile proxy-passwd + Require valid-user +</Proxy> +</IfDefine> + <IfModule !mod_authn_core.c> LoadModule authn_core_module modules/mod_authn_core.so </IfModule> diff --git a/t/lib-httpd/proxy-passwd b/t/lib-httpd/proxy-passwd new file mode 100644 index 0000000000..77c25138e0 --- /dev/null +++ b/t/lib-httpd/proxy-passwd @@ -0,0 +1 @@ +proxuser:2x7tAukjAED5M diff --git a/t/t0023-crlf-am.sh b/t/t0023-crlf-am.sh index f9bbb91f64..575805513a 100755 --- a/t/t0023-crlf-am.sh +++ b/t/t0023-crlf-am.sh @@ -2,6 +2,7 @@ test_description='Test am with auto.crlf' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh cat >patchfile <<\EOF diff --git a/t/t0066-dir-iterator.sh b/t/t0066-dir-iterator.sh index 63a1a45cd3..7d0a0da8c0 100755 --- a/t/t0066-dir-iterator.sh +++ b/t/t0066-dir-iterator.sh @@ -106,11 +106,7 @@ test_expect_success SYMLINKS 'setup dirs with symlinks' ' ln -s d dir4/a/e && ln -s ../b dir4/a/f && - mkdir -p dir5/a/b && - mkdir -p dir5/a/c && - ln -s ../c dir5/a/b/d && - ln -s ../ dir5/a/b/e && - ln -s ../../ dir5/a/b/f + ln -s dir4 dir5 ' test_expect_success SYMLINKS 'dir-iterator should not follow symlinks by default' ' @@ -129,21 +125,10 @@ test_expect_success SYMLINKS 'dir-iterator should not follow symlinks by default test_cmp expected-no-follow-sorted-output actual-no-follow-sorted-output ' -test_expect_success SYMLINKS 'dir-iterator should follow symlinks w/ follow flag' ' - cat >expected-follow-sorted-output <<-EOF && - [d] (a) [a] ./dir4/a - [d] (a/f) [f] ./dir4/a/f - [d] (a/f/c) [c] ./dir4/a/f/c - [d] (b) [b] ./dir4/b - [d] (b/c) [c] ./dir4/b/c - [f] (a/d) [d] ./dir4/a/d - [f] (a/e) [e] ./dir4/a/e - EOF - - test-tool dir-iterator --follow-symlinks ./dir4 >out && - sort out >actual-follow-sorted-output && +test_expect_success SYMLINKS 'dir-iterator does not resolve top-level symlinks' ' + test_must_fail test-tool dir-iterator ./dir5 >out && - test_cmp expected-follow-sorted-output actual-follow-sorted-output + grep "ENOTDIR" out ' test_done diff --git a/t/t1301-shared-repo.sh b/t/t1301-shared-repo.sh index 58d6da7feb..1b6437ec07 100755 --- a/t/t1301-shared-repo.sh +++ b/t/t1301-shared-repo.sh @@ -9,6 +9,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME TEST_CREATE_REPO_NO_TEMPLATE=1 +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Remove a default ACL from the test dir if possible. diff --git a/t/t1302-repo-version.sh b/t/t1302-repo-version.sh index 7cf80bf66a..70389fa2eb 100755 --- a/t/t1302-repo-version.sh +++ b/t/t1302-repo-version.sh @@ -5,6 +5,7 @@ test_description='Test repository version check' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t1304-default-acl.sh b/t/t1304-default-acl.sh index c69ae41306..31b89dd969 100755 --- a/t/t1304-default-acl.sh +++ b/t/t1304-default-acl.sh @@ -9,6 +9,7 @@ test_description='Test repository with default ACL' # => this must come before . ./test-lib.sh umask 077 +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # We need an arbitrary other user give permission to using ACLs. root diff --git a/t/t1408-packed-refs.sh b/t/t1408-packed-refs.sh index 41ba1f1d7f..9469c79a58 100755 --- a/t/t1408-packed-refs.sh +++ b/t/t1408-packed-refs.sh @@ -5,6 +5,7 @@ test_description='packed-refs entries are covered by loose refs' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh index aa59954f6c..6c45965b1e 100755 --- a/t/t1410-reflog.sh +++ b/t/t1410-reflog.sh @@ -7,6 +7,7 @@ test_description='Test prune and reflog expiration' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh check_have () { diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh index 27731722a5..b32ca798f9 100755 --- a/t/t1416-ref-transaction-hooks.sh +++ b/t/t1416-ref-transaction-hooks.sh @@ -5,6 +5,7 @@ test_description='reference transaction hooks' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t1451-fsck-buffer.sh b/t/t1451-fsck-buffer.sh index 9ac270abab..3413da40e4 100755 --- a/t/t1451-fsck-buffer.sh +++ b/t/t1451-fsck-buffer.sh @@ -14,6 +14,8 @@ so. These tests _might_ catch such overruns in normal use, but should be run with ASan or valgrind for more confidence. ' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # the general idea for tags and commits is to build up the "base" file diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh index 2ef3579fa7..3506f627b6 100755 --- a/t/t1800-hook.sh +++ b/t/t1800-hook.sh @@ -177,4 +177,22 @@ test_expect_success 'git hook run a hook with a bad shebang' ' test_cmp expect actual ' +test_expect_success 'stdin to hooks' ' + write_script .git/hooks/test-hook <<-\EOF && + echo BEGIN stdin + cat + echo END stdin + EOF + + cat >expect <<-EOF && + BEGIN stdin + hello + END stdin + EOF + + echo hello >input && + git hook run --to-stdin=input test-hook 2>actual && + test_cmp expect actual +' + test_done diff --git a/t/t2015-checkout-unborn.sh b/t/t2015-checkout-unborn.sh index 9425aae639..fb0e13881c 100755 --- a/t/t2015-checkout-unborn.sh +++ b/t/t2015-checkout-unborn.sh @@ -9,11 +9,12 @@ TEST_PASSES_SANITIZE_LEAK=true test_expect_success 'setup' ' mkdir parent && - (cd parent && - git init && - echo content >file && - git add file && - git commit -m base + ( + cd parent && + git init && + echo content >file && + git add file && + git commit -m base ) && git fetch parent main:origin ' diff --git a/t/t2016-checkout-patch.sh b/t/t2016-checkout-patch.sh index a5822e41af..747eb5563e 100755 --- a/t/t2016-checkout-patch.sh +++ b/t/t2016-checkout-patch.sh @@ -4,12 +4,6 @@ test_description='git checkout --patch' . ./lib-patch-mode.sh -if ! test_have_prereq ADD_I_USE_BUILTIN && ! test_have_prereq PERL -then - skip_all='skipping interactive add tests, PERL not set' - test_done -fi - test_expect_success 'setup' ' mkdir dir && echo parent > dir/foo && diff --git a/t/t2401-worktree-prune.sh b/t/t2401-worktree-prune.sh index 3d28c7f06b..568a47ec42 100755 --- a/t/t2401-worktree-prune.sh +++ b/t/t2401-worktree-prune.sh @@ -5,6 +5,7 @@ test_description='prune $GIT_DIR/worktrees' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success initialize ' diff --git a/t/t2402-worktree-list.sh b/t/t2402-worktree-list.sh index 79e0fce2d9..9ad9be0c20 100755 --- a/t/t2402-worktree-list.sh +++ b/t/t2402-worktree-list.sh @@ -5,6 +5,7 @@ test_description='test git worktree list' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t2406-worktree-repair.sh b/t/t2406-worktree-repair.sh index 5c44453e1c..8970780efc 100755 --- a/t/t2406-worktree-repair.sh +++ b/t/t2406-worktree-repair.sh @@ -2,6 +2,7 @@ test_description='test git worktree repair' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh index 84dd0cd26d..b5f4d6a653 100755 --- a/t/t3206-range-diff.sh +++ b/t/t3206-range-diff.sh @@ -33,6 +33,26 @@ test_expect_success 'setup' ' u3 sha256:736c4bc u4 sha256:673e77d + # topic (abbrev=10) + t1_abbrev sha1:4de457d2c0 + t2_abbrev sha1:fccce22f8c + t3_abbrev sha1:147e64ef53 + t4_abbrev sha1:a63e992599 + t1_abbrev sha256:b89f8b9092 + t2_abbrev sha256:5f12aadf34 + t3_abbrev sha256:ea8b273a6c + t4_abbrev sha256:14b73361fc + + # unmodified (abbrev=10) + u1_abbrev sha1:35b9b25f76 + u2_abbrev sha1:de345ab3de + u3_abbrev sha1:9af6654000 + u4_abbrev sha1:2901f773f3 + u1_abbrev sha256:e3731be242 + u2_abbrev sha256:14fadf8cee + u3_abbrev sha256:736c4bcb44 + u4_abbrev sha256:673e77d589 + # reordered r1 sha1:aca177a r2 sha1:14ad629 @@ -153,6 +173,18 @@ test_expect_success 'simple A B C (unmodified)' ' test_cmp expect actual ' +test_expect_success 'simple A..B A..C (unmodified) with --abbrev' ' + git range-diff --no-color --abbrev=10 main..topic main..unmodified \ + >actual && + cat >expect <<-EOF && + 1: $(test_oid t1_abbrev) = 1: $(test_oid u1_abbrev) s/5/A/ + 2: $(test_oid t2_abbrev) = 2: $(test_oid u2_abbrev) s/4/A/ + 3: $(test_oid t3_abbrev) = 3: $(test_oid u3_abbrev) s/11/B/ + 4: $(test_oid t4_abbrev) = 4: $(test_oid u4_abbrev) s/12/B/ + EOF + test_cmp expect actual +' + test_expect_success 'A^! and A^-<n> (unmodified)' ' git range-diff --no-color topic^! unmodified^-1 >actual && cat >expect <<-EOF && diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh index 577f32dc71..07a0ff93de 100755 --- a/t/t3210-pack-refs.sh +++ b/t/t3210-pack-refs.sh @@ -12,6 +12,7 @@ semantic is still the same. GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'enable reflogs' ' diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 462cefd25d..efeb74ad50 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -2072,6 +2072,7 @@ test_expect_success '--update-refs: --edit-todo with no update-ref lines' ' ' test_expect_success '--update-refs: check failed ref update' ' + test_when_finished "test_might_fail git rebase --abort" && git checkout -B update-refs-error no-conflict-branch && git branch -f base HEAD~4 && git branch -f first HEAD~3 && @@ -2123,6 +2124,28 @@ test_expect_success '--update-refs: check failed ref update' ' test_cmp expect err.trimmed ' +test_expect_success 'bad labels and refs rejected when parsing todo list' ' + test_when_finished "test_might_fail git rebase --abort" && + cat >todo <<-\EOF && + exec >execed + label # + label :invalid + update-ref :bad + update-ref topic + EOF + rm -f execed && + ( + set_replace_editor todo && + test_must_fail git rebase -i HEAD 2>err + ) && + grep "'\''#'\'' is not a valid label" err && + grep "'\'':invalid'\'' is not a valid label" err && + grep "'\'':bad'\'' is not a valid refname" err && + grep "update-ref requires a fully qualified refname e.g. refs/heads/topic" \ + err && + test_path_is_missing execed +' + # This must be the last test in this file test_expect_success '$EDITOR and friends are unchanged' ' test_editor_unchanged diff --git a/t/t3405-rebase-malformed.sh b/t/t3405-rebase-malformed.sh index 2524331861..8979bc3407 100755 --- a/t/t3405-rebase-malformed.sh +++ b/t/t3405-rebase-malformed.sh @@ -5,6 +5,7 @@ test_description='rebase should handle arbitrary git message' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-rebase.sh diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh index 58371d8a54..e75b3d0e07 100755 --- a/t/t3412-rebase-root.sh +++ b/t/t3412-rebase-root.sh @@ -7,6 +7,7 @@ Tests if git rebase --root --onto <newparent> can rebase the root commit. GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh log_with_names () { diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh index ea501f2b42..f8c4ed78c9 100755 --- a/t/t3416-rebase-onto-threedots.sh +++ b/t/t3416-rebase-onto-threedots.sh @@ -5,6 +5,7 @@ test_description='git rebase --onto A...B' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY/lib-rebase.sh" diff --git a/t/t3419-rebase-patch-id.sh b/t/t3419-rebase-patch-id.sh index 7181f176b8..6c61f240cf 100755 --- a/t/t3419-rebase-patch-id.sh +++ b/t/t3419-rebase-patch-id.sh @@ -5,6 +5,7 @@ test_description='git rebase - test patch id computation' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh scramble () { diff --git a/t/t3423-rebase-reword.sh b/t/t3423-rebase-reword.sh index 4859bb8f72..2fab703d61 100755 --- a/t/t3423-rebase-reword.sh +++ b/t/t3423-rebase-reword.sh @@ -2,6 +2,7 @@ test_description='git rebase interactive with rewording' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-rebase.sh diff --git a/t/t3425-rebase-topology-merges.sh b/t/t3425-rebase-topology-merges.sh index 63acc1ea4d..a16428bdf5 100755 --- a/t/t3425-rebase-topology-merges.sh +++ b/t/t3425-rebase-topology-merges.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='rebase topology tests with merges' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-rebase.sh diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh index 70e8136356..4bfc779bb8 100755 --- a/t/t3431-rebase-fork-point.sh +++ b/t/t3431-rebase-fork-point.sh @@ -8,6 +8,7 @@ test_description='git rebase --fork-point test' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # A---B---D---E (main) diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index 5086e14c02..7f1a5dd3de 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -8,6 +8,7 @@ test_description='ensure rebase fast-forwards commits when possible' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t3437-rebase-fixup-options.sh b/t/t3437-rebase-fixup-options.sh index c023fefd68..274699dadb 100755 --- a/t/t3437-rebase-fixup-options.sh +++ b/t/t3437-rebase-fixup-options.sh @@ -14,6 +14,7 @@ to the "fixup" command that works with "fixup!", "fixup -C" works with "amend!" upon --autosquash. ' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-rebase.sh diff --git a/t/t3438-rebase-broken-files.sh b/t/t3438-rebase-broken-files.sh index b92a3ce46b..c614c4f2e4 100755 --- a/t/t3438-rebase-broken-files.sh +++ b/t/t3438-rebase-broken-files.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='rebase behavior when on-disk files are broken' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'set up conflicting branches' ' diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh index 1f4cfc3744..2f3e3e2416 100755 --- a/t/t3501-revert-cherry-pick.sh +++ b/t/t3501-revert-cherry-pick.sh @@ -13,6 +13,7 @@ test_description='test cherry-pick and revert with renames GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t3502-cherry-pick-merge.sh b/t/t3502-cherry-pick-merge.sh index 5495eacfec..1b2c0d6aca 100755 --- a/t/t3502-cherry-pick-merge.sh +++ b/t/t3502-cherry-pick-merge.sh @@ -11,6 +11,7 @@ test_description='cherry picking and reverting a merge GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t3503-cherry-pick-root.sh b/t/t3503-cherry-pick-root.sh index 95fe4feaee..76d393dc8a 100755 --- a/t/t3503-cherry-pick-root.sh +++ b/t/t3503-cherry-pick-root.sh @@ -5,6 +5,7 @@ test_description='test cherry-picking (and reverting) a root commit' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t3506-cherry-pick-ff.sh b/t/t3506-cherry-pick-ff.sh index 7e11bd4a4c..b71bad17b8 100755 --- a/t/t3506-cherry-pick-ff.sh +++ b/t/t3506-cherry-pick-ff.sh @@ -5,6 +5,7 @@ test_description='test cherry-picking with --ff option' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t3511-cherry-pick-x.sh b/t/t3511-cherry-pick-x.sh index 84a587daf3..dd5d92ef30 100755 --- a/t/t3511-cherry-pick-x.sh +++ b/t/t3511-cherry-pick-x.sh @@ -2,6 +2,7 @@ test_description='Test cherry-pick -x and -s' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh pristine_detach () { diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 5841f280fb..3a99837d9b 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -7,7 +7,7 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh . "$TEST_DIRECTORY"/lib-terminal.sh -if test_have_prereq !ADD_I_USE_BUILTIN,!PERL +if test_have_prereq !PERL then skip_all='skipping add -i (scripted) tests, perl not available' test_done @@ -46,6 +46,21 @@ force_color () { ) } +test_expect_success 'warn about add.interactive.useBuiltin' ' + cat >expect <<-\EOF && + warning: the add.interactive.useBuiltin setting has been removed! + See its entry in '\''git help config'\'' for details. + No changes. + EOF + + for v in = =true =false + do + git -c "add.interactive.useBuiltin$v" add -p >out 2>actual && + test_must_be_empty out && + test_cmp expect actual || return 1 + done +' + test_expect_success 'setup (initial)' ' echo content >file && git add file && @@ -547,15 +562,7 @@ test_expect_success 'split hunk "add -p (edit)"' ' ! grep "^+15" actual ' -test_expect_success 'setup ADD_I_USE_BUILTIN check' ' - result=success && - if ! test_have_prereq ADD_I_USE_BUILTIN - then - result=failure - fi -' - -test_expect_$result 'split hunk "add -p (no, yes, edit)"' ' +test_expect_success 'split hunk "add -p (no, yes, edit)"' ' test_write_lines 5 10 20 21 30 31 40 50 60 >test && git reset && # test sequence is s(plit), n(o), y(es), e(dit) @@ -579,7 +586,7 @@ test_expect_success 'split hunk with incomplete line at end' ' test_must_fail git grep --cached before ' -test_expect_$result 'edit, adding lines to the first hunk' ' +test_expect_success 'edit, adding lines to the first hunk' ' test_write_lines 10 11 20 30 40 50 51 60 >test && git reset && tr _ " " >patch <<-EOF && diff --git a/t/t3800-mktag.sh b/t/t3800-mktag.sh index e3cf0ffbe5..d3e428ff46 100755 --- a/t/t3800-mktag.sh +++ b/t/t3800-mktag.sh @@ -4,6 +4,7 @@ test_description='git mktag: tag object verify test' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh ########################################################### diff --git a/t/t4018/java-class-brace-on-separate-line b/t/t4018/java-class-brace-on-separate-line new file mode 100644 index 0000000000..8795acd4cf --- /dev/null +++ b/t/t4018/java-class-brace-on-separate-line @@ -0,0 +1,6 @@ +class RIGHT +{ + static int ONE; + static int TWO; + static int ChangeMe; +} diff --git a/t/t4018/java-class-space-before-type-parameters b/t/t4018/java-class-space-before-type-parameters new file mode 100644 index 0000000000..0bdef1dfbe --- /dev/null +++ b/t/t4018/java-class-space-before-type-parameters @@ -0,0 +1,6 @@ +class RIGHT <TYPE, PARAMS, AFTER, SPACE> { + static int ONE; + static int TWO; + static int THREE; + private A ChangeMe; +} diff --git a/t/t4018/java-class-type-parameters b/t/t4018/java-class-type-parameters new file mode 100644 index 0000000000..579aa7af21 --- /dev/null +++ b/t/t4018/java-class-type-parameters @@ -0,0 +1,6 @@ +class RIGHT<A, B> { + static int ONE; + static int TWO; + static int THREE; + private A ChangeMe; +} diff --git a/t/t4018/java-class-type-parameters-implements b/t/t4018/java-class-type-parameters-implements new file mode 100644 index 0000000000..b8038b1866 --- /dev/null +++ b/t/t4018/java-class-type-parameters-implements @@ -0,0 +1,6 @@ +class RIGHT<A, B> implements List<A> { + static int ONE; + static int TWO; + static int THREE; + private A ChangeMe; +} diff --git a/t/t4018/java-interface-type-parameters b/t/t4018/java-interface-type-parameters new file mode 100644 index 0000000000..a4baa1ae68 --- /dev/null +++ b/t/t4018/java-interface-type-parameters @@ -0,0 +1,6 @@ +interface RIGHT<A, B> { + static int ONE; + static int TWO; + static int THREE; + public B foo(A ChangeMe); +} diff --git a/t/t4018/java-interface-type-parameters-extends b/t/t4018/java-interface-type-parameters-extends new file mode 100644 index 0000000000..31d7fb3244 --- /dev/null +++ b/t/t4018/java-interface-type-parameters-extends @@ -0,0 +1,6 @@ +interface RIGHT<A, B> extends Function<A, B> { + static int ONE; + static int TWO; + static int THREE; + public B foo(A ChangeMe); +} diff --git a/t/t4018/java-non-sealed b/t/t4018/java-non-sealed new file mode 100644 index 0000000000..069087c1c6 --- /dev/null +++ b/t/t4018/java-non-sealed @@ -0,0 +1,8 @@ +public abstract sealed class SealedClass { + public static non-sealed class RIGHT extends SealedClass { + static int ONE; + static int TWO; + static int THREE; + private int ChangeMe; + } +} diff --git a/t/t4018/java-record b/t/t4018/java-record new file mode 100644 index 0000000000..97aa819dd8 --- /dev/null +++ b/t/t4018/java-record @@ -0,0 +1,6 @@ +public record RIGHT(int comp1, double comp2, String comp3) { + static int ONE; + static int TWO; + static int THREE; + static int ChangeMe; +} diff --git a/t/t4018/java-record-space-before-components b/t/t4018/java-record-space-before-components new file mode 100644 index 0000000000..9827f22583 --- /dev/null +++ b/t/t4018/java-record-space-before-components @@ -0,0 +1,6 @@ +public record RIGHT (String components, String after, String space) { + static int ONE; + static int TWO; + static int THREE; + static int ChangeMe; +} diff --git a/t/t4018/java-record-type-parameters b/t/t4018/java-record-type-parameters new file mode 100644 index 0000000000..f62a035cc8 --- /dev/null +++ b/t/t4018/java-record-type-parameters @@ -0,0 +1,6 @@ +public record RIGHT<A, N extends Number>(A comp1, N comp2, int comp3) { + static int ONE; + static int TWO; + static int THREE; + static int ChangeMe; +} diff --git a/t/t4018/java-sealed b/t/t4018/java-sealed new file mode 100644 index 0000000000..785fbc62bc --- /dev/null +++ b/t/t4018/java-sealed @@ -0,0 +1,7 @@ +public abstract sealed class Sealed { // RIGHT + static int ONE; + static int TWO; + static int THREE; + public final class ChangeMe extends Sealed { + } +} diff --git a/t/t4018/java-sealed-permits b/t/t4018/java-sealed-permits new file mode 100644 index 0000000000..18dd4894cf --- /dev/null +++ b/t/t4018/java-sealed-permits @@ -0,0 +1,6 @@ +public abstract sealed class RIGHT permits PermittedA, PermittedB { + static int ONE; + static int TWO; + static int THREE; + private int ChangeMe; +} diff --git a/t/t4018/java-sealed-type-parameters b/t/t4018/java-sealed-type-parameters new file mode 100644 index 0000000000..e6530c47c3 --- /dev/null +++ b/t/t4018/java-sealed-type-parameters @@ -0,0 +1,6 @@ +public abstract sealed class RIGHT<A, B> { + static int ONE; + static int TWO; + static int THREE; + private int ChangeMe; +} diff --git a/t/t4018/java-sealed-type-parameters-implements-permits b/t/t4018/java-sealed-type-parameters-implements-permits new file mode 100644 index 0000000000..bd6e6d3582 --- /dev/null +++ b/t/t4018/java-sealed-type-parameters-implements-permits @@ -0,0 +1,6 @@ +public abstract sealed class RIGHT<A, B> implements List<A> permits PermittedA, PermittedB { + static int ONE; + static int TWO; + static int THREE; + private int ChangeMe; +} diff --git a/t/t4018/java-sealed-type-parameters-permits b/t/t4018/java-sealed-type-parameters-permits new file mode 100644 index 0000000000..25a0da6442 --- /dev/null +++ b/t/t4018/java-sealed-type-parameters-permits @@ -0,0 +1,6 @@ +public abstract sealed class RIGHT<A, B> permits PermittedA, PermittedB { + static int ONE; + static int TWO; + static int THREE; + private int ChangeMe; +} diff --git a/t/t4115-apply-symlink.sh b/t/t4115-apply-symlink.sh index d0f3edef54..65ac7df2d7 100755 --- a/t/t4115-apply-symlink.sh +++ b/t/t4115-apply-symlink.sh @@ -45,4 +45,85 @@ test_expect_success 'apply --index symlink patch' ' ' +test_expect_success 'symlink setup' ' + ln -s .git symlink && + git add symlink && + git commit -m "add symlink" +' + +test_expect_success SYMLINKS 'symlink escape when creating new files' ' + test_when_finished "git reset --hard && git clean -dfx" && + + cat >patch <<-EOF && + diff --git a/symlink b/renamed-symlink + similarity index 100% + rename from symlink + rename to renamed-symlink + -- + diff --git /dev/null b/renamed-symlink/create-me + new file mode 100644 + index 0000000..039727e + --- /dev/null + +++ b/renamed-symlink/create-me + @@ -0,0 +1,1 @@ + +busted + EOF + + test_must_fail git apply patch 2>stderr && + cat >expected_stderr <<-EOF && + error: affected file ${SQ}renamed-symlink/create-me${SQ} is beyond a symbolic link + EOF + test_cmp expected_stderr stderr && + ! test_path_exists .git/create-me +' + +test_expect_success SYMLINKS 'symlink escape when modifying file' ' + test_when_finished "git reset --hard && git clean -dfx" && + touch .git/modify-me && + + cat >patch <<-EOF && + diff --git a/symlink b/renamed-symlink + similarity index 100% + rename from symlink + rename to renamed-symlink + -- + diff --git a/renamed-symlink/modify-me b/renamed-symlink/modify-me + index 1111111..2222222 100644 + --- a/renamed-symlink/modify-me + +++ b/renamed-symlink/modify-me + @@ -0,0 +1,1 @@ + +busted + EOF + + test_must_fail git apply patch 2>stderr && + cat >expected_stderr <<-EOF && + error: renamed-symlink/modify-me: No such file or directory + EOF + test_cmp expected_stderr stderr && + test_must_be_empty .git/modify-me +' + +test_expect_success SYMLINKS 'symlink escape when deleting file' ' + test_when_finished "git reset --hard && git clean -dfx && rm .git/delete-me" && + touch .git/delete-me && + + cat >patch <<-EOF && + diff --git a/symlink b/renamed-symlink + similarity index 100% + rename from symlink + rename to renamed-symlink + -- + diff --git a/renamed-symlink/delete-me b/renamed-symlink/delete-me + deleted file mode 100644 + index 1111111..0000000 100644 + EOF + + test_must_fail git apply patch 2>stderr && + cat >expected_stderr <<-EOF && + error: renamed-symlink/delete-me: No such file or directory + EOF + test_cmp expected_stderr stderr && + test_path_is_file .git/delete-me +' + test_done diff --git a/t/t4152-am-subjects.sh b/t/t4152-am-subjects.sh index 4c68245aca..9f2edba1f8 100755 --- a/t/t4152-am-subjects.sh +++ b/t/t4152-am-subjects.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='test subject preservation with format-patch | am' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh make_patches() { diff --git a/t/t4254-am-corrupt.sh b/t/t4254-am-corrupt.sh index 54be7da161..45f1d4f95e 100755 --- a/t/t4254-am-corrupt.sh +++ b/t/t4254-am-corrupt.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='git am with corrupt input' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh make_mbox_with_nul () { diff --git a/t/t4256-am-format-flowed.sh b/t/t4256-am-format-flowed.sh index 2369c4e17a..1015273bc8 100755 --- a/t/t4256-am-format-flowed.sh +++ b/t/t4256-am-format-flowed.sh @@ -2,6 +2,7 @@ test_description='test format=flowed support of git am' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t4257-am-interactive.sh b/t/t4257-am-interactive.sh index aed8f4de3d..f26d7fd2db 100755 --- a/t/t4257-am-interactive.sh +++ b/t/t4257-am-interactive.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='am --interactive tests' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'set up patches to apply' ' diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index d473048138..918a2fc7c6 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -105,6 +105,18 @@ check_added() { ' } +check_mtime() { + dir=$1 + path_in_archive=$2 + mtime=$3 + + test_expect_success " validate mtime of $path_in_archive" ' + test-tool chmtime --get $dir/$path_in_archive >actual.mtime && + echo $mtime >expect.mtime && + test_cmp expect.mtime actual.mtime + ' +} + test_expect_success 'setup' ' test_oid_cache <<-EOF obj sha1:19f9c8273ec45a8938e6999cb59b3ff66739902a @@ -174,6 +186,13 @@ test_expect_success 'git archive' ' check_tar b +test_expect_success 'git archive --mtime' ' + git archive --mtime=2002-02-02T02:02:02-0200 HEAD >with_mtime.tar +' + +check_tar with_mtime +check_mtime with_mtime a/a 1012622522 + test_expect_success 'git archive --prefix=prefix/' ' git archive --prefix=prefix/ HEAD >with_prefix.tar ' @@ -402,11 +421,11 @@ test_expect_success GZIP 'extract tgz file (external gzip)' ' test_expect_success 'archive and :(glob)' ' git archive -v HEAD -- ":(glob)**/sh" >/dev/null 2>actual && - cat >expect <<EOF && -a/ -a/bin/ -a/bin/sh -EOF + cat >expect <<-\EOF && + a/ + a/bin/ + a/bin/sh + EOF test_cmp expect actual ' diff --git a/t/t5001-archive-attr.sh b/t/t5001-archive-attr.sh index 2f6eef5e37..04d300eeda 100755 --- a/t/t5001-archive-attr.sh +++ b/t/t5001-archive-attr.sh @@ -3,6 +3,7 @@ test_description='git archive attribute tests' TEST_CREATE_REPO_NO_TEMPLATE=1 +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh SUBSTFORMAT='%H (%h)%n' diff --git a/t/t5004-archive-corner-cases.sh b/t/t5004-archive-corner-cases.sh index ae508e2162..9f2c6da80e 100755 --- a/t/t5004-archive-corner-cases.sh +++ b/t/t5004-archive-corner-cases.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='test corner cases of git-archive' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # the 10knuls.tar file is used to test for an empty git generated tar diff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh index 59e9e77223..f89809be53 100755 --- a/t/t5302-pack-index.sh +++ b/t/t5302-pack-index.sh @@ -4,6 +4,8 @@ # test_description='pack index with 64-bit offsets and object CRC' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t5306-pack-nobase.sh b/t/t5306-pack-nobase.sh index 51973f4a51..846c5ca7d3 100755 --- a/t/t5306-pack-nobase.sh +++ b/t/t5306-pack-nobase.sh @@ -6,6 +6,8 @@ test_description='git-pack-object with missing base ' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Create A-B chain diff --git a/t/t5312-prune-corruption.sh b/t/t5312-prune-corruption.sh index 9d8e249ae8..230cb38712 100755 --- a/t/t5312-prune-corruption.sh +++ b/t/t5312-prune-corruption.sh @@ -14,6 +14,7 @@ what currently happens. If that changes, these tests should be revisited. GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'disable reflogs' ' diff --git a/t/t5317-pack-objects-filter-objects.sh b/t/t5317-pack-objects-filter-objects.sh index 5b707d911b..b26d476c64 100755 --- a/t/t5317-pack-objects-filter-objects.sh +++ b/t/t5317-pack-objects-filter-objects.sh @@ -5,6 +5,7 @@ test_description='git pack-objects using object filtering' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Test blob:none filter. diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh index b5f9b10922..499d5d4c78 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh @@ -1015,4 +1015,20 @@ test_expect_success 'complains when run outside of a repository' ' grep "not a git repository" err ' +test_expect_success 'repack with delta islands' ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + test_commit first && + git repack && + test_commit second && + git repack && + + git multi-pack-index write && + git -c repack.useDeltaIslands=true multi-pack-index repack + ) +' + test_done diff --git a/t/t5330-no-lazy-fetch-with-commit-graph.sh b/t/t5330-no-lazy-fetch-with-commit-graph.sh index 2cc7fd7a47..5eb28f0512 100755 --- a/t/t5330-no-lazy-fetch-with-commit-graph.sh +++ b/t/t5330-no-lazy-fetch-with-commit-graph.sh @@ -2,6 +2,7 @@ test_description='test for no lazy fetch with the commit-graph' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup: prepare a repository with a commit' ' diff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh index 978f240cda..cfaae54739 100755 --- a/t/t5403-post-checkout-hook.sh +++ b/t/t5403-post-checkout-hook.sh @@ -7,6 +7,7 @@ test_description='Test the post-checkout hook.' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t5405-send-pack-rewind.sh b/t/t5405-send-pack-rewind.sh index 11f03239a0..1686ac13aa 100755 --- a/t/t5405-send-pack-rewind.sh +++ b/t/t5405-send-pack-rewind.sh @@ -5,6 +5,7 @@ test_description='forced push to replace commit we do not have' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t5406-remote-rejects.sh b/t/t5406-remote-rejects.sh index dcbeb42082..d6a9946633 100755 --- a/t/t5406-remote-rejects.sh +++ b/t/t5406-remote-rejects.sh @@ -2,6 +2,7 @@ test_description='remote push rejects are reported by client' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t5502-quickfetch.sh b/t/t5502-quickfetch.sh index b160f8b7fb..7b3ff21b98 100755 --- a/t/t5502-quickfetch.sh +++ b/t/t5502-quickfetch.sh @@ -5,6 +5,7 @@ test_description='test quickfetch from local' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh index 88d3c56750..0b8ab4afdb 100755 --- a/t/t5504-fetch-receive-strict.sh +++ b/t/t5504-fetch-receive-strict.sh @@ -4,6 +4,7 @@ test_description='fetch/receive strict mode' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup and inject "corrupt or missing" object' ' diff --git a/t/t5507-remote-environment.sh b/t/t5507-remote-environment.sh index e6149295b1..c6a6957c50 100755 --- a/t/t5507-remote-environment.sh +++ b/t/t5507-remote-environment.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='check environment showed to remote side of transports' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'set up "remote" push situation' ' diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index c0b745e33b..34a1261520 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -806,6 +806,14 @@ test_expect_success 'fetch.writeCommitGraph with submodules' ' ) ' +# fetches from first configured url +test_expect_success 'fetch from multiple configured URLs in single remote' ' + git init url1 && + git remote add multipleurls url1 && + git remote set-url --add multipleurls url2 && + git fetch multipleurls +' + # configured prune tests set_config_tristate () { diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh index 511ba3bd45..54f422ced3 100755 --- a/t/t5514-fetch-multiple.sh +++ b/t/t5514-fetch-multiple.sh @@ -197,4 +197,9 @@ test_expect_success 'parallel' ' test_i18ngrep "could not fetch .two.*128" err ' +test_expect_success 'git fetch --multiple --jobs=0 picks a default' ' + (cd test && + git fetch --multiple --jobs=0) +' + test_done diff --git a/t/t5522-pull-symlink.sh b/t/t5522-pull-symlink.sh index bcff460d0a..394bc60cb8 100755 --- a/t/t5522-pull-symlink.sh +++ b/t/t5522-pull-symlink.sh @@ -2,6 +2,7 @@ test_description='pulling from symlinked subdir' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # The scenario we are building: diff --git a/t/t5523-push-upstream.sh b/t/t5523-push-upstream.sh index fdb4292056..c9acc07635 100755 --- a/t/t5523-push-upstream.sh +++ b/t/t5523-push-upstream.sh @@ -4,6 +4,7 @@ test_description='push with --set-upstream' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-terminal.sh diff --git a/t/t5527-fetch-odd-refs.sh b/t/t5527-fetch-odd-refs.sh index e2770e4541..98ece27c6a 100755 --- a/t/t5527-fetch-odd-refs.sh +++ b/t/t5527-fetch-odd-refs.sh @@ -4,6 +4,7 @@ test_description='test fetching of oddly-named refs' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # afterwards we will have: diff --git a/t/t5529-push-errors.sh b/t/t5529-push-errors.sh index ce85fd30ad..0247137cb3 100755 --- a/t/t5529-push-errors.sh +++ b/t/t5529-push-errors.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='detect some push errors early (before contacting remote)' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup commits' ' diff --git a/t/t5546-receive-limits.sh b/t/t5546-receive-limits.sh index 0b0e987fdb..eed3c9d81a 100755 --- a/t/t5546-receive-limits.sh +++ b/t/t5546-receive-limits.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='check receive input limits' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Let's run tests with different unpack limits: 1 and 10000 diff --git a/t/t5547-push-quarantine.sh b/t/t5547-push-quarantine.sh index 1876fb34e5..9f899b8c7d 100755 --- a/t/t5547-push-quarantine.sh +++ b/t/t5547-push-quarantine.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='check quarantine of objects during push' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'create picky dest repo' ' diff --git a/t/t5558-clone-bundle-uri.sh b/t/t5558-clone-bundle-uri.sh index 9155f31fa2..afd56926c5 100755 --- a/t/t5558-clone-bundle-uri.sh +++ b/t/t5558-clone-bundle-uri.sh @@ -285,6 +285,8 @@ test_expect_success 'clone HTTP bundle' ' ' test_expect_success 'clone bundle list (HTTP, no heuristic)' ' + test_when_finished rm -f trace*.txt && + cp clone-from/bundle-*.bundle "$HTTPD_DOCUMENT_ROOT_PATH/" && cat >"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF && [bundle] @@ -304,12 +306,26 @@ test_expect_success 'clone bundle list (HTTP, no heuristic)' ' uri = $HTTPD_URL/bundle-4.bundle EOF - git clone --bundle-uri="$HTTPD_URL/bundle-list" \ + GIT_TRACE2_EVENT="$(pwd)/trace-clone.txt" \ + git clone --bundle-uri="$HTTPD_URL/bundle-list" \ clone-from clone-list-http 2>err && ! grep "Repository lacks these prerequisite commits" err && git -C clone-from for-each-ref --format="%(objectname)" >oids && - git -C clone-list-http cat-file --batch-check <oids + git -C clone-list-http cat-file --batch-check <oids && + + cat >expect <<-EOF && + $HTTPD_URL/bundle-1.bundle + $HTTPD_URL/bundle-2.bundle + $HTTPD_URL/bundle-3.bundle + $HTTPD_URL/bundle-4.bundle + $HTTPD_URL/bundle-list + EOF + + # Sort the list, since the order is not well-defined + # without a heuristic. + test_remote_https_urls <trace-clone.txt | sort >actual && + test_cmp expect actual ' test_expect_success 'clone bundle list (HTTP, any mode)' ' @@ -350,6 +366,658 @@ test_expect_success 'clone bundle list (HTTP, any mode)' ' test_cmp expect actual ' +test_expect_success 'clone bundle list (http, creationToken)' ' + test_when_finished rm -f trace*.txt && + + cp clone-from/bundle-*.bundle "$HTTPD_DOCUMENT_ROOT_PATH/" && + cat >"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF && + [bundle] + version = 1 + mode = all + heuristic = creationToken + + [bundle "bundle-1"] + uri = bundle-1.bundle + creationToken = 1 + + [bundle "bundle-2"] + uri = bundle-2.bundle + creationToken = 2 + + [bundle "bundle-3"] + uri = bundle-3.bundle + creationToken = 3 + + [bundle "bundle-4"] + uri = bundle-4.bundle + creationToken = 4 + EOF + + GIT_TRACE2_EVENT="$(pwd)/trace-clone.txt" git \ + clone --bundle-uri="$HTTPD_URL/bundle-list" \ + "$HTTPD_URL/smart/fetch.git" clone-list-http-2 && + + git -C clone-from for-each-ref --format="%(objectname)" >oids && + git -C clone-list-http-2 cat-file --batch-check <oids && + + cat >expect <<-EOF && + $HTTPD_URL/bundle-list + $HTTPD_URL/bundle-4.bundle + $HTTPD_URL/bundle-3.bundle + $HTTPD_URL/bundle-2.bundle + $HTTPD_URL/bundle-1.bundle + EOF + + test_remote_https_urls <trace-clone.txt >actual && + test_cmp expect actual +' + +test_expect_success 'clone incomplete bundle list (http, creationToken)' ' + test_when_finished rm -f trace*.txt && + + cp clone-from/bundle-*.bundle "$HTTPD_DOCUMENT_ROOT_PATH/" && + cat >"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF && + [bundle] + version = 1 + mode = all + heuristic = creationToken + + [bundle "bundle-1"] + uri = bundle-1.bundle + creationToken = 1 + EOF + + GIT_TRACE2_EVENT=$(pwd)/trace-clone.txt \ + git clone --bundle-uri="$HTTPD_URL/bundle-list" \ + --single-branch --branch=base --no-tags \ + "$HTTPD_URL/smart/fetch.git" clone-token-http && + + test_cmp_config -C clone-token-http "$HTTPD_URL/bundle-list" fetch.bundleuri && + test_cmp_config -C clone-token-http 1 fetch.bundlecreationtoken && + + cat >expect <<-EOF && + $HTTPD_URL/bundle-list + $HTTPD_URL/bundle-1.bundle + EOF + + test_remote_https_urls <trace-clone.txt >actual && + test_cmp expect actual && + + # We now have only one bundle ref. + git -C clone-token-http for-each-ref --format="%(refname)" "refs/bundles/*" >refs && + cat >expect <<-\EOF && + refs/bundles/base + EOF + test_cmp expect refs && + + # Add remaining bundles, exercising the "deepening" strategy + # for downloading via the creationToken heurisitc. + cat >>"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF && + [bundle "bundle-2"] + uri = bundle-2.bundle + creationToken = 2 + + [bundle "bundle-3"] + uri = bundle-3.bundle + creationToken = 3 + + [bundle "bundle-4"] + uri = bundle-4.bundle + creationToken = 4 + EOF + + GIT_TRACE2_EVENT="$(pwd)/trace1.txt" \ + git -C clone-token-http fetch origin --no-tags \ + refs/heads/merge:refs/heads/merge && + test_cmp_config -C clone-token-http 4 fetch.bundlecreationtoken && + + cat >expect <<-EOF && + $HTTPD_URL/bundle-list + $HTTPD_URL/bundle-4.bundle + $HTTPD_URL/bundle-3.bundle + $HTTPD_URL/bundle-2.bundle + EOF + + test_remote_https_urls <trace1.txt >actual && + test_cmp expect actual && + + # We now have all bundle refs. + git -C clone-token-http for-each-ref --format="%(refname)" "refs/bundles/*" >refs && + + cat >expect <<-\EOF && + refs/bundles/base + refs/bundles/left + refs/bundles/merge + refs/bundles/right + EOF + test_cmp expect refs +' + +test_expect_success 'http clone with bundle.heuristic creates fetch.bundleURI' ' + test_when_finished rm -rf fetch-http-4 trace*.txt && + + cat >"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF && + [bundle] + version = 1 + mode = all + heuristic = creationToken + + [bundle "bundle-1"] + uri = bundle-1.bundle + creationToken = 1 + EOF + + GIT_TRACE2_EVENT="$(pwd)/trace-clone.txt" \ + git clone --single-branch --branch=base \ + --bundle-uri="$HTTPD_URL/bundle-list" \ + "$HTTPD_URL/smart/fetch.git" fetch-http-4 && + + test_cmp_config -C fetch-http-4 "$HTTPD_URL/bundle-list" fetch.bundleuri && + test_cmp_config -C fetch-http-4 1 fetch.bundlecreationtoken && + + cat >expect <<-EOF && + $HTTPD_URL/bundle-list + $HTTPD_URL/bundle-1.bundle + EOF + + test_remote_https_urls <trace-clone.txt >actual && + test_cmp expect actual && + + # only received base ref from bundle-1 + git -C fetch-http-4 for-each-ref --format="%(refname)" "refs/bundles/*" >refs && + cat >expect <<-\EOF && + refs/bundles/base + EOF + test_cmp expect refs && + + cat >>"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF && + [bundle "bundle-2"] + uri = bundle-2.bundle + creationToken = 2 + EOF + + # Fetch the objects for bundle-2 _and_ bundle-3. + GIT_TRACE2_EVENT="$(pwd)/trace1.txt" \ + git -C fetch-http-4 fetch origin --no-tags \ + refs/heads/left:refs/heads/left \ + refs/heads/right:refs/heads/right && + test_cmp_config -C fetch-http-4 2 fetch.bundlecreationtoken && + + cat >expect <<-EOF && + $HTTPD_URL/bundle-list + $HTTPD_URL/bundle-2.bundle + EOF + + test_remote_https_urls <trace1.txt >actual && + test_cmp expect actual && + + # received left from bundle-2 + git -C fetch-http-4 for-each-ref --format="%(refname)" "refs/bundles/*" >refs && + cat >expect <<-\EOF && + refs/bundles/base + refs/bundles/left + EOF + test_cmp expect refs && + + # No-op fetch + GIT_TRACE2_EVENT="$(pwd)/trace1b.txt" \ + git -C fetch-http-4 fetch origin --no-tags \ + refs/heads/left:refs/heads/left \ + refs/heads/right:refs/heads/right && + + cat >expect <<-EOF && + $HTTPD_URL/bundle-list + EOF + test_remote_https_urls <trace1b.txt >actual && + test_cmp expect actual && + + cat >>"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF && + [bundle "bundle-3"] + uri = bundle-3.bundle + creationToken = 3 + + [bundle "bundle-4"] + uri = bundle-4.bundle + creationToken = 4 + EOF + + # This fetch should skip bundle-3.bundle, since its objects are + # already local (we have the requisite commits for bundle-4.bundle). + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \ + git -C fetch-http-4 fetch origin --no-tags \ + refs/heads/merge:refs/heads/merge && + test_cmp_config -C fetch-http-4 4 fetch.bundlecreationtoken && + + cat >expect <<-EOF && + $HTTPD_URL/bundle-list + $HTTPD_URL/bundle-4.bundle + EOF + + test_remote_https_urls <trace2.txt >actual && + test_cmp expect actual && + + # received merge ref from bundle-4, but right is missing + # because we did not download bundle-3. + git -C fetch-http-4 for-each-ref --format="%(refname)" "refs/bundles/*" >refs && + + cat >expect <<-\EOF && + refs/bundles/base + refs/bundles/left + refs/bundles/merge + EOF + test_cmp expect refs && + + # No-op fetch + GIT_TRACE2_EVENT="$(pwd)/trace2b.txt" \ + git -C fetch-http-4 fetch origin && + + cat >expect <<-EOF && + $HTTPD_URL/bundle-list + EOF + test_remote_https_urls <trace2b.txt >actual && + test_cmp expect actual +' + +test_expect_success 'creationToken heuristic with failed downloads (clone)' ' + test_when_finished rm -rf download-* trace*.txt && + + # Case 1: base bundle does not exist, nothing can unbundle + cat >"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF && + [bundle] + version = 1 + mode = all + heuristic = creationToken + + [bundle "bundle-1"] + uri = fake.bundle + creationToken = 1 + + [bundle "bundle-2"] + uri = bundle-2.bundle + creationToken = 2 + + [bundle "bundle-3"] + uri = bundle-3.bundle + creationToken = 3 + + [bundle "bundle-4"] + uri = bundle-4.bundle + creationToken = 4 + EOF + + GIT_TRACE2_EVENT="$(pwd)/trace-clone-1.txt" \ + git clone --single-branch --branch=base \ + --bundle-uri="$HTTPD_URL/bundle-list" \ + "$HTTPD_URL/smart/fetch.git" download-1 && + + # Bundle failure does not set these configs. + test_must_fail git -C download-1 config fetch.bundleuri && + test_must_fail git -C download-1 config fetch.bundlecreationtoken && + + cat >expect <<-EOF && + $HTTPD_URL/bundle-list + $HTTPD_URL/bundle-4.bundle + $HTTPD_URL/bundle-3.bundle + $HTTPD_URL/bundle-2.bundle + $HTTPD_URL/fake.bundle + EOF + test_remote_https_urls <trace-clone-1.txt >actual && + test_cmp expect actual && + + # All bundles failed to unbundle + git -C download-1 for-each-ref --format="%(refname)" "refs/bundles/*" >refs && + test_must_be_empty refs && + + # Case 2: middle bundle does not exist, only two bundles can unbundle + cat >"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF && + [bundle] + version = 1 + mode = all + heuristic = creationToken + + [bundle "bundle-1"] + uri = bundle-1.bundle + creationToken = 1 + + [bundle "bundle-2"] + uri = fake.bundle + creationToken = 2 + + [bundle "bundle-3"] + uri = bundle-3.bundle + creationToken = 3 + + [bundle "bundle-4"] + uri = bundle-4.bundle + creationToken = 4 + EOF + + GIT_TRACE2_EVENT="$(pwd)/trace-clone-2.txt" \ + git clone --single-branch --branch=base \ + --bundle-uri="$HTTPD_URL/bundle-list" \ + "$HTTPD_URL/smart/fetch.git" download-2 && + + # Bundle failure does not set these configs. + test_must_fail git -C download-2 config fetch.bundleuri && + test_must_fail git -C download-2 config fetch.bundlecreationtoken && + + cat >expect <<-EOF && + $HTTPD_URL/bundle-list + $HTTPD_URL/bundle-4.bundle + $HTTPD_URL/bundle-3.bundle + $HTTPD_URL/fake.bundle + $HTTPD_URL/bundle-1.bundle + EOF + test_remote_https_urls <trace-clone-2.txt >actual && + test_cmp expect actual && + + # bundle-1 and bundle-3 could unbundle, but bundle-4 could not + git -C download-2 for-each-ref --format="%(refname)" "refs/bundles/*" >refs && + cat >expect <<-EOF && + refs/bundles/base + refs/bundles/right + EOF + test_cmp expect refs && + + # Case 3: top bundle does not exist, rest unbundle fine. + cat >"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF && + [bundle] + version = 1 + mode = all + heuristic = creationToken + + [bundle "bundle-1"] + uri = bundle-1.bundle + creationToken = 1 + + [bundle "bundle-2"] + uri = bundle-2.bundle + creationToken = 2 + + [bundle "bundle-3"] + uri = bundle-3.bundle + creationToken = 3 + + [bundle "bundle-4"] + uri = fake.bundle + creationToken = 4 + EOF + + GIT_TRACE2_EVENT="$(pwd)/trace-clone-3.txt" \ + git clone --single-branch --branch=base \ + --bundle-uri="$HTTPD_URL/bundle-list" \ + "$HTTPD_URL/smart/fetch.git" download-3 && + + # As long as we have continguous successful downloads, + # we _do_ set these configs. + test_cmp_config -C download-3 "$HTTPD_URL/bundle-list" fetch.bundleuri && + test_cmp_config -C download-3 3 fetch.bundlecreationtoken && + + cat >expect <<-EOF && + $HTTPD_URL/bundle-list + $HTTPD_URL/fake.bundle + $HTTPD_URL/bundle-3.bundle + $HTTPD_URL/bundle-2.bundle + $HTTPD_URL/bundle-1.bundle + EOF + test_remote_https_urls <trace-clone-3.txt >actual && + test_cmp expect actual && + + # fake.bundle did not unbundle, but the others did. + git -C download-3 for-each-ref --format="%(refname)" "refs/bundles/*" >refs && + cat >expect <<-EOF && + refs/bundles/base + refs/bundles/left + refs/bundles/right + EOF + test_cmp expect refs +' + +# Expand the bundle list to include other interesting shapes, specifically +# interesting for use when fetching from a previous state. +# +# ---------------- bundle-7 +# 7 +# _/|\_ +# ---/--|--\------ bundle-6 +# 5 | 6 +# --|---|---|----- bundle-4 +# | 4 | +# | / \ / +# --|-|---|/------ bundle-3 (the client will be caught up to this point.) +# \ | 3 +# ---\|---|------- bundle-2 +# 2 | +# ----|---|------- bundle-1 +# \ / +# 1 +# | +# (previous commits) +test_expect_success 'expand incremental bundle list' ' + ( + cd clone-from && + git checkout -b lefter left && + test_commit 5 && + git checkout -b righter right && + test_commit 6 && + git checkout -b top lefter && + git merge -m "7" merge righter && + + git bundle create bundle-6.bundle lefter righter --not left right && + git bundle create bundle-7.bundle top --not lefter merge righter && + + cp bundle-*.bundle "$HTTPD_DOCUMENT_ROOT_PATH/" + ) && + git -C "$HTTPD_DOCUMENT_ROOT_PATH/fetch.git" fetch origin +refs/heads/*:refs/heads/* +' + +test_expect_success 'creationToken heuristic with failed downloads (fetch)' ' + test_when_finished rm -rf download-* trace*.txt && + + cat >"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF && + [bundle] + version = 1 + mode = all + heuristic = creationToken + + [bundle "bundle-1"] + uri = bundle-1.bundle + creationToken = 1 + + [bundle "bundle-2"] + uri = bundle-2.bundle + creationToken = 2 + + [bundle "bundle-3"] + uri = bundle-3.bundle + creationToken = 3 + EOF + + git clone --single-branch --branch=left \ + --bundle-uri="$HTTPD_URL/bundle-list" \ + "$HTTPD_URL/smart/fetch.git" fetch-base && + test_cmp_config -C fetch-base "$HTTPD_URL/bundle-list" fetch.bundleURI && + test_cmp_config -C fetch-base 3 fetch.bundleCreationToken && + + # Case 1: all bundles exist: successful unbundling of all bundles + cat >"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF && + [bundle] + version = 1 + mode = all + heuristic = creationToken + + [bundle "bundle-1"] + uri = bundle-1.bundle + creationToken = 1 + + [bundle "bundle-2"] + uri = bundle-2.bundle + creationToken = 2 + + [bundle "bundle-3"] + uri = bundle-3.bundle + creationToken = 3 + + [bundle "bundle-4"] + uri = bundle-4.bundle + creationToken = 4 + + [bundle "bundle-6"] + uri = bundle-6.bundle + creationToken = 6 + + [bundle "bundle-7"] + uri = bundle-7.bundle + creationToken = 7 + EOF + + cp -r fetch-base fetch-1 && + GIT_TRACE2_EVENT="$(pwd)/trace-fetch-1.txt" \ + git -C fetch-1 fetch origin && + test_cmp_config -C fetch-1 7 fetch.bundlecreationtoken && + + cat >expect <<-EOF && + $HTTPD_URL/bundle-list + $HTTPD_URL/bundle-7.bundle + $HTTPD_URL/bundle-6.bundle + $HTTPD_URL/bundle-4.bundle + EOF + test_remote_https_urls <trace-fetch-1.txt >actual && + test_cmp expect actual && + + # Check which bundles have unbundled by refs + git -C fetch-1 for-each-ref --format="%(refname)" "refs/bundles/*" >refs && + cat >expect <<-EOF && + refs/bundles/base + refs/bundles/left + refs/bundles/lefter + refs/bundles/merge + refs/bundles/right + refs/bundles/righter + refs/bundles/top + EOF + test_cmp expect refs && + + # Case 2: middle bundle does not exist, only bundle-4 can unbundle + cat >"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF && + [bundle] + version = 1 + mode = all + heuristic = creationToken + + [bundle "bundle-1"] + uri = bundle-1.bundle + creationToken = 1 + + [bundle "bundle-2"] + uri = bundle-2.bundle + creationToken = 2 + + [bundle "bundle-3"] + uri = bundle-3.bundle + creationToken = 3 + + [bundle "bundle-4"] + uri = bundle-4.bundle + creationToken = 4 + + [bundle "bundle-6"] + uri = fake.bundle + creationToken = 6 + + [bundle "bundle-7"] + uri = bundle-7.bundle + creationToken = 7 + EOF + + cp -r fetch-base fetch-2 && + GIT_TRACE2_EVENT="$(pwd)/trace-fetch-2.txt" \ + git -C fetch-2 fetch origin && + + # Since bundle-7 fails to unbundle, do not update creation token. + test_cmp_config -C fetch-2 3 fetch.bundlecreationtoken && + + cat >expect <<-EOF && + $HTTPD_URL/bundle-list + $HTTPD_URL/bundle-7.bundle + $HTTPD_URL/fake.bundle + $HTTPD_URL/bundle-4.bundle + EOF + test_remote_https_urls <trace-fetch-2.txt >actual && + test_cmp expect actual && + + # Check which bundles have unbundled by refs + git -C fetch-2 for-each-ref --format="%(refname)" "refs/bundles/*" >refs && + cat >expect <<-EOF && + refs/bundles/base + refs/bundles/left + refs/bundles/merge + refs/bundles/right + EOF + test_cmp expect refs && + + # Case 3: top bundle does not exist, rest unbundle fine. + cat >"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF && + [bundle] + version = 1 + mode = all + heuristic = creationToken + + [bundle "bundle-1"] + uri = bundle-1.bundle + creationToken = 1 + + [bundle "bundle-2"] + uri = bundle-2.bundle + creationToken = 2 + + [bundle "bundle-3"] + uri = bundle-3.bundle + creationToken = 3 + + [bundle "bundle-4"] + uri = bundle-4.bundle + creationToken = 4 + + [bundle "bundle-6"] + uri = bundle-6.bundle + creationToken = 6 + + [bundle "bundle-7"] + uri = fake.bundle + creationToken = 7 + EOF + + cp -r fetch-base fetch-3 && + GIT_TRACE2_EVENT="$(pwd)/trace-fetch-3.txt" \ + git -C fetch-3 fetch origin && + + # As long as we have continguous successful downloads, + # we _do_ set the maximum creation token. + test_cmp_config -C fetch-3 6 fetch.bundlecreationtoken && + + # NOTE: the fetch skips bundle-4 since bundle-6 successfully + # unbundles itself and bundle-7 failed to download. + cat >expect <<-EOF && + $HTTPD_URL/bundle-list + $HTTPD_URL/fake.bundle + $HTTPD_URL/bundle-6.bundle + EOF + test_remote_https_urls <trace-fetch-3.txt >actual && + test_cmp expect actual && + + # Check which bundles have unbundled by refs + git -C fetch-3 for-each-ref --format="%(refname)" "refs/bundles/*" >refs && + cat >expect <<-EOF && + refs/bundles/base + refs/bundles/left + refs/bundles/lefter + refs/bundles/right + refs/bundles/righter + EOF + test_cmp expect refs +' + # Do not add tests here unless they use the HTTP server, as they will # not run unless the HTTP dependencies exist. diff --git a/t/t5560-http-backend-noserver.sh b/t/t5560-http-backend-noserver.sh index d30cf4f5b8..f75068de64 100755 --- a/t/t5560-http-backend-noserver.sh +++ b/t/t5560-http-backend-noserver.sh @@ -4,6 +4,7 @@ test_description='test git-http-backend-noserver' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh HTTPD_DOCUMENT_ROOT_PATH="$TRASH_DIRECTORY" diff --git a/t/t5561-http-backend.sh b/t/t5561-http-backend.sh index 9c57d84315..e1d3b8caed 100755 --- a/t/t5561-http-backend.sh +++ b/t/t5561-http-backend.sh @@ -4,6 +4,7 @@ test_description='test git-http-backend' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-httpd.sh diff --git a/t/t5562-http-backend-content-length.sh b/t/t5562-http-backend-content-length.sh index b68ec22d3f..7ee9858a78 100755 --- a/t/t5562-http-backend-content-length.sh +++ b/t/t5562-http-backend-content-length.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='test git-http-backend respects CONTENT_LENGTH' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_lazy_prereq GZIP 'gzip --version' diff --git a/t/t5564-http-proxy.sh b/t/t5564-http-proxy.sh new file mode 100755 index 0000000000..9da5134614 --- /dev/null +++ b/t/t5564-http-proxy.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +test_description="test fetching through http proxy" + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-httpd.sh + +LIB_HTTPD_PROXY=1 +start_httpd + +test_expect_success 'setup repository' ' + test_commit foo && + git init --bare "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + git push --mirror "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" +' + +setup_askpass_helper + +# sanity check that our test setup is correctly using proxy +test_expect_success 'proxy requires password' ' + test_config_global http.proxy $HTTPD_DEST && + test_must_fail git clone $HTTPD_URL/smart/repo.git 2>err && + grep "error.*407" err +' + +test_expect_success 'clone through proxy with auth' ' + test_when_finished "rm -rf clone" && + test_config_global http.proxy http://proxuser:proxpass@$HTTPD_DEST && + GIT_TRACE_CURL=$PWD/trace git clone $HTTPD_URL/smart/repo.git clone && + grep -i "Proxy-Authorization: Basic <redacted>" trace +' + +test_expect_success 'clone can prompt for proxy password' ' + test_when_finished "rm -rf clone" && + test_config_global http.proxy http://proxuser@$HTTPD_DEST && + set_askpass nobody proxpass && + GIT_TRACE_CURL=$PWD/trace git clone $HTTPD_URL/smart/repo.git clone && + expect_askpass pass proxuser +' + +test_done diff --git a/t/t5573-pull-verify-signatures.sh b/t/t5573-pull-verify-signatures.sh index a53dd8550d..1221ac0597 100755 --- a/t/t5573-pull-verify-signatures.sh +++ b/t/t5573-pull-verify-signatures.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='pull signature verification tests' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY/lib-gpg.sh" diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index 1928ea1dd7..b7d5551262 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -831,6 +831,52 @@ test_expect_success 'auto-discover multiple bundles from HTTP clone' ' grep -f pattern trace.txt ' +test_expect_success 'auto-discover multiple bundles from HTTP clone: creationToken heuristic' ' + test_when_finished rm -rf "$HTTPD_DOCUMENT_ROOT_PATH/repo4.git" && + test_when_finished rm -rf clone-heuristic trace*.txt && + + test_commit -C src newest && + git -C src bundle create "$HTTPD_DOCUMENT_ROOT_PATH/newest.bundle" HEAD~1..HEAD && + git clone --bare --no-local src "$HTTPD_DOCUMENT_ROOT_PATH/repo4.git" && + + cat >>"$HTTPD_DOCUMENT_ROOT_PATH/repo4.git/config" <<-EOF && + [uploadPack] + advertiseBundleURIs = true + + [bundle] + version = 1 + mode = all + heuristic = creationToken + + [bundle "everything"] + uri = $HTTPD_URL/everything.bundle + creationtoken = 1 + + [bundle "new"] + uri = $HTTPD_URL/new.bundle + creationtoken = 2 + + [bundle "newest"] + uri = $HTTPD_URL/newest.bundle + creationtoken = 3 + EOF + + GIT_TRACE2_EVENT="$(pwd)/trace-clone.txt" \ + git -c protocol.version=2 \ + -c transfer.bundleURI=true clone \ + "$HTTPD_URL/smart/repo4.git" clone-heuristic && + + cat >expect <<-EOF && + $HTTPD_URL/newest.bundle + $HTTPD_URL/new.bundle + $HTTPD_URL/everything.bundle + EOF + + # We should fetch all bundles in the expected order. + test_remote_https_urls <trace-clone.txt >actual && + test_cmp expect actual +' + # 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/t5604-clone-reference.sh b/t/t5604-clone-reference.sh index 2734e37e88..83e3c97861 100755 --- a/t/t5604-clone-reference.sh +++ b/t/t5604-clone-reference.sh @@ -7,6 +7,7 @@ test_description='test clone --reference' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh base_dir=$(pwd) @@ -344,4 +345,20 @@ test_expect_success SYMLINKS 'clone repo with symlinked or unknown files at obje test_must_be_empty T--shared.objects-symlinks.raw ' +test_expect_success SYMLINKS 'clone repo with symlinked objects directory' ' + test_when_finished "rm -fr sensitive malicious" && + + mkdir -p sensitive && + echo "secret" >sensitive/file && + + git init malicious && + rm -fr malicious/.git/objects && + ln -s "$(pwd)/sensitive" ./malicious/.git/objects && + + test_must_fail git clone --local malicious clone 2>err && + + test_path_is_missing clone && + grep "failed to start iterator over" err +' + test_done diff --git a/t/t5606-clone-options.sh b/t/t5606-clone-options.sh index cf221e92c4..27f9f77638 100755 --- a/t/t5606-clone-options.sh +++ b/t/t5606-clone-options.sh @@ -4,6 +4,7 @@ test_description='basic clone options' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t5613-info-alternate.sh b/t/t5613-info-alternate.sh index 895f46bb91..7708cbafa9 100755 --- a/t/t5613-info-alternate.sh +++ b/t/t5613-info-alternate.sh @@ -4,6 +4,8 @@ # test_description='test transitive info/alternate entries' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'preparing first repository' ' diff --git a/t/t5619-clone-local-ambiguous-transport.sh b/t/t5619-clone-local-ambiguous-transport.sh new file mode 100755 index 0000000000..cce62bf78d --- /dev/null +++ b/t/t5619-clone-local-ambiguous-transport.sh @@ -0,0 +1,70 @@ +#!/bin/sh + +test_description='test local clone with ambiguous transport' + +. ./test-lib.sh +. "$TEST_DIRECTORY/lib-httpd.sh" + +if ! test_have_prereq SYMLINKS +then + skip_all='skipping test, symlink support unavailable' + test_done +fi + +start_httpd + +REPO="$HTTPD_DOCUMENT_ROOT_PATH/sub.git" +URI="$HTTPD_URL/dumb/sub.git" + +test_expect_success 'setup' ' + mkdir -p sensitive && + echo "secret" >sensitive/secret && + + git init --bare "$REPO" && + test_commit_bulk -C "$REPO" --ref=main 1 && + + git -C "$REPO" update-ref HEAD main && + git -C "$REPO" update-server-info && + + git init malicious && + ( + cd malicious && + + git submodule add "$URI" && + + mkdir -p repo/refs && + touch repo/refs/.gitkeep && + printf "ref: refs/heads/a" >repo/HEAD && + ln -s "$(cd .. && pwd)/sensitive" repo/objects && + + mkdir -p "$HTTPD_URL/dumb" && + ln -s "../../../.git/modules/sub/../../../repo/" "$URI" && + + git add . && + git commit -m "initial commit" + ) && + + # Delete all of the references in our malicious submodule to + # avoid the client attempting to checkout any objects (which + # will be missing, and thus will cause the clone to fail before + # we can trigger the exploit). + git -C "$REPO" for-each-ref --format="delete %(refname)" >in && + git -C "$REPO" update-ref --stdin <in && + git -C "$REPO" update-server-info +' + +test_expect_success 'ambiguous transport does not lead to arbitrary file-inclusion' ' + git clone malicious clone && + test_must_fail git -C clone submodule update --init 2>err && + + test_path_is_missing clone/.git/modules/sub/objects/secret && + # We would actually expect "transport .file. not allowed" here, + # but due to quirks of the URL detection in Git, we mis-parse + # the absolute path as a bogus URL and die before that step. + # + # This works for now, and if we ever fix the URL detection, it + # is OK to change this to detect the transport error. + grep "protocol .* is not supported" err +' + +test_done diff --git a/t/t5705-session-id-in-capabilities.sh b/t/t5705-session-id-in-capabilities.sh index ed38c76c29..b8a722ec27 100755 --- a/t/t5705-session-id-in-capabilities.sh +++ b/t/t5705-session-id-in-capabilities.sh @@ -2,6 +2,7 @@ test_description='session ID in capabilities' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh REPO="$(pwd)/repo" diff --git a/t/t5750-bundle-uri-parse.sh b/t/t5750-bundle-uri-parse.sh index 7b4f930e53..81bdf58b94 100755 --- a/t/t5750-bundle-uri-parse.sh +++ b/t/t5750-bundle-uri-parse.sh @@ -250,4 +250,41 @@ test_expect_success 'parse config format edge cases: empty key or value' ' test_cmp_config_output expect actual ' +test_expect_success 'parse config format: creationToken heuristic' ' + cat >expect <<-\EOF && + [bundle] + version = 1 + mode = all + heuristic = creationToken + [bundle "one"] + uri = http://example.com/bundle.bdl + creationToken = 123456 + [bundle "two"] + uri = https://example.com/bundle.bdl + creationToken = 12345678901234567890 + [bundle "three"] + uri = file:///usr/share/git/bundle.bdl + creationToken = 1 + EOF + + test-tool bundle-uri parse-config expect >actual 2>err && + test_must_be_empty err && + test_cmp_config_output expect actual +' + +test_expect_success 'parse config format edge cases: creationToken heuristic' ' + cat >expect <<-\EOF && + [bundle] + version = 1 + mode = all + heuristic = creationToken + [bundle "one"] + uri = http://example.com/bundle.bdl + creationToken = bogus + EOF + + test-tool bundle-uri parse-config expect >actual 2>err && + grep "could not parse bundle list key creationToken with value '\''bogus'\''" err +' + test_done diff --git a/t/t5810-proto-disable-local.sh b/t/t5810-proto-disable-local.sh index c1ef99b85c..862610256f 100755 --- a/t/t5810-proto-disable-local.sh +++ b/t/t5810-proto-disable-local.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='test disabling of local paths in clone/fetch' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY/lib-proto-disable.sh" diff --git a/t/t5813-proto-disable-ssh.sh b/t/t5813-proto-disable-ssh.sh index 3f084ee306..2e975dc70e 100755 --- a/t/t5813-proto-disable-ssh.sh +++ b/t/t5813-proto-disable-ssh.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='test disabling of git-over-ssh in clone/fetch' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY/lib-proto-disable.sh" diff --git a/t/t6011-rev-list-with-bad-commit.sh b/t/t6011-rev-list-with-bad-commit.sh index bad02cf5b8..b2e422cf0f 100755 --- a/t/t6011-rev-list-with-bad-commit.sh +++ b/t/t6011-rev-list-with-bad-commit.sh @@ -2,6 +2,7 @@ test_description='git rev-list should notice bad commits' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Note: diff --git a/t/t6014-rev-list-all.sh b/t/t6014-rev-list-all.sh index c9bedd29cb..16b8bd1d09 100755 --- a/t/t6014-rev-list-all.sh +++ b/t/t6014-rev-list-all.sh @@ -2,6 +2,7 @@ test_description='--all includes detached HEADs' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh diff --git a/t/t6020-bundle-misc.sh b/t/t6020-bundle-misc.sh index 3a1cf30b1d..7d40994991 100755 --- a/t/t6020-bundle-misc.sh +++ b/t/t6020-bundle-misc.sh @@ -566,4 +566,44 @@ test_expect_success 'cloning from filtered bundle has useful error' ' grep "cannot clone from filtered bundle" err ' +test_expect_success 'verify catches unreachable, broken prerequisites' ' + test_when_finished rm -rf clone-from clone-to && + git init clone-from && + ( + cd clone-from && + git checkout -b base && + test_commit A && + git checkout -b tip && + git commit --allow-empty -m "will drop by shallow" && + git commit --allow-empty -m "will keep by shallow" && + git commit --allow-empty -m "for bundle, not clone" && + git bundle create tip.bundle tip~1..tip && + git reset --hard HEAD~1 && + git checkout base + ) && + BAD_OID=$(git -C clone-from rev-parse tip~1) && + TIP_OID=$(git -C clone-from rev-parse tip) && + git clone --depth=1 --no-single-branch \ + "file://$(pwd)/clone-from" clone-to && + ( + cd clone-to && + + # Set up broken history by removing shallow markers + git update-ref -d refs/remotes/origin/tip && + rm .git/shallow && + + # Verify should fail + test_must_fail git bundle verify \ + ../clone-from/tip.bundle 2>err && + grep "some prerequisite commits .* are not connected" err && + test_line_count = 1 err && + + # Unbundling should fail + test_must_fail git bundle unbundle \ + ../clone-from/tip.bundle 2>err && + grep "some prerequisite commits .* are not connected" err && + test_line_count = 1 err + ) +' + test_done diff --git a/t/t6021-rev-list-exclude-hidden.sh b/t/t6021-rev-list-exclude-hidden.sh index 32b2b09413..11c50b7c0d 100755 --- a/t/t6021-rev-list-exclude-hidden.sh +++ b/t/t6021-rev-list-exclude-hidden.sh @@ -2,6 +2,7 @@ test_description='git rev-list --exclude-hidden test' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index 9a35e783a7..c9afcef201 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -657,4 +657,10 @@ test_expect_success 'setup: describe commits with disjoint bases 2' ' check_describe -C disjoint2 "B-3-gHASH" HEAD +test_expect_success 'setup misleading taggerdates' ' + GIT_COMMITTER_DATE="2006-12-12 12:31" git tag -a -m "another tag" newer-tag-older-commit unique-file~1 +' + +check_describe newer-tag-older-commit~1 --contains unique-file~2 + test_done diff --git a/t/t6132-pathspec-exclude.sh b/t/t6132-pathspec-exclude.sh index cada952f9a..9fdafeb1e9 100755 --- a/t/t6132-pathspec-exclude.sh +++ b/t/t6132-pathspec-exclude.sh @@ -293,11 +293,7 @@ test_expect_success 'add with all negative' ' test_cmp expect actual ' -test_lazy_prereq ADD_I_USE_BUILTIN_OR_PERL ' - test_have_prereq ADD_I_USE_BUILTIN || test_have_prereq PERL -' - -test_expect_success ADD_I_USE_BUILTIN_OR_PERL 'add -p with all negative' ' +test_expect_success 'add -p with all negative' ' H=$(git rev-parse HEAD) && git reset --hard $H && git clean -f && diff --git a/t/t6439-merge-co-error-msgs.sh b/t/t6439-merge-co-error-msgs.sh index 52cf0c8769..0cbec57cda 100755 --- a/t/t6439-merge-co-error-msgs.sh +++ b/t/t6439-merge-co-error-msgs.sh @@ -5,6 +5,7 @@ test_description='unpack-trees error messages' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh diff --git a/t/t6501-freshen-objects.sh b/t/t6501-freshen-objects.sh index 10662456ae..3968b47ed5 100755 --- a/t/t6501-freshen-objects.sh +++ b/t/t6501-freshen-objects.sh @@ -28,6 +28,7 @@ test_description='check pruning of dependent objects' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # We care about reachability, so we do not want to use diff --git a/t/t7105-reset-patch.sh b/t/t7105-reset-patch.sh index fc2a6cf5c7..9b46da7aaa 100755 --- a/t/t7105-reset-patch.sh +++ b/t/t7105-reset-patch.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='git reset --patch' + +TEST_PASSES_SANITIZE_LEAK=true . ./lib-patch-mode.sh test_expect_success PERL 'setup' ' diff --git a/t/t7106-reset-unborn-branch.sh b/t/t7106-reset-unborn-branch.sh index ecb85c3b82..a0b67a0b84 100755 --- a/t/t7106-reset-unborn-branch.sh +++ b/t/t7106-reset-unborn-branch.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='git reset should work on unborn branch' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t7107-reset-pathspec-file.sh b/t/t7107-reset-pathspec-file.sh index 523efbecde..af5ea406db 100755 --- a/t/t7107-reset-pathspec-file.sh +++ b/t/t7107-reset-pathspec-file.sh @@ -2,6 +2,7 @@ test_description='reset --pathspec-from-file' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_tick diff --git a/t/t7301-clean-interactive.sh b/t/t7301-clean-interactive.sh index a07e8b86de..d82a3210a1 100755 --- a/t/t7301-clean-interactive.sh +++ b/t/t7301-clean-interactive.sh @@ -2,6 +2,7 @@ test_description='git clean -i basic tests' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-terminal.sh diff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh index ebeca12a71..b19792b326 100755 --- a/t/t7402-submodule-rebase.sh +++ b/t/t7402-submodule-rebase.sh @@ -5,6 +5,7 @@ test_description='Test rebasing, stashing, etc. with submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t7403-submodule-sync.sh b/t/t7403-submodule-sync.sh index ea92ef52a5..ff09443a0a 100755 --- a/t/t7403-submodule-sync.sh +++ b/t/t7403-submodule-sync.sh @@ -11,6 +11,7 @@ These tests exercise the "git submodule sync" subcommand. GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t7409-submodule-detached-work-tree.sh b/t/t7409-submodule-detached-work-tree.sh index 374ed481e9..574a6fc526 100755 --- a/t/t7409-submodule-detached-work-tree.sh +++ b/t/t7409-submodule-detached-work-tree.sh @@ -13,6 +13,7 @@ TEST_NO_CREATE_REPO=1 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t7416-submodule-dash-url.sh b/t/t7416-submodule-dash-url.sh index 3ebd985981..7cf72b9a07 100755 --- a/t/t7416-submodule-dash-url.sh +++ b/t/t7416-submodule-dash-url.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='check handling of disallowed .gitmodule urls' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t7450-bad-git-dotfiles.sh b/t/t7450-bad-git-dotfiles.sh index ba1f569bcb..0d0c3f2c68 100755 --- a/t/t7450-bad-git-dotfiles.sh +++ b/t/t7450-bad-git-dotfiles.sh @@ -12,6 +12,8 @@ Such as: - symlinked .gitmodules, etc ' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-pack.sh diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh index bc7a31ba3e..48f86cb367 100755 --- a/t/t7510-signed-commit.sh +++ b/t/t7510-signed-commit.sh @@ -387,4 +387,48 @@ test_expect_success GPG 'verify-commit verifies multiply signed commits' ' ! grep "BAD signature from" actual ' +test_expect_success 'custom `gpg.program`' ' + write_script fake-gpg <<-\EOF && + args="$*" + + # skip uninteresting options + while case "$1" in + --status-fd=*|--keyid-format=*) ;; # skip + *) break;; + esac; do shift; done + + case "$1" in + -bsau) + test -z "$LET_GPG_PROGRAM_FAIL" || { + echo "zOMG signing failed!" >&2 + exit 1 + } + cat >sign.file + echo "[GNUPG:] SIG_CREATED $args" >&2 + echo "-----BEGIN PGP MESSAGE-----" + echo "$args" + echo "-----END PGP MESSAGE-----" + ;; + --verify) + cat "$2" >verify.file + exit 0 + ;; + *) + echo "Unhandled args: $*" >&2 + exit 1 + ;; + esac + EOF + + test_config gpg.program "$(pwd)/fake-gpg" && + git commit -S --allow-empty -m signed-commit && + test_path_exists sign.file && + git show --show-signature && + test_path_exists verify.file && + + test_must_fail env LET_GPG_PROGRAM_FAIL=1 \ + git commit -S --allow-empty -m must-fail 2>err && + grep zOMG err +' + test_done diff --git a/t/t7612-merge-verify-signatures.sh b/t/t7612-merge-verify-signatures.sh index 61330f71b1..f5c90cc22a 100755 --- a/t/t7612-merge-verify-signatures.sh +++ b/t/t7612-merge-verify-signatures.sh @@ -4,6 +4,7 @@ test_description='merge signature verification tests' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY/lib-gpg.sh" diff --git a/t/t7701-repack-unpack-unreachable.sh b/t/t7701-repack-unpack-unreachable.sh index b7ac4f598a..ebb267855f 100755 --- a/t/t7701-repack-unpack-unreachable.sh +++ b/t/t7701-repack-unpack-unreachable.sh @@ -5,6 +5,7 @@ test_description='git repack works correctly' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh fsha1= diff --git a/t/t9106-git-svn-commit-diff-clobber.sh b/t/t9106-git-svn-commit-diff-clobber.sh index 3cab0b9720..bca496c40e 100755 --- a/t/t9106-git-svn-commit-diff-clobber.sh +++ b/t/t9106-git-svn-commit-diff-clobber.sh @@ -3,7 +3,6 @@ # Copyright (c) 2006 Eric Wong test_description='git svn commit-diff clobber' -TEST_FAILS_SANITIZE_LEAK=true . ./lib-git-svn.sh test_expect_success 'initialize repo' ' diff --git a/t/t9164-git-svn-dcommit-concurrent.sh b/t/t9164-git-svn-dcommit-concurrent.sh index 1465156072..c8e6c0733f 100755 --- a/t/t9164-git-svn-dcommit-concurrent.sh +++ b/t/t9164-git-svn-dcommit-concurrent.sh @@ -5,7 +5,6 @@ test_description='concurrent git svn dcommit' -TEST_FAILS_SANITIZE_LEAK=true . ./lib-git-svn.sh diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 75b8ee95e7..58cfd2f1fd 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -1767,6 +1767,14 @@ test_region () { return 0 } +# Given a GIT_TRACE2_EVENT log over stdin, writes to stdout a list of URLs +# sent to git-remote-https child processes. +test_remote_https_urls() { + grep -e '"event":"child_start".*"argv":\["git-remote-https",".*"\]' | + sed -e 's/{"event":"child_start".*"argv":\["git-remote-https","//g' \ + -e 's/"\]}//g' +} + # Print the destination of symlink(s) provided as arguments. Basically # the same as the readlink command, but it's not available everywhere. test_readlink () { diff --git a/t/test-lib.sh b/t/test-lib.sh index 01e88781dd..d272cca008 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -1937,10 +1937,6 @@ test_lazy_prereq SHA1 ' esac ' -test_lazy_prereq ADD_I_USE_BUILTIN ' - test_bool_env GIT_TEST_ADD_I_USE_BUILTIN true -' - # Ensure that no test accidentally triggers a Git command # that runs the actual maintenance scheduler, affecting a user's # system permanently. diff --git a/userdiff.c b/userdiff.c index d71b82feb7..58a3d59ef8 100644 --- a/userdiff.c +++ b/userdiff.c @@ -170,8 +170,8 @@ PATTERNS("html", "[^<>= \t]+"), PATTERNS("java", "!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n" - /* Class, enum, and interface declarations */ - "^[ \t]*(([a-z]+[ \t]+)*(class|enum|interface)[ \t]+[A-Za-z][A-Za-z0-9_$]*[ \t]+.*)$\n" + /* Class, enum, interface, and record declarations */ + "^[ \t]*(([a-z-]+[ \t]+)*(class|enum|interface|record)[ \t]+.*)$\n" /* Method definitions; note that constructor signatures are not */ /* matched because they are indistinguishable from method calls. */ "^[ \t]*(([A-Za-z_<>&][][?&<>.,A-Za-z_0-9]*[ \t]+)+[A-Za-z_][A-Za-z_0-9]*[ \t]*\\([^;]*)$", @@ -293,7 +293,7 @@ PATTERNS("scheme", "|([^][)(}{[ \t])+"), PATTERNS("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$", "\\\\[a-zA-Z@]+|\\\\.|[a-zA-Z0-9\x80-\xff]+"), -{ "default", NULL, -1, { NULL, 0 } }, +{ "default", NULL, NULL, -1, { NULL, 0 } }, }; #undef PATTERNS #undef IPATTERN @@ -394,6 +394,8 @@ int userdiff_config(const char *k, const char *v) return parse_bool(&drv->textconv_want_cache, k, v); if (!strcmp(type, "wordregex")) return git_config_string(&drv->word_regex, k, v); + if (!strcmp(type, "algorithm")) + return git_config_string(&drv->algorithm, k, v); return 0; } diff --git a/userdiff.h b/userdiff.h index aee91bc77e..24419db697 100644 --- a/userdiff.h +++ b/userdiff.h @@ -14,6 +14,7 @@ struct userdiff_funcname { struct userdiff_driver { const char *name; const char *external; + const char *algorithm; int binary; struct userdiff_funcname funcname; const char *word_regex; |
