diff options
266 files changed, 3364 insertions, 2923 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 e942a83b45..e875c59054 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,6 @@ /bin-wrappers/ /git /git-add -/git-add--interactive /git-am /git-annotate /git-apply @@ -59,7 +58,6 @@ /git-difftool /git-difftool--helper /git-describe -/git-env--helper /git-fast-export /git-fast-import /git-fetch 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 629d3c6980..d1428f5fb8 100644 --- a/Documentation/RelNotes/2.40.0.txt +++ b/Documentation/RelNotes/2.40.0.txt @@ -35,6 +35,25 @@ UI, Workflows & Features * Introduce an optional configuration to allow the trailing hash that protects the index file from bit flipping. + * "git check-attr" learned to take an optional tree-ish to read the + .gitattributes file from. + + * "scalar" learned to give progress bar. + + * "grep -P" learned to use Unicode Character Property to grok + character classes when processing \b and \w etc. + + * "git rebase" often ignored incompatible options instead of + complaining, which has been corrected. + + * "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. + Performance, Internal Implementation, Development Support etc. @@ -55,12 +74,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. @@ -70,14 +87,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. @@ -92,8 +116,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. @@ -123,49 +145,85 @@ 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. + + * Remove "git env--helper" and demote it to a test-tool subcommand. + (merge 4a1baacd46 ab/test-env-helper later to maint). + + * 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. + + * Pthread emulation on Win32 leaked thread handle when a thread is + joined. + (merge 238a9dfe86 sk/win32-close-handle-upon-pthread-join later to maint). + + * "git send-email -v 3" used to be expanded to "git send-email + --validate 3" when the user meant to pass them down to + "format-patch", which has been corrected. + (merge 8774aa56ad km/send-email-with-v-reroll-count later to maint). + + * Document that "branch -f <branch>" disables only the safety to + avoid recreating an existing branch. + + * "git fetch <group>", when "<group>" of remotes lists the same + remote twice, unnecessarily failed when parallel fetching was + enabled, which has been corrected. + (merge 06a668cb90 cw/fetch-remote-group-with-duplication later to maint). + + * Clarify how "checkout -b/-B" and "git branch [-f]" are similar but + different in the documentation. + + * "git hash-object" now checks that the resulting object is well + formed with the same code as "git fsck". + (merge 8e4309038f jk/hash-object-fsck later to maint). + + * Improve the error message given when private key is not loaded in + the ssh agent in the codepath to sign with an ssh key. + (merge dce7b31126 as/ssh-signing-improve-key-missing-error later to maint). + + * Adjust "git request-pull" to strip embedded signature from signed + tags to notice non-PGP signatures. + (merge a9cad02538 gm/request-pull-with-non-pgp-signed-tags later to maint). + + * Remove support for MSys, which now lags way behind MSys2. + (merge 2987407f3c hj/remove-msys-support later to maint). + + * Fix use of CreateThread() API call made early in the windows + start-up code. + (merge 592bcab61b sk/winansi-createthread-fix later to maint). + + * "git pack-objects" learned to release delta-island bitmap data when + 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). * 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). @@ -177,3 +235,17 @@ Fixes since v2.39 (merge f5156f1885 ar/bisect-doc-update later to maint). (merge fca2d86c97 jk/interop-error later to maint). (merge cf4936ed74 tl/ls-tree-code-clean-up later to maint). + (merge dcb47e52b0 en/t6426-todo-cleanup later to maint). + (merge 5b8db44bdd jc/format-patch-v-unleak later to maint). + (merge 590b636737 jk/hash-object-literally-fd-leak later to maint). + (merge 5458ba0a4d tb/t0003-invoke-dd-more-portably later to maint). + (merge 70661d288b ar/markup-em-dash later to maint). + (merge e750951e74 en/ls-files-doc-update later to maint). + (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). 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-apply.txt b/Documentation/git-apply.txt index 1d478cbe9b..5e16e6db7e 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -208,7 +208,7 @@ behavior: * `warn` outputs warnings for a few such errors, but applies the patch as-is (default). * `fix` outputs warnings for a few such errors, and applies the - patch after fixing them (`strip` is a synonym --- the tool + patch after fixing them (`strip` is a synonym -- the tool used to consider only trailing whitespace characters as errors, and the fix involved 'stripping' them, but modern Gits do more). * `error` outputs warnings for a few such errors, and refuses diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index aa2f78c4c2..d382ac69f7 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -123,6 +123,10 @@ OPTIONS points to a valid commit. In combination with `-m` (or `--move`), allow renaming the branch even if the new branch name already exists, the same applies for `-c` (or `--copy`). ++ +Note that 'git branch -f <branchname> [<start-point>]', even with '-f', +refuses to change an existing branch `<branchname>` that is checked out +in another worktree linked to the same repository. -m:: --move:: diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt index 830f0a2eff..411de2e27d 100644 --- a/Documentation/git-cat-file.txt +++ b/Documentation/git-cat-file.txt @@ -93,47 +93,52 @@ OPTIONS Print object information and contents for each object provided on stdin. May not be combined with any other options or arguments except `--textconv`, `--filters`, or `--use-mailmap`. - + ++ +-- * When used with `--textconv` or `--filters`, the input lines must specify the path, separated by whitespace. See the section `BATCH OUTPUT` below for details. - + + * When used with `--use-mailmap`, for commit and tag objects, the contents part of the output shows the identities replaced using the mailmap mechanism, while the information part of the output shows the size of the object as if it actually recorded the replacement identities. +-- --batch-check:: --batch-check=<format>:: Print object information for each object provided on stdin. May not be combined with any other options or arguments except `--textconv`, `--filters` or `--use-mailmap`. - + ++ +-- * When used with `--textconv` or `--filters`, the input lines must specify the path, separated by whitespace. See the section `BATCH OUTPUT` below for details. - + + * When used with `--use-mailmap`, for commit and tag objects, the printed object information shows the size of the object as if the identities recorded in it were replaced by the mailmap mechanism. +-- --batch-command:: --batch-command=<format>:: Enter a command mode that reads commands and arguments from stdin. May only be combined with `--buffer`, `--textconv`, `--use-mailmap` or `--filters`. - + ++ +-- * When used with `--textconv` or `--filters`, the input lines must specify the path, separated by whitespace. See the section `BATCH OUTPUT` below for details. - + + * When used with `--use-mailmap`, for commit and tag objects, the `contents` command shows the identities replaced using the mailmap mechanism, while the `info` command shows the size of the object as if it actually recorded the replacement identities. - +-- + `--batch-command` recognizes the following commands: + diff --git a/Documentation/git-check-attr.txt b/Documentation/git-check-attr.txt index 84f41a8e82..6e4f3aaf34 100644 --- a/Documentation/git-check-attr.txt +++ b/Documentation/git-check-attr.txt @@ -9,8 +9,8 @@ git-check-attr - Display gitattributes information SYNOPSIS -------- [verse] -'git check-attr' [-a | --all | <attr>...] [--] <pathname>... -'git check-attr' --stdin [-z] [-a | --all | <attr>...] +'git check-attr' [--source <tree-ish>] [-a | --all | <attr>...] [--] <pathname>... +'git check-attr' --stdin [-z] [--source <tree-ish>] [-a | --all | <attr>...] DESCRIPTION ----------- @@ -36,6 +36,11 @@ OPTIONS If `--stdin` is also given, input paths are separated with a NUL character instead of a linefeed character. +--source=<tree-ish>:: + Check attributes against the specified tree-ish. It is common to + specify the source tree by naming a commit, branch or tag associated + with it. + \--:: Interpret all preceding arguments as attributes and all following arguments as path names. diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 9f116acdbd..6bb32ab460 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -146,14 +146,16 @@ on your side branch as `theirs` (i.e. "one contributor's work on top of it"). -b <new-branch>:: - Create a new branch named `<new-branch>` and start it at - `<start-point>`; see linkgit:git-branch[1] for details. + Create a new branch named `<new-branch>`, start it at + `<start-point>`, and check the resulting branch out; + see linkgit:git-branch[1] for details. -B <new-branch>:: - Creates the branch `<new-branch>` and start it at `<start-point>`; - if it already exists, then reset it to `<start-point>`. This is - equivalent to running "git branch" with "-f"; see - linkgit:git-branch[1] for details. + Creates the branch `<new-branch>`, start it at `<start-point>`; + if it already exists, then reset it to `<start-point>`. And then + check the resulting branch out. This is equivalent to running + "git branch" with "-f" followed by "git checkout" of that branch; + see linkgit:git-branch[1] for details. -t:: --track[=(direct|inherit)]:: diff --git a/Documentation/git-credential-cache.txt b/Documentation/git-credential-cache.txt index 432e159d95..f473994a86 100644 --- a/Documentation/git-credential-cache.txt +++ b/Documentation/git-credential-cache.txt @@ -14,10 +14,13 @@ git config credential.helper 'cache [<options>]' DESCRIPTION ----------- -This command caches credentials in memory for use by future Git -programs. The stored credentials never touch the disk, and are forgotten -after a configurable timeout. The cache is accessible over a Unix -domain socket, restricted to the current user by filesystem permissions. +This command caches credentials for use by future Git programs. +The stored credentials are kept in memory of the cache-daemon +process (instead of written to a file) and are forgotten after a +configurable timeout. Credentials are forgotten sooner if the +cache-daemon dies, for example if the system restarts. The cache +is accessible over a Unix domain socket, restricted to the current +user by filesystem permissions. You probably don't want to invoke this command directly; it is meant to be used as a credential helper by other parts of Git. See diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index 440043cdb8..1abdd3c21c 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -12,6 +12,7 @@ SYNOPSIS 'git ls-files' [-z] [-t] [-v] [-f] [-c|--cached] [-d|--deleted] [-o|--others] [-i|--ignored] [-s|--stage] [-u|--unmerged] [-k|--killed] [-m|--modified] + [--resolve-undo] [--directory [--no-empty-directory]] [--eol] [--deduplicate] [-x <pattern>|--exclude=<pattern>] @@ -28,21 +29,26 @@ This merges the file listing in the index with the actual working directory list, and shows different combinations of the two. One or more of the options below may be used to determine the files -shown: +shown, and each file may be printed multiple times if there are +multiple entries in the index or multiple statuses are applicable for +the relevant file selection options. OPTIONS ------- -c:: --cached:: - Show cached files in the output (default) + Show all files cached in Git's index, i.e. all tracked files. + (This is the default if no -c/-s/-d/-o/-u/-k/-m/--resolve-undo + options are specified.) -d:: --deleted:: - Show deleted files in the output + Show files with an unstaged deletion -m:: --modified:: - Show modified files in the output + Show files with an unstaged modification (note that an unstaged + deletion also counts as an unstaged modification) -o:: --others:: @@ -50,11 +56,14 @@ OPTIONS -i:: --ignored:: - Show only ignored files in the output. When showing files in the - index, print only those matched by an exclude pattern. When - showing "other" files, show only those matched by an exclude - pattern. Standard ignore rules are not automatically activated, - therefore at least one of the `--exclude*` options is required. + Show only ignored files in the output. Must be used with + either an explicit '-c' or '-o'. When showing files in the + index (i.e. when used with '-c'), print only those files + matching an exclude pattern. When showing "other" files + (i.e. when used with '-o'), show only those matched by an + exclude pattern. Standard ignore rules are not automatically + activated, therefore at least one of the `--exclude*` options + is required. -s:: --stage:: @@ -63,19 +72,29 @@ OPTIONS --directory:: If a whole directory is classified as "other", show just its name (with a trailing slash) and not its whole contents. + Has no effect without -o/--others. --no-empty-directory:: Do not list empty directories. Has no effect without --directory. -u:: --unmerged:: - Show unmerged files in the output (forces --stage) + Show information about unmerged files in the output, but do + not show any other tracked files (forces --stage, overrides + --cached). -k:: --killed:: - Show files on the filesystem that need to be removed due - to file/directory conflicts for checkout-index to - succeed. + Show untracked files on the filesystem that need to be removed + due to file/directory conflicts for tracked files to be able to + be written to the filesystem. + +--resolve-undo:: + Show files having resolve-undo information in the index + together with their resolve-undo information. (resolve-undo + information is what is used to implement "git checkout -m + $PATH", i.e. to recreate merge conflicts that were + accidentally resolved) -z:: \0 line termination on output and do not quote filenames. @@ -100,7 +119,8 @@ OPTIONS --exclude-per-directory=<file>:: Read additional exclude patterns that apply only to the - directory and its subdirectories in <file>. + directory and its subdirectories in <file>. Deprecated; use + --exclude-standard instead. --exclude-standard:: Add the standard Git exclusions: .git/info/exclude, .gitignore @@ -118,24 +138,27 @@ OPTIONS with `-s` or `-u` options does not make any sense. -t:: - This feature is semi-deprecated. For scripting purpose, - linkgit:git-status[1] `--porcelain` and + Show status tags together with filenames. Note that for + scripting purposes, linkgit:git-status[1] `--porcelain` and linkgit:git-diff-files[1] `--name-status` are almost always superior alternatives, and users should look at linkgit:git-status[1] `--short` or linkgit:git-diff[1] `--name-status` for more user-friendly alternatives. + -- -This option identifies the file status with the following tags (followed by -a space) at the start of each line: - - H:: cached - S:: skip-worktree - M:: unmerged - R:: removed/deleted - C:: modified/changed - K:: to be killed - ?:: other +This option provides a reason for showing each filename, in the form +of a status tag (which is followed by a space and then the filename). +The status tags are all single characters from the following list: + + H:: tracked file that is not either unmerged or skip-worktree + S:: tracked file that is skip-worktree + M:: tracked file that is unmerged + R:: tracked file with unstaged removal/deletion + C:: tracked file with unstaged modification/change + K:: untracked paths which are part of file/directory conflicts + which prevent checking out tracked files + ?:: untracked file + U:: file with resolve-undo information -- -v:: @@ -269,7 +292,9 @@ traversing the directory tree and finding files to show when the flags --others or --ignored are specified. linkgit:gitignore[5] specifies the format of exclude patterns. -These exclude patterns come from these places, in order: +Generally, you should just use --exclude-standard, but for historical +reasons the exclude patterns can be specified from the following +places, in order: 1. The command-line flag --exclude=<pattern> specifies a single pattern. Patterns are ordered in the same order 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/git-read-tree.txt b/Documentation/git-read-tree.txt index 7567955bad..b09707474d 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -219,7 +219,7 @@ see which of the "local changes" that you made were carried forward by running `git diff-index --cached $M`. Note that this does not necessarily match what `git diff-index --cached $H` would have produced before such a two tree merge. This is because of cases -18 and 19 --- if you already had the changes in $M (e.g. maybe +18 and 19 -- if you already had the changes in $M (e.g. maybe you picked it up via e-mail in a patch form), `git diff-index --cached $H` would have told you about the change before this merge, but it would not show in `git diff-index --cached $M` diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index d811c1cf44..9a295bcee4 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -208,6 +208,39 @@ Alternatively, you can undo the 'git rebase' with git rebase --abort +MODE OPTIONS +------------ + +The options in this section cannot be used with any other option, +including not with each other: + +--continue:: + Restart the rebasing process after having resolved a merge conflict. + +--skip:: + Restart the rebasing process by skipping the current patch. + +--abort:: + Abort the rebase operation and reset HEAD to the original + branch. If `<branch>` was provided when the rebase operation was + started, then `HEAD` will be reset to `<branch>`. Otherwise `HEAD` + will be reset to where it was when the rebase operation was + started. + +--quit:: + Abort the rebase operation but `HEAD` is not reset back to the + original branch. The index and working tree are also left + unchanged as a result. If a temporary stash entry was created + using `--autostash`, it will be saved to the stash list. + +--edit-todo:: + Edit the todo list during an interactive rebase. + +--show-current-patch:: + Show the current patch in an interactive rebase or when rebase + is stopped because of conflicts. This is the equivalent of + `git show REBASE_HEAD`. + OPTIONS ------- --onto <newbase>:: @@ -249,22 +282,6 @@ See also INCOMPATIBLE OPTIONS below. <branch>:: Working branch; defaults to `HEAD`. ---continue:: - Restart the rebasing process after having resolved a merge conflict. - ---abort:: - Abort the rebase operation and reset HEAD to the original - branch. If `<branch>` was provided when the rebase operation was - started, then `HEAD` will be reset to `<branch>`. Otherwise `HEAD` - will be reset to where it was when the rebase operation was - started. - ---quit:: - Abort the rebase operation but `HEAD` is not reset back to the - original branch. The index and working tree are also left - unchanged as a result. If a temporary stash entry was created - using `--autostash`, it will be saved to the stash list. - --apply:: Use applying strategies to rebase (calling `git-am` internally). This option may become a no-op in the future @@ -321,7 +338,6 @@ See also INCOMPATIBLE OPTIONS below. upstream changes, the behavior towards them is controlled by the `--empty` flag.) + - In the absence of `--keep-base` (or if `--no-reapply-cherry-picks` is given), these commits will be automatically dropped. Because this necessitates reading all upstream commits, this can be expensive in @@ -330,7 +346,6 @@ read. When using the 'merge' backend, warnings will be issued for each dropped commit (unless `--quiet` is given). Advice will also be issued unless `advice.skippedCherryPicks` is set to false (see linkgit:git-config[1]). - + `--reapply-cherry-picks` allows rebase to forgo reading all upstream commits, potentially improving performance. @@ -345,17 +360,6 @@ See also INCOMPATIBLE OPTIONS below. + See also INCOMPATIBLE OPTIONS below. ---skip:: - Restart the rebasing process by skipping the current patch. - ---edit-todo:: - Edit the todo list during an interactive rebase. - ---show-current-patch:: - Show the current patch in an interactive rebase or when rebase - is stopped because of conflicts. This is the equivalent of - `git show REBASE_HEAD`. - -m:: --merge:: Using merging strategies to rebase (default). @@ -574,10 +578,7 @@ See also INCOMPATIBLE OPTIONS below. --root:: Rebase all commits reachable from `<branch>`, instead of limiting them with an `<upstream>`. This allows you to rebase - the root commit(s) on a branch. When used with `--onto`, it - will skip changes already contained in `<newbase>` (instead of - `<upstream>`) whereas without `--onto` it will operate on every - change. + the root commit(s) on a branch. + See also INCOMPATIBLE OPTIONS below. @@ -630,6 +631,8 @@ start would be overridden by the presence of + If the configuration variable `rebase.updateRefs` is set, then this option can be used to override and disable this setting. ++ +See also INCOMPATIBLE OPTIONS below. INCOMPATIBLE OPTIONS -------------------- @@ -645,17 +648,15 @@ are incompatible with the following options: * --merge * --strategy * --strategy-option - * --allow-empty-message - * --[no-]autosquash + * --autosquash * --rebase-merges * --interactive * --exec * --no-keep-empty * --empty= - * --reapply-cherry-picks - * --edit-todo + * --[no-]reapply-cherry-picks when used without --keep-base * --update-refs - * --root when used in combination with --onto + * --root when used without --onto In addition, the following pairs of options are incompatible: diff --git a/Documentation/git.txt b/Documentation/git.txt index f9a7a4554c..74973d3cc4 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -613,7 +613,7 @@ The file parameters can point at the user's working file (e.g. `new-file` in "git-diff-files"), `/dev/null` (e.g. `old-file` when a new file is added), or a temporary file (e.g. `old-file` in the index). `GIT_EXTERNAL_DIFF` should not worry about unlinking the -temporary file --- it is removed when `GIT_EXTERNAL_DIFF` exits. +temporary file -- it is removed when `GIT_EXTERNAL_DIFF` exits. + For a path that is unmerged, `GIT_EXTERNAL_DIFF` is called with 1 parameter, <path>. diff --git a/Documentation/gitformat-index.txt b/Documentation/gitformat-index.txt index 015cb21bdc..0773e5c380 100644 --- a/Documentation/gitformat-index.txt +++ b/Documentation/gitformat-index.txt @@ -83,11 +83,13 @@ Git index format 32-bit mode, split into (high to low bits) + 16-bit unused, must be zero + 4-bit object type valid values in binary are 1000 (regular file), 1010 (symbolic link) and 1110 (gitlink) - 3-bit unused + 3-bit unused, must be zero 9-bit unix permission. Only 0755 and 0644 are valid for regular files. Symbolic links and gitlinks have value 0 in this field. diff --git a/Documentation/gitformat-signature.txt b/Documentation/gitformat-signature.txt index a249869faf..d8e3eb1bac 100644 --- a/Documentation/gitformat-signature.txt +++ b/Documentation/gitformat-signature.txt @@ -37,7 +37,7 @@ line. This is even true for an originally empty line. In the following examples, the end of line that ends with a whitespace letter is highlighted with a `$` sign; if you are trying to recreate these -example by hand, do not cut and paste them---they are there +example by hand, do not cut and paste them--they are there primarily to highlight extra whitespace at the end of some lines. The signed payload and the way the signature is embedded depends 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/pretty-formats.txt b/Documentation/pretty-formats.txt index 0b4c1c8d98..3b71334459 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -146,24 +146,34 @@ The placeholders are: '%m':: left (`<`), right (`>`) or boundary (`-`) mark '%w([<w>[,<i1>[,<i2>]]])':: switch line wrapping, like the -w option of linkgit:git-shortlog[1]. -'%<(<N>[,trunc|ltrunc|mtrunc])':: make the next placeholder take at - least N columns, padding spaces on +'%<( <N> [,trunc|ltrunc|mtrunc])':: make the next placeholder take at + least N column widths, padding spaces on the right if necessary. Optionally - truncate at the beginning (ltrunc), - the middle (mtrunc) or the end - (trunc) if the output is longer than - N columns. Note that truncating + truncate (with ellipsis '..') at the left (ltrunc) `..ft`, + the middle (mtrunc) `mi..le`, or the end + (trunc) `rig..`, if the output is longer than + N columns. + Note 1: that truncating only works correctly with N >= 2. -'%<|(<N>)':: make the next placeholder take at least until Nth - columns, padding spaces on the right if necessary -'%>(<N>)', '%>|(<N>)':: similar to '%<(<N>)', '%<|(<N>)' respectively, + Note 2: spaces around the N and M (see below) + values are optional. + Note 3: Emojis and other wide characters + will take two display columns, which may + over-run column boundaries. + Note 4: decomposed character combining marks + may be misplaced at padding boundaries. +'%<|( <M> )':: make the next placeholder take at least until Mth + display column, padding spaces on the right if necessary. + Use negative M values for column positions measured + from the right hand edge of the terminal window. +'%>( <N> )', '%>|( <M> )':: similar to '%<( <N> )', '%<|( <M> )' respectively, but padding spaces on the left -'%>>(<N>)', '%>>|(<N>)':: similar to '%>(<N>)', '%>|(<N>)' +'%>>( <N> )', '%>>|( <M> )':: similar to '%>( <N> )', '%>|( <M> )' respectively, except that if the next placeholder takes more spaces than given and there are spaces on its left, use those spaces -'%><(<N>)', '%><|(<N>)':: similar to '%<(<N>)', '%<|(<N>)' +'%><( <N> )', '%><|( <M> )':: similar to '%<( <N> )', '%<|( <M> )' respectively, but padding both sides (i.e. the text is centered) 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/technical/hash-function-transition.txt b/Documentation/technical/hash-function-transition.txt index e2ac36dd21..ed57481089 100644 --- a/Documentation/technical/hash-function-transition.txt +++ b/Documentation/technical/hash-function-transition.txt @@ -562,7 +562,7 @@ hash re-encode during clone and to encourage peers to modernize. The design described here allows fetches by SHA-1 clients of a personal SHA-256 repository because it's not much more difficult than allowing pushes from that repository. This support needs to be guarded -by a configuration option --- servers like git.kernel.org that serve a +by a configuration option -- servers like git.kernel.org that serve a large number of clients would not be expected to bear that cost. Meaning of signatures diff --git a/Documentation/technical/rerere.txt b/Documentation/technical/rerere.txt index 35d4541433..be58f1bee3 100644 --- a/Documentation/technical/rerere.txt +++ b/Documentation/technical/rerere.txt @@ -99,7 +99,7 @@ conflict to leave line D means that the user declares: compatible with what AB and AC wanted to do. So the conflict we would see when merging AB into ACAB should be -resolved the same way---it is the resolution that is in line with that +resolved the same way--it is the resolution that is in line with that declaration. Imagine that similarly previously a branch XYXZ was forked from XY, 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` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -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 @@ -289,6 +289,10 @@ include shared.mak # Define NO_REGEX if your C library lacks regex support with REG_STARTEND # feature. # +# Define USE_ENHANCED_BASIC_REGULAR_EXPRESSIONS if your C library provides +# the flag REG_ENHANCED and you'd like to use it to enable enhanced basic +# regular expressions. +# # Define HAVE_DEV_TTY if your system can open /dev/tty to interact with the # user. # @@ -704,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 @@ -798,6 +801,7 @@ TEST_BUILTINS_OBJS += test-dump-cache-tree.o TEST_BUILTINS_OBJS += test-dump-fsmonitor.o TEST_BUILTINS_OBJS += test-dump-split-index.o TEST_BUILTINS_OBJS += test-dump-untracked-cache.o +TEST_BUILTINS_OBJS += test-env-helper.o TEST_BUILTINS_OBJS += test-example-decorate.o TEST_BUILTINS_OBJS += test-fast-rebase.o TEST_BUILTINS_OBJS += test-fsmonitor-client.o @@ -1231,7 +1235,6 @@ BUILTIN_OBJS += builtin/diff-index.o BUILTIN_OBJS += builtin/diff-tree.o BUILTIN_OBJS += builtin/diff.o BUILTIN_OBJS += builtin/difftool.o -BUILTIN_OBJS += builtin/env--helper.o BUILTIN_OBJS += builtin/fast-export.o BUILTIN_OBJS += builtin/fast-import.o BUILTIN_OBJS += builtin/fetch-pack.o @@ -2037,6 +2040,11 @@ endif ifdef NO_REGEX COMPAT_CFLAGS += -Icompat/regex COMPAT_OBJS += compat/regex/regex.o +else +ifdef USE_ENHANCED_BASIC_REGULAR_EXPRESSIONS + COMPAT_CFLAGS += -DUSE_ENHANCED_BASIC_REGULAR_EXPRESSIONS + COMPAT_OBJS += compat/regcomp_enhanced.o +endif endif ifdef NATIVE_CRLF BASIC_CFLAGS += -DNATIVE_CRLF @@ -4105,7 +4105,7 @@ static int preimage_oid_in_gitlink_patch(struct patch *p, struct object_id *oid) static int build_fake_ancestor(struct apply_state *state, struct patch *list) { struct patch *patch; - struct index_state result = { NULL }; + struct index_state result = INDEX_STATE_INIT(state->repo); struct lock_file lock = LOCK_INIT; int res; @@ -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; @@ -120,7 +120,7 @@ static const struct attr_check *get_archive_attrs(struct index_state *istate, static struct attr_check *check; if (!check) check = attr_check_initl("export-ignore", "export-subst", NULL); - git_check_attr(istate, path, check); + git_check_attr(istate, NULL, path, check); return check; } @@ -710,6 +710,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; } @@ -13,6 +13,8 @@ #include "dir.h" #include "utf8.h" #include "quote.h" +#include "revision.h" +#include "object-store.h" #include "thread-utils.h" const char git_attr__true[] = "(builtin)true"; @@ -744,13 +746,61 @@ static struct attr_stack *read_attr_from_file(const char *path, unsigned flags) return res; } -static struct attr_stack *read_attr_from_index(struct index_state *istate, - const char *path, - unsigned flags) +static struct attr_stack *read_attr_from_buf(char *buf, const char *path, + unsigned flags) { struct attr_stack *res; - char *buf, *sp; + char *sp; int lineno = 0; + + if (!buf) + return NULL; + + CALLOC_ARRAY(res, 1); + for (sp = buf; *sp;) { + char *ep; + int more; + + ep = strchrnul(sp, '\n'); + more = (*ep == '\n'); + *ep = '\0'; + handle_attr_line(res, sp, path, ++lineno, flags); + sp = ep + more; + } + free(buf); + + return res; +} + +static struct attr_stack *read_attr_from_blob(struct index_state *istate, + const struct object_id *tree_oid, + const char *path, unsigned flags) +{ + struct object_id oid; + unsigned long sz; + enum object_type type; + void *buf; + unsigned short mode; + + if (!tree_oid) + return NULL; + + if (get_tree_entry(istate->repo, tree_oid, path, &oid, &mode)) + return NULL; + + buf = repo_read_object_file(istate->repo, &oid, &type, &sz); + if (!buf || type != OBJ_BLOB) { + free(buf); + return NULL; + } + + return read_attr_from_buf(buf, path, flags); +} + +static struct attr_stack *read_attr_from_index(struct index_state *istate, + const char *path, unsigned flags) +{ + char *buf; unsigned long size; if (!istate) @@ -778,28 +828,19 @@ static struct attr_stack *read_attr_from_index(struct index_state *istate, return NULL; } - CALLOC_ARRAY(res, 1); - for (sp = buf; *sp; ) { - char *ep; - int more; - - ep = strchrnul(sp, '\n'); - more = (*ep == '\n'); - *ep = '\0'; - handle_attr_line(res, sp, path, ++lineno, flags); - sp = ep + more; - } - free(buf); - return res; + return read_attr_from_buf(buf, path, flags); } static struct attr_stack *read_attr(struct index_state *istate, + const struct object_id *tree_oid, const char *path, unsigned flags) { struct attr_stack *res = NULL; if (direction == GIT_ATTR_INDEX) { res = read_attr_from_index(istate, path, flags); + } else if (tree_oid) { + res = read_attr_from_blob(istate, tree_oid, path, flags); } else if (!is_bare_repository()) { if (direction == GIT_ATTR_CHECKOUT) { res = read_attr_from_index(istate, path, flags); @@ -859,6 +900,7 @@ static void push_stack(struct attr_stack **attr_stack_p, } static void bootstrap_attr_stack(struct index_state *istate, + const struct object_id *tree_oid, struct attr_stack **stack) { struct attr_stack *e; @@ -884,7 +926,7 @@ static void bootstrap_attr_stack(struct index_state *istate, } /* root directory */ - e = read_attr(istate, GITATTRIBUTES_FILE, flags | READ_ATTR_NOFOLLOW); + e = read_attr(istate, tree_oid, GITATTRIBUTES_FILE, flags | READ_ATTR_NOFOLLOW); push_stack(stack, e, xstrdup(""), 0); /* info frame */ @@ -898,6 +940,7 @@ static void bootstrap_attr_stack(struct index_state *istate, } static void prepare_attr_stack(struct index_state *istate, + const struct object_id *tree_oid, const char *path, int dirlen, struct attr_stack **stack) { @@ -919,7 +962,7 @@ static void prepare_attr_stack(struct index_state *istate, * .gitattributes in deeper directories to shallower ones, * and finally use the built-in set as the default. */ - bootstrap_attr_stack(istate, stack); + bootstrap_attr_stack(istate, tree_oid, stack); /* * Pop the "info" one that is always at the top of the stack. @@ -974,7 +1017,7 @@ static void prepare_attr_stack(struct index_state *istate, strbuf_add(&pathbuf, path + pathbuf.len, (len - pathbuf.len)); strbuf_addf(&pathbuf, "/%s", GITATTRIBUTES_FILE); - next = read_attr(istate, pathbuf.buf, READ_ATTR_NOFOLLOW); + next = read_attr(istate, tree_oid, pathbuf.buf, READ_ATTR_NOFOLLOW); /* reset the pathbuf to not include "/.gitattributes" */ strbuf_setlen(&pathbuf, len); @@ -1094,8 +1137,8 @@ static void determine_macros(struct all_attrs_item *all_attrs, * Otherwise all attributes are collected. */ static void collect_some_attrs(struct index_state *istate, - const char *path, - struct attr_check *check) + const struct object_id *tree_oid, + const char *path, struct attr_check *check) { int pathlen, rem, dirlen; const char *cp, *last_slash = NULL; @@ -1114,7 +1157,7 @@ static void collect_some_attrs(struct index_state *istate, dirlen = 0; } - prepare_attr_stack(istate, path, dirlen, &check->stack); + prepare_attr_stack(istate, tree_oid, path, dirlen, &check->stack); all_attrs_init(&g_attr_hashmap, check); determine_macros(check->all_attrs, check->stack); @@ -1123,12 +1166,12 @@ static void collect_some_attrs(struct index_state *istate, } void git_check_attr(struct index_state *istate, - const char *path, + const struct object_id *tree_oid, const char *path, struct attr_check *check) { int i; - collect_some_attrs(istate, path, check); + collect_some_attrs(istate, tree_oid, path, check); for (i = 0; i < check->nr; i++) { unsigned int n = check->items[i].attr->attr_nr; @@ -1139,13 +1182,13 @@ void git_check_attr(struct index_state *istate, } } -void git_all_attrs(struct index_state *istate, +void git_all_attrs(struct index_state *istate, const struct object_id *tree_oid, const char *path, struct attr_check *check) { int i; attr_check_reset(check); - collect_some_attrs(istate, path, check); + collect_some_attrs(istate, tree_oid, path, check); for (i = 0; i < check->all_attrs_nr; i++) { const char *name = check->all_attrs[i].attr->name; @@ -45,7 +45,7 @@ * const char *path; * * setup_check(); - * git_check_attr(path, check); + * git_check_attr(&the_index, tree_oid, path, check); * ------------ * * - Act on `.value` member of the result, left in `check->items[]`: @@ -120,6 +120,7 @@ #define ATTR_MAX_FILE_SIZE (100 * 1024 * 1024) struct index_state; +struct object_id; /** * An attribute is an opaque object that is identified by its name. Pass the @@ -202,13 +203,14 @@ void attr_check_free(struct attr_check *check); const char *git_attr_name(const struct git_attr *); void git_check_attr(struct index_state *istate, - const char *path, struct attr_check *check); + const struct object_id *tree_oid, const char *path, + struct attr_check *check); /* * Retrieve all attributes that apply to the specified path. * check holds the attributes and their values. */ -void git_all_attrs(struct index_state *istate, +void git_all_attrs(struct index_state *istate, const struct object_id *tree_oid, const char *path, struct attr_check *check); enum git_attr_direction { @@ -756,7 +756,7 @@ void create_branches_recursively(struct repository *r, const char *name, _("submodule '%s': unable to find submodule"), submodule_entry_list.entries[i].submodule->name); if (advice_enabled(ADVICE_SUBMODULES_NOT_UPDATED)) - advise(_("You may try updating the submodules using 'git checkout %s && git submodule update --init'"), + advise(_("You may try updating the submodules using 'git checkout --no-recurse-submodules %s && git submodule update --init'"), start_commitish); exit(code); } 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 8be91617fe..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" @@ -1643,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)) { @@ -2051,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/check-attr.c b/builtin/check-attr.c index 0fef10eb6b..d7a40e674c 100644 --- a/builtin/check-attr.c +++ b/builtin/check-attr.c @@ -9,9 +9,10 @@ static int all_attrs; static int cached_attrs; static int stdin_paths; +static char *source; static const char * const check_attr_usage[] = { -N_("git check-attr [-a | --all | <attr>...] [--] <pathname>..."), -N_("git check-attr --stdin [-z] [-a | --all | <attr>...]"), +N_("git check-attr [--source <tree-ish>] [-a | --all | <attr>...] [--] <pathname>..."), +N_("git check-attr --stdin [-z] [--source <tree-ish>] [-a | --all | <attr>...]"), NULL }; @@ -23,6 +24,7 @@ static const struct option check_attr_options[] = { OPT_BOOL(0 , "stdin", &stdin_paths, N_("read file names from stdin")), OPT_BOOL('z', NULL, &nul_term_line, N_("terminate input and output records by a NUL character")), + OPT_STRING(0, "source", &source, N_("<tree-ish>"), N_("which tree-ish to check attributes at")), OPT_END() }; @@ -55,27 +57,26 @@ static void output_attr(struct attr_check *check, const char *file) } } -static void check_attr(const char *prefix, - struct attr_check *check, - int collect_all, +static void check_attr(const char *prefix, struct attr_check *check, + const struct object_id *tree_oid, int collect_all, const char *file) + { char *full_path = prefix_path(prefix, prefix ? strlen(prefix) : 0, file); if (collect_all) { - git_all_attrs(&the_index, full_path, check); + git_all_attrs(&the_index, tree_oid, full_path, check); } else { - git_check_attr(&the_index, full_path, check); + git_check_attr(&the_index, tree_oid, full_path, check); } output_attr(check, file); free(full_path); } -static void check_attr_stdin_paths(const char *prefix, - struct attr_check *check, - int collect_all) +static void check_attr_stdin_paths(const char *prefix, struct attr_check *check, + const struct object_id *tree_oid, int collect_all) { struct strbuf buf = STRBUF_INIT; struct strbuf unquoted = STRBUF_INIT; @@ -89,7 +90,7 @@ static void check_attr_stdin_paths(const char *prefix, die("line is badly quoted"); strbuf_swap(&buf, &unquoted); } - check_attr(prefix, check, collect_all, buf.buf); + check_attr(prefix, check, tree_oid, collect_all, buf.buf); maybe_flush_or_die(stdout, "attribute to stdout"); } strbuf_release(&buf); @@ -105,6 +106,8 @@ static NORETURN void error_with_usage(const char *msg) int cmd_check_attr(int argc, const char **argv, const char *prefix) { struct attr_check *check; + struct object_id *tree_oid = NULL; + struct object_id initialized_oid; int cnt, i, doubledash, filei; if (!is_bare_repository()) @@ -176,11 +179,17 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix) } } + if (source) { + if (repo_get_oid_tree(the_repository, source, &initialized_oid)) + die("%s: not a valid tree-ish source", source); + tree_oid = &initialized_oid; + } + if (stdin_paths) - check_attr_stdin_paths(prefix, check, all_attrs); + check_attr_stdin_paths(prefix, check, tree_oid, all_attrs); else { for (i = filei; i < argc; i++) - check_attr(prefix, check, all_attrs, argv[i]); + check_attr(prefix, check, tree_oid, all_attrs, argv[i]); maybe_flush_or_die(stdout, "attribute to stdout"); } 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/difftool.c b/builtin/difftool.c index d9b76226f6..dbbfb19f19 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -361,7 +361,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, struct hashmap symlinks2 = HASHMAP_INIT(pair_cmp, NULL); struct hashmap_iter iter; struct pair_entry *entry; - struct index_state wtindex; + struct index_state wtindex = INDEX_STATE_INIT(the_repository); struct checkout lstate, rstate; int err = 0; struct child_process cmd = CHILD_PROCESS_INIT; @@ -387,8 +387,6 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, mkdir(ldir.buf, 0700); mkdir(rdir.buf, 0700); - memset(&wtindex, 0, sizeof(wtindex)); - memset(&lstate, 0, sizeof(lstate)); lstate.base_dir = lbase_dir = xstrdup(ldir.buf); lstate.base_dir_len = ldir.len; diff --git a/builtin/fetch.c b/builtin/fetch.c index 7378cafeec..a21ce89312 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,10 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (dry_run) write_fetch_head = 0; + 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")); @@ -2228,6 +2234,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) argv++; } } + string_list_remove_duplicates(&list, 0); if (negotiate_only) { struct oidset acked_commits = OIDSET_INIT; diff --git a/builtin/hash-object.c b/builtin/hash-object.c index b506381502..44db83f07f 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -27,6 +27,7 @@ static int hash_literally(struct object_id *oid, int fd, const char *type, unsig else ret = write_object_file_literally(buf.buf, buf.len, type, oid, flags); + close(fd); strbuf_release(&buf); return ret; } diff --git a/builtin/log.c b/builtin/log.c index 057e299c24..04412dd9c9 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -1875,6 +1875,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) struct strbuf rdiff1 = STRBUF_INIT; struct strbuf rdiff2 = STRBUF_INIT; struct strbuf rdiff_title = STRBUF_INIT; + struct strbuf sprefix = STRBUF_INIT; int creation_factor = -1; const struct option builtin_format_patch_options[] = { @@ -2015,12 +2016,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) cover_from_description_mode = parse_cover_from_description(cover_from_description_arg); if (reroll_count) { - struct strbuf sprefix = STRBUF_INIT; - strbuf_addf(&sprefix, "%s v%s", rev.subject_prefix, reroll_count); rev.reroll_count = reroll_count; - rev.subject_prefix = strbuf_detach(&sprefix, NULL); + rev.subject_prefix = sprefix.buf; } for (i = 0; i < extra_hdr.nr; i++) { @@ -2384,6 +2383,7 @@ done: strbuf_release(&rdiff1); strbuf_release(&rdiff2); strbuf_release(&rdiff_title); + strbuf_release(&sprefix); free(to_free); if (rev.ref_message_ids) string_list_clear(rev.ref_message_ids, 0); 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/ls-tree.c b/builtin/ls-tree.c index 72eb70823d..8cc8c995df 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -94,14 +94,14 @@ static size_t expand_show_tree(struct strbuf *sb, const char *start, } else if (skip_prefix(start, "(path)", &p)) { const char *name = data->base->buf; const char *prefix = options->chomp_prefix ? options->ls_tree_prefix : NULL; - struct strbuf quoted = STRBUF_INIT; struct strbuf sbuf = STRBUF_INIT; + size_t baselen = data->base->len; + strbuf_addstr(data->base, data->pathname); name = relative_path(data->base->buf, prefix, &sbuf); - quote_c_style(name, "ed, NULL, 0); - strbuf_addbuf(sb, "ed); + quote_c_style(name, sb, NULL, 0); + strbuf_setlen(data->base, baselen); strbuf_release(&sbuf); - strbuf_release("ed); } else { errlen = (unsigned long)len; die(_("bad ls-tree format: %%%.*s"), errlen, start); @@ -144,7 +144,6 @@ static int show_tree_fmt(const struct object_id *oid, struct strbuf *base, const char *pathname, unsigned mode, void *context) { struct ls_tree_options *options = context; - size_t baselen; int recurse = 0; struct strbuf sb = STRBUF_INIT; enum object_type type = object_type(mode); @@ -164,12 +163,10 @@ static int show_tree_fmt(const struct object_id *oid, struct strbuf *base, if (type == OBJ_BLOB && (options->ls_options & LS_TREE_ONLY)) return 0; - baselen = base->len; strbuf_expand(&sb, options->format, expand_show_tree, &cb_data); strbuf_addch(&sb, options->null_termination ? '\0' : '\n'); fwrite(sb.buf, sb.len, 1, stdout); strbuf_release(&sb); - strbuf_setlen(base, baselen); return recurse; } 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 2193f80b89..74a167a180 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -929,8 +929,10 @@ static struct object_entry **compute_write_order(void) */ for_each_tag_ref(mark_tagged, NULL); - if (use_delta_islands) + if (use_delta_islands) { max_layers = compute_pack_layers(&to_pack); + free_island_marks(); + } ALLOC_ARRAY(wo, to_pack.nr_objects); wo_end = 0; @@ -1318,7 +1320,7 @@ static int no_try_delta(const char *path) if (!check) check = attr_check_initl("delta", NULL); - git_check_attr(the_repository->index, path, check); + git_check_attr(the_repository->index, NULL, path, check); if (ATTR_FALSE(check->items[0].value)) return 1; return 0; @@ -1708,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; @@ -1743,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) @@ -1795,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 a26cc0cfdb..6635f10d52 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -122,6 +122,8 @@ struct rebase_options { int reapply_cherry_picks; int fork_point; int update_refs; + int config_autosquash; + int config_update_refs; }; #define REBASE_OPTIONS_INIT { \ @@ -134,6 +136,12 @@ struct rebase_options { .exec = STRING_LIST_INIT_NODUP, \ .git_format_patch_opt = STRBUF_INIT, \ .fork_point = -1, \ + .reapply_cherry_picks = -1, \ + .allow_empty_message = 1, \ + .autosquash = -1, \ + .config_autosquash = -1, \ + .update_refs = -1, \ + .config_update_refs = -1, \ } static struct replay_opts get_replay_opts(const struct rebase_options *opts) @@ -246,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; @@ -254,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", @@ -292,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); @@ -333,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: @@ -548,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)) @@ -776,7 +784,7 @@ static int rebase_config(const char *var, const char *value, void *data) } if (!strcmp(var, "rebase.autosquash")) { - opts->autosquash = git_config_bool(var, value); + opts->config_autosquash = git_config_bool(var, value); return 0; } @@ -793,7 +801,7 @@ static int rebase_config(const char *var, const char *value, void *data) } if (!strcmp(var, "rebase.updaterefs")) { - opts->update_refs = git_config_bool(var, value); + opts->config_update_refs = git_config_bool(var, value); return 0; } @@ -907,6 +915,9 @@ static int parse_opt_am(const struct option *opt, const char *arg, int unset) BUG_ON_OPT_NEG(unset); BUG_ON_OPT_ARG(arg); + if (opts->type != REBASE_UNSPECIFIED && opts->type != REBASE_APPLY) + die(_("apply options and merge options cannot be used together")); + opts->type = REBASE_APPLY; return 0; @@ -920,8 +931,10 @@ static int parse_opt_merge(const struct option *opt, const char *arg, int unset) BUG_ON_OPT_NEG(unset); BUG_ON_OPT_ARG(arg); - if (!is_merge(opts)) - opts->type = REBASE_MERGE; + if (opts->type != REBASE_UNSPECIFIED && opts->type != REBASE_MERGE) + die(_("apply options and merge options cannot be used together")); + + opts->type = REBASE_MERGE; return 0; } @@ -935,6 +948,9 @@ static int parse_opt_interactive(const struct option *opt, const char *arg, BUG_ON_OPT_NEG(unset); BUG_ON_OPT_ARG(arg); + if (opts->type != REBASE_UNSPECIFIED && opts->type != REBASE_MERGE) + die(_("apply options and merge options cannot be used together")); + opts->type = REBASE_MERGE; opts->flags |= REBASE_INTERACTIVE_EXPLICIT; @@ -1023,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; @@ -1150,8 +1167,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) prepare_repo_settings(the_repository); the_repository->settings.command_requires_full_index = 0; - options.reapply_cherry_picks = -1; - options.allow_empty_message = 1; git_config(rebase_config, &options); /* options.gpg_sign_opt will be either "-S" or NULL */ gpg_sign = options.gpg_sign_opt ? "" : NULL; @@ -1216,13 +1231,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (options.fork_point < 0) options.fork_point = 0; } - /* - * --keep-base defaults to --reapply-cherry-picks to avoid losing - * commits when using this option. - */ - if (options.reapply_cherry_picks < 0) - options.reapply_cherry_picks = keep_base; - if (options.root && options.fork_point > 0) die(_("options '%s' and '%s' cannot be used together"), "--root", "--fork-point"); @@ -1320,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); @@ -1365,7 +1374,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if ((options.flags & REBASE_INTERACTIVE_EXPLICIT) || (options.action != ACTION_NONE) || (options.exec.nr > 0) || - options.autosquash) { + (options.autosquash == -1 && options.config_autosquash == 1) || + options.autosquash == 1) { allow_preemptive_ff = 0; } if (options.committer_date_is_author_date || options.ignore_date) @@ -1398,12 +1408,27 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (options.empty != EMPTY_UNSPECIFIED) imply_merge(&options, "--empty"); - /* - * --keep-base implements --reapply-cherry-picks by altering upstream so - * it works with both backends. - */ - if (options.reapply_cherry_picks && !keep_base) - imply_merge(&options, "--reapply-cherry-picks"); + if (options.reapply_cherry_picks < 0) + /* + * We default to --no-reapply-cherry-picks unless + * --keep-base is given; when --keep-base is given, we want + * to default to --reapply-cherry-picks. + */ + options.reapply_cherry_picks = keep_base; + else if (!keep_base) + /* + * The apply backend always searches for and drops cherry + * picks. This is often not wanted with --keep-base, so + * --keep-base allows --reapply-cherry-picks to be + * simulated by altering the upstream such that + * cherry-picks cannot be detected and thus all commits are + * reapplied. Thus, --[no-]reapply-cherry-picks is + * supported when --keep-base is specified, but not when + * --keep-base is left out. + */ + imply_merge(&options, options.reapply_cherry_picks ? + "--reapply-cherry-picks" : + "--no-reapply-cherry-picks"); if (gpg_sign) options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign); @@ -1483,15 +1508,29 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (strcmp(options.git_am_opts.v[i], "-q")) break; - if (i >= 0) { + if (i >= 0 || options.type == REBASE_APPLY) { if (is_merge(&options)) die(_("apply options and merge options " "cannot be used together")); + else if (options.autosquash == -1 && options.config_autosquash == 1) + die(_("apply options are incompatible with rebase.autosquash. Consider adding --no-autosquash")); + else if (options.update_refs == -1 && options.config_update_refs == 1) + die(_("apply options are incompatible with rebase.updateRefs. Consider adding --no-update-refs")); else options.type = REBASE_APPLY; } } + if (options.update_refs == 1) + imply_merge(&options, "--update-refs"); + options.update_refs = (options.update_refs >= 0) ? options.update_refs : + ((options.config_update_refs >= 0) ? options.config_update_refs : 0); + + if (options.autosquash == 1) + imply_merge(&options, "--autosquash"); + options.autosquash = (options.autosquash >= 0) ? options.autosquash : + ((options.config_autosquash >= 0) ? options.config_autosquash : 0); + if (options.type == REBASE_UNSPECIFIED) { if (!strcmp(options.default_backend, "merge")) imply_merge(&options, "--merge"); @@ -1637,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, "...")) { @@ -1811,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/sparse-checkout.c b/builtin/sparse-checkout.c index 58a22503f0..c373815491 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -217,6 +217,7 @@ static int update_working_directory(struct pattern_list *pl) o.head_idx = -1; o.src_index = r->index; o.dst_index = r->index; + index_state_init(&o.result, r); o.skip_sparse_checkout = 0; o.pl = pl; diff --git a/builtin/stash.c b/builtin/stash.c index bb0fd86143..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(); @@ -1137,7 +1140,7 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg, int ret = 0; struct strbuf untracked_msg = STRBUF_INIT; struct child_process cp_upd_index = CHILD_PROCESS_INIT; - struct index_state istate = { NULL }; + struct index_state istate = INDEX_STATE_INIT(the_repository); cp_upd_index.git_cmd = 1; strvec_pushl(&cp_upd_index.args, "update-index", "-z", "--add", @@ -1165,7 +1168,7 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg, } done: - discard_index(&istate); + release_index(&istate); strbuf_release(&untracked_msg); remove_path(stash_index_path.buf); return ret; @@ -1176,7 +1179,7 @@ static int stash_staged(struct stash_info *info, struct strbuf *out_patch, { int ret = 0; struct child_process cp_diff_tree = CHILD_PROCESS_INIT; - struct index_state istate = { NULL }; + struct index_state istate = INDEX_STATE_INIT(the_repository); if (write_index_as_tree(&info->w_tree, &istate, the_repository->index_file, 0, NULL)) { @@ -1199,7 +1202,7 @@ static int stash_staged(struct stash_info *info, struct strbuf *out_patch, } done: - discard_index(&istate); + release_index(&istate); return ret; } @@ -1209,7 +1212,7 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps, int ret = 0; struct child_process cp_read_tree = CHILD_PROCESS_INIT; struct child_process cp_diff_tree = CHILD_PROCESS_INIT; - struct index_state istate = { NULL }; + struct index_state istate = INDEX_STATE_INIT(the_repository); char *old_index_env = NULL, *old_repo_index_file; remove_path(stash_index_path.buf); @@ -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) @@ -1260,7 +1263,7 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps, } done: - discard_index(&istate); + release_index(&istate); remove_path(stash_index_path.buf); return ret; } @@ -1271,7 +1274,7 @@ static int stash_working_tree(struct stash_info *info, const struct pathspec *ps struct rev_info rev; struct child_process cp_upd_index = CHILD_PROCESS_INIT; struct strbuf diff_output = STRBUF_INIT; - struct index_state istate = { NULL }; + struct index_state istate = INDEX_STATE_INIT(the_repository); init_revisions(&rev, NULL); copy_pathspec(&rev.prune_data, ps); @@ -1319,7 +1322,7 @@ static int stash_working_tree(struct stash_info *info, const struct pathspec *ps } done: - discard_index(&istate); + release_index(&istate); release_revisions(&rev); strbuf_release(&diff_output); remove_path(stash_index_path.buf); @@ -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 591d659fae..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); @@ -923,7 +923,7 @@ static int unlock_worktree(int ac, const char **av, const char *prefix) static void validate_no_submodules(const struct worktree *wt) { - struct index_state istate = { NULL }; + struct index_state istate = INDEX_STATE_INIT(the_repository); struct strbuf path = STRBUF_INIT; int i, found_submodules = 0; 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 @@ -360,6 +360,22 @@ struct index_state { struct pattern_list *sparse_checkout_patterns; }; +/** + * A "struct index_state istate" must be initialized with + * INDEX_STATE_INIT or the corresponding index_state_init(). + * + * If the variable won't be used again, use release_index() to free() + * its resources. If it needs to be used again use discard_index(), + * which does the same thing, but will use use index_state_init() at + * the end. The discard_index() will use its own "istate->repo" as the + * "r" argument to index_state_init() in that case. + */ +#define INDEX_STATE_INIT(r) { \ + .repo = (r), \ +} +void index_state_init(struct index_state *istate, struct repository *r); +void release_index(struct index_state *istate); + /* Name hashing */ int test_lazy_init_name_hash(struct index_state *istate, int try_threaded); void add_name_hash(struct index_state *istate, struct cache_entry *ce); @@ -433,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 @@ -1607,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; diff --git a/compat/regcomp_enhanced.c b/compat/regcomp_enhanced.c new file mode 100644 index 0000000000..84193ce53b --- /dev/null +++ b/compat/regcomp_enhanced.c @@ -0,0 +1,9 @@ +#include "../git-compat-util.h" +#undef regcomp + +int git_regcomp(regex_t *preg, const char *pattern, int cflags) +{ + if (!(cflags & REG_EXTENDED)) + cflags |= REG_ENHANCED; + return regcomp(preg, pattern, cflags); +} diff --git a/compat/win32/pthread.c b/compat/win32/pthread.c index 2e7eead42c..85f8f7920c 100644 --- a/compat/win32/pthread.c +++ b/compat/win32/pthread.c @@ -22,12 +22,12 @@ static unsigned __stdcall win32_start_routine(void *arg) } int pthread_create(pthread_t *thread, const void *unused, - void *(*start_routine)(void*), void *arg) + void *(*start_routine)(void *), void *arg) { thread->arg = arg; thread->start_routine = start_routine; - thread->handle = (HANDLE) - _beginthreadex(NULL, 0, win32_start_routine, thread, 0, NULL); + thread->handle = (HANDLE)_beginthreadex(NULL, 0, win32_start_routine, + thread, 0, NULL); if (!thread->handle) return errno; @@ -39,14 +39,17 @@ int win32_pthread_join(pthread_t *thread, void **value_ptr) { DWORD result = WaitForSingleObject(thread->handle, INFINITE); switch (result) { - case WAIT_OBJECT_0: - if (value_ptr) - *value_ptr = thread->arg; - return 0; - case WAIT_ABANDONED: - return EINVAL; - default: - return err_win_to_posix(GetLastError()); + case WAIT_OBJECT_0: + if (value_ptr) + *value_ptr = thread->arg; + CloseHandle(thread->handle); + return 0; + case WAIT_ABANDONED: + CloseHandle(thread->handle); + return EINVAL; + default: + /* the wait failed, so do not detach */ + return err_win_to_posix(GetLastError()); } } diff --git a/compat/winansi.c b/compat/winansi.c index 3abe8dd5a2..f83610f684 100644 --- a/compat/winansi.c +++ b/compat/winansi.c @@ -644,7 +644,7 @@ void winansi_init(void) /* start console spool thread on the pipe's read end */ hthread = CreateThread(NULL, 0, console_thread, NULL, 0, NULL); - if (hthread == INVALID_HANDLE_VALUE) + if (!hthread) die_lasterr("CreateThread(console_thread) failed"); /* schedule cleanup routine */ @@ -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/config.mak.uname b/config.mak.uname index d63629fe80..64c44db805 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -147,6 +147,7 @@ ifeq ($(uname_S),Darwin) FREAD_READS_DIRECTORIES = UnfortunatelyYes HAVE_NS_GET_EXECUTABLE_PATH = YesPlease CSPRNG_METHOD = arc4random + USE_ENHANCED_BASIC_REGULAR_EXPRESSIONS = YesPlease # Workaround for `gettext` being keg-only and not even being linked via # `brew link --force gettext`, should be obsolete as of @@ -623,6 +624,9 @@ ifeq ($(uname_S),NONSTOP_KERNEL) SHELL_PATH = /usr/coreutils/bin/bash endif ifeq ($(uname_S),MINGW) + ifeq ($(shell expr "$(uname_R)" : '1\.'),2) + $(error "Building with MSys is no longer supported") + endif pathsep = ; HAVE_ALLOCA_H = YesPlease NO_PREAD = YesPlease @@ -652,7 +656,6 @@ ifeq ($(uname_S),MINGW) USE_WIN32_IPC = YesPlease USE_WIN32_MMAP = YesPlease MMAP_PREVENTS_DELETE = UnfortunatelyYes - USE_NED_ALLOCATOR = YesPlease UNRELIABLE_FSTAT = UnfortunatelyYes OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo NO_REGEX = YesPlease @@ -677,61 +680,43 @@ ifeq ($(uname_S),MINGW) RC = windres -O coff NATIVE_CRLF = YesPlease X = .exe -ifneq (,$(wildcard ../THIS_IS_MSYSGIT)) - htmldir = doc/git/html/ - prefix = + # MSys2 + prefix = /usr/ + # Enable DEP + BASIC_LDFLAGS += -Wl,--nxcompat + # Enable ASLR (unless debugging) + ifneq (,$(findstring -O,$(filter-out -O0 -Og,$(CFLAGS)))) + BASIC_LDFLAGS += -Wl,--dynamicbase + endif + ifeq (MINGW32,$(MSYSTEM)) + prefix = /mingw32 + HOST_CPU = i686 + BASIC_LDFLAGS += -Wl,--pic-executable,-e,_mainCRTStartup + endif + ifeq (MINGW64,$(MSYSTEM)) + prefix = /mingw64 + HOST_CPU = x86_64 + BASIC_LDFLAGS += -Wl,--pic-executable,-e,mainCRTStartup + else + COMPAT_CFLAGS += -D_USE_32BIT_TIME_T + BASIC_LDFLAGS += -Wl,--large-address-aware + endif + CC = gcc + COMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO=0 -DDETECT_MSYS_TTY \ + -fstack-protector-strong + EXTLIBS += -lntdll INSTALL = /bin/install - EXTLIBS += /mingw/lib/libz.a INTERNAL_QSORT = YesPlease HAVE_LIBCHARSET_H = YesPlease - NO_GETTEXT = YesPlease - NO_PYTHON = YesPlease - COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -else - ifneq ($(shell expr "$(uname_R)" : '1\.'),2) - # MSys2 - prefix = /usr/ - # Enable DEP - BASIC_LDFLAGS += -Wl,--nxcompat - # Enable ASLR (unless debugging) - ifneq (,$(findstring -O,$(filter-out -O0 -Og,$(CFLAGS)))) - BASIC_LDFLAGS += -Wl,--dynamicbase - endif - ifeq (MINGW32,$(MSYSTEM)) - prefix = /mingw32 - HOST_CPU = i686 - BASIC_LDFLAGS += -Wl,--pic-executable,-e,_mainCRTStartup - endif - ifeq (MINGW64,$(MSYSTEM)) - prefix = /mingw64 - HOST_CPU = x86_64 - BASIC_LDFLAGS += -Wl,--pic-executable,-e,mainCRTStartup - else - COMPAT_CFLAGS += -D_USE_32BIT_TIME_T - BASIC_LDFLAGS += -Wl,--large-address-aware - endif - CC = gcc - COMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO=0 -DDETECT_MSYS_TTY \ - -fstack-protector-strong - EXTLIBS += -lntdll - INSTALL = /bin/install - INTERNAL_QSORT = YesPlease - HAVE_LIBCHARSET_H = YesPlease - USE_GETTEXT_SCHEME = fallthrough - USE_LIBPCRE = YesPlease - USE_NED_ALLOCATOR = YesPlease - ifeq (/mingw64,$(subst 32,64,$(prefix))) - # Move system config into top-level /etc/ - ETC_GITCONFIG = ../etc/gitconfig - ETC_GITATTRIBUTES = ../etc/gitattributes - endif - else - COMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO - NO_CURL = YesPlease - NO_PYTHON = YesPlease + USE_GETTEXT_SCHEME = fallthrough + USE_LIBPCRE = YesPlease + USE_NED_ALLOCATOR = YesPlease + ifeq (/mingw64,$(subst 32,64,$(prefix))) + # Move system config into top-level /etc/ + ETC_GITCONFIG = ../etc/gitconfig + ETC_GITATTRIBUTES = ../etc/gitattributes endif endif -endif ifeq ($(uname_S),QNX) COMPAT_CFLAGS += -DSA_RESTART=0 EXPAT_NEEDS_XMLPARSE_H = YesPlease 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, - ...) @@ -1308,7 +1308,7 @@ void convert_attrs(struct index_state *istate, git_config(read_convert_config, NULL); } - git_check_attr(istate, path, check); + git_check_attr(istate, NULL, path, check); ccheck = check->items; ca->crlf_action = git_path_check_crlf(ccheck + 4); if (ca->crlf_action == CRLF_UNDEFINED) diff --git a/delta-islands.c b/delta-islands.c index 90c0d6958f..8b234cb85b 100644 --- a/delta-islands.c +++ b/delta-islands.c @@ -513,6 +513,20 @@ void propagate_island_marks(struct commit *commit) } } +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); + + /* detect use-after-free with a an address which is never valid: */ + island_marks = (void *)-1; +} + int compute_pack_layers(struct packing_data *to_pack) { uint32_t i; diff --git a/delta-islands.h b/delta-islands.h index eb0f952629..8d1591ae28 100644 --- a/delta-islands.h +++ b/delta-islands.h @@ -14,5 +14,6 @@ void resolve_tree_islands(struct repository *r, void load_delta_islands(struct repository *r, int progress); void propagate_island_marks(struct commit *commit); int compute_pack_layers(struct packing_data *to_pack); +void free_island_marks(void); #endif /* DELTA_ISLANDS_H */ diff --git a/dir-iterator.c b/dir-iterator.c index b17e9f970a..3764dd81a1 100644 --- a/dir-iterator.c +++ b/dir-iterator.c @@ -203,7 +203,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 +213,15 @@ 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: stat/lstat already checks for NULL or empty strings and + * nonexistent paths. */ - if (stat(iter->base.path.buf, &iter->base.st) < 0) { + 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); + + if (err < 0) { saved_errno = errno; goto error_out; } diff --git a/dir-iterator.h b/dir-iterator.h index 08229157c6..e3b6ff2800 100644 --- a/dir-iterator.h +++ b/dir-iterator.h @@ -61,6 +61,11 @@ * not the symlinks themselves, which is the default behavior. Broken * symlinks are ignored. * + * Note: setting DIR_ITERATOR_FOLLOW_SYMLINKS affects resolving the + * starting path as well (e.g., attempting to iterate starting at a + * symbolic link pointing to a directory without FOLLOW_SYMLINKS will + * result in an error). + * * 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. @@ -748,6 +748,23 @@ static int fsck_tree(const struct object_id *tree_oid, return retval; } +/* + * Confirm that the headers of a commit or tag object end in a reasonable way, + * either with the usual "\n\n" separator, or at least with a trailing newline + * on the final header line. + * + * This property is important for the memory safety of our callers. It allows + * them to scan the buffer linewise without constantly checking the remaining + * size as long as: + * + * - they check that there are bytes left in the buffer at the start of any + * line (i.e., that the last newline they saw was not the final one we + * found here) + * + * - any intra-line scanning they do will stop at a newline, which will worst + * case hit the newline we found here as the end-of-header. This makes it + * OK for them to use helpers like parse_oid_hex(), or even skip_prefix(). + */ static int verify_headers(const void *data, unsigned long size, const struct object_id *oid, enum object_type type, struct fsck_options *options) @@ -808,6 +825,20 @@ static int fsck_ident(const char **ident, if (*p != ' ') return report(options, oid, type, FSCK_MSG_MISSING_SPACE_BEFORE_DATE, "invalid author/committer line - missing space before date"); p++; + /* + * Our timestamp parser is based on the C strto*() functions, which + * will happily eat whitespace, including the newline that is supposed + * to prevent us walking past the end of the buffer. So do our own + * scan, skipping linear whitespace but not newlines, and then + * confirming we found a digit. We _could_ be even more strict here, + * as we really expect only a single space, but since we have + * traditionally allowed extra whitespace, we'll continue to do so. + */ + while (*p == ' ' || *p == '\t') + p++; + if (!isdigit(*p)) + return report(options, oid, type, FSCK_MSG_BAD_DATE, + "invalid author/committer line - bad date"); if (*p == '0' && p[1] != ' ') return report(options, oid, type, FSCK_MSG_ZERO_PADDED_DATE, "invalid author/committer line - zero-padded date"); if (date_overflows(parse_timestamp(p, &end, 10))) @@ -834,12 +865,18 @@ static int fsck_commit(const struct object_id *oid, unsigned author_count; int err; const char *buffer_begin = buffer; + const char *buffer_end = buffer + size; const char *p; + /* + * We _must_ stop parsing immediately if this reports failure, as the + * memory safety of the rest of the function depends on it. See the + * comment above the definition of verify_headers() for more details. + */ if (verify_headers(buffer, size, oid, OBJ_COMMIT, options)) return -1; - if (!skip_prefix(buffer, "tree ", &buffer)) + if (buffer >= buffer_end || !skip_prefix(buffer, "tree ", &buffer)) return report(options, oid, OBJ_COMMIT, FSCK_MSG_MISSING_TREE, "invalid format - expected 'tree' line"); if (parse_oid_hex(buffer, &tree_oid, &p) || *p != '\n') { err = report(options, oid, OBJ_COMMIT, FSCK_MSG_BAD_TREE_SHA1, "invalid 'tree' line format - bad sha1"); @@ -847,7 +884,7 @@ static int fsck_commit(const struct object_id *oid, return err; } buffer = p + 1; - while (skip_prefix(buffer, "parent ", &buffer)) { + while (buffer < buffer_end && skip_prefix(buffer, "parent ", &buffer)) { if (parse_oid_hex(buffer, &parent_oid, &p) || *p != '\n') { err = report(options, oid, OBJ_COMMIT, FSCK_MSG_BAD_PARENT_SHA1, "invalid 'parent' line format - bad sha1"); if (err) @@ -856,7 +893,7 @@ static int fsck_commit(const struct object_id *oid, buffer = p + 1; } author_count = 0; - while (skip_prefix(buffer, "author ", &buffer)) { + while (buffer < buffer_end && skip_prefix(buffer, "author ", &buffer)) { author_count++; err = fsck_ident(&buffer, oid, OBJ_COMMIT, options); if (err) @@ -868,7 +905,7 @@ static int fsck_commit(const struct object_id *oid, err = report(options, oid, OBJ_COMMIT, FSCK_MSG_MULTIPLE_AUTHORS, "invalid format - multiple 'author' lines"); if (err) return err; - if (!skip_prefix(buffer, "committer ", &buffer)) + if (buffer >= buffer_end || !skip_prefix(buffer, "committer ", &buffer)) return report(options, oid, OBJ_COMMIT, FSCK_MSG_MISSING_COMMITTER, "invalid format - expected 'committer' line"); err = fsck_ident(&buffer, oid, OBJ_COMMIT, options); if (err) @@ -899,13 +936,19 @@ int fsck_tag_standalone(const struct object_id *oid, const char *buffer, int ret = 0; char *eol; struct strbuf sb = STRBUF_INIT; + const char *buffer_end = buffer + size; const char *p; + /* + * We _must_ stop parsing immediately if this reports failure, as the + * memory safety of the rest of the function depends on it. See the + * comment above the definition of verify_headers() for more details. + */ ret = verify_headers(buffer, size, oid, OBJ_TAG, options); if (ret) goto done; - if (!skip_prefix(buffer, "object ", &buffer)) { + if (buffer >= buffer_end || !skip_prefix(buffer, "object ", &buffer)) { ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_OBJECT, "invalid format - expected 'object' line"); goto done; } @@ -916,11 +959,11 @@ int fsck_tag_standalone(const struct object_id *oid, const char *buffer, } buffer = p + 1; - if (!skip_prefix(buffer, "type ", &buffer)) { + if (buffer >= buffer_end || !skip_prefix(buffer, "type ", &buffer)) { ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_TYPE_ENTRY, "invalid format - expected 'type' line"); goto done; } - eol = strchr(buffer, '\n'); + eol = memchr(buffer, '\n', buffer_end - buffer); if (!eol) { ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_TYPE, "invalid format - unexpected end after 'type' line"); goto done; @@ -932,11 +975,11 @@ int fsck_tag_standalone(const struct object_id *oid, const char *buffer, goto done; buffer = eol + 1; - if (!skip_prefix(buffer, "tag ", &buffer)) { + if (buffer >= buffer_end || !skip_prefix(buffer, "tag ", &buffer)) { ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_TAG_ENTRY, "invalid format - expected 'tag' line"); goto done; } - eol = strchr(buffer, '\n'); + eol = memchr(buffer, '\n', buffer_end - buffer); if (!eol) { ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_TAG, "invalid format - unexpected end after 'type' line"); goto done; @@ -952,7 +995,7 @@ int fsck_tag_standalone(const struct object_id *oid, const char *buffer, } buffer = eol + 1; - if (!skip_prefix(buffer, "tagger ", &buffer)) { + if (buffer >= buffer_end || !skip_prefix(buffer, "tagger ", &buffer)) { /* early tags do not contain 'tagger' lines; warn only */ ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_TAGGER_ENTRY, "invalid format - expected 'tagger' line"); if (ret) @@ -960,10 +1003,8 @@ int fsck_tag_standalone(const struct object_id *oid, const char *buffer, } else ret = fsck_ident(&buffer, oid, OBJ_TAG, options); - if (!*buffer) - goto done; - if (!starts_with(buffer, "\n")) { + if (buffer < buffer_end && !starts_with(buffer, "\n")) { /* * The verify_headers() check will allow * e.g. "[...]tagger <tagger>\nsome @@ -1237,19 +1278,26 @@ int fsck_object(struct object *obj, void *data, unsigned long size, if (!obj) return report(options, NULL, OBJ_NONE, FSCK_MSG_BAD_OBJECT_SHA1, "no valid object to fsck"); - if (obj->type == OBJ_BLOB) - return fsck_blob(&obj->oid, data, size, options); - if (obj->type == OBJ_TREE) - return fsck_tree(&obj->oid, data, size, options); - if (obj->type == OBJ_COMMIT) - return fsck_commit(&obj->oid, data, size, options); - if (obj->type == OBJ_TAG) - return fsck_tag(&obj->oid, data, size, options); + return fsck_buffer(&obj->oid, obj->type, data, size, options); +} + +int fsck_buffer(const struct object_id *oid, enum object_type type, + void *data, unsigned long size, + struct fsck_options *options) +{ + if (type == OBJ_BLOB) + return fsck_blob(oid, data, size, options); + if (type == OBJ_TREE) + return fsck_tree(oid, data, size, options); + if (type == OBJ_COMMIT) + return fsck_commit(oid, data, size, options); + if (type == OBJ_TAG) + return fsck_tag(oid, data, size, options); - return report(options, &obj->oid, obj->type, + return report(options, oid, type, FSCK_MSG_UNKNOWN_TYPE, "unknown type '%d' (internal fsck error)", - obj->type); + type); } int fsck_error_function(struct fsck_options *o, @@ -184,6 +184,14 @@ int fsck_object(struct object *obj, void *data, unsigned long size, struct fsck_options *options); /* + * Same as fsck_object(), but for when the caller doesn't have an object + * struct. + */ +int fsck_buffer(const struct object_id *oid, enum object_type, + void *data, unsigned long size, + struct fsck_options *options); + +/* * fsck a tag, and pass info about it back to the caller. This is * exposed fsck_object() internals for git-mktag(1). */ diff --git a/fsmonitor-settings.c b/fsmonitor-settings.c index ee63a97dc5..899bfe9c81 100644 --- a/fsmonitor-settings.c +++ b/fsmonitor-settings.c @@ -143,8 +143,6 @@ static void lookup_fsmonitor_settings(struct repository *r) enum fsmonitor_mode fsm_settings__get_mode(struct repository *r) { - if (!r) - r = the_repository; if (!r->settings.fsmonitor) lookup_fsmonitor_settings(r); @@ -153,8 +151,6 @@ enum fsmonitor_mode fsm_settings__get_mode(struct repository *r) const char *fsm_settings__get_hook_path(struct repository *r) { - if (!r) - r = the_repository; if (!r->settings.fsmonitor) lookup_fsmonitor_settings(r); @@ -174,8 +170,6 @@ void fsm_settings__set_ipc(struct repository *r) * Caller requested IPC explicitly, so avoid (possibly * recursive) config lookup. */ - if (!r) - r = the_repository; if (!r->settings.fsmonitor) r->settings.fsmonitor = alloc_settings(); @@ -197,8 +191,6 @@ void fsm_settings__set_hook(struct repository *r, const char *path) * Caller requested hook explicitly, so avoid (possibly * recursive) config lookup. */ - if (!r) - r = the_repository; if (!r->settings.fsmonitor) r->settings.fsmonitor = alloc_settings(); @@ -210,8 +202,6 @@ void fsm_settings__set_hook(struct repository *r, const char *path) void fsm_settings__set_disabled(struct repository *r) { - if (!r) - r = the_repository; if (!r->settings.fsmonitor) r->settings.fsmonitor = alloc_settings(); @@ -223,8 +213,6 @@ void fsm_settings__set_disabled(struct repository *r) void fsm_settings__set_incompatible(struct repository *r, enum fsmonitor_reason reason) { - if (!r) - r = the_repository; if (!r->settings.fsmonitor) r->settings.fsmonitor = alloc_settings(); @@ -235,8 +223,6 @@ void fsm_settings__set_incompatible(struct repository *r, enum fsmonitor_reason fsm_settings__get_reason(struct repository *r) { - if (!r) - r = the_repository; if (!r->settings.fsmonitor) lookup_fsmonitor_settings(r); diff --git a/fsmonitor.c b/fsmonitor.c index 08af00c738..a5b9e75437 100644 --- a/fsmonitor.c +++ b/fsmonitor.c @@ -304,7 +304,7 @@ void refresh_fsmonitor(struct index_state *istate) char *buf; unsigned int i; int is_trivial = 0; - struct repository *r = istate->repo ? istate->repo : the_repository; + struct repository *r = istate->repo; enum fsmonitor_mode fsm_mode = fsm_settings__get_mode(r); enum fsmonitor_reason reason = fsm_settings__get_reason(r); 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/git-compat-util.h b/git-compat-util.h index 6240fc0795..4f0028ce60 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -1357,6 +1357,11 @@ static inline int regexec_buf(const regex_t *preg, const char *buf, size_t size, return regexec(preg, buf, nmatch, pmatch, eflags | REG_STARTEND); } +#ifdef USE_ENHANCED_BASIC_REGULAR_EXPRESSIONS +int git_regcomp(regex_t *preg, const char *pattern, int cflags); +#define regcomp git_regcomp +#endif + #ifndef DIR_HAS_BSD_GROUP_SEMANTICS # define FORCE_DIR_SET_GID S_ISGID #else diff --git a/git-request-pull.sh b/git-request-pull.sh index 2d0e44656c..01640a044b 100755 --- a/git-request-pull.sh +++ b/git-request-pull.sh @@ -153,7 +153,7 @@ for you to fetch changes up to %H: if test $(git cat-file -t "$head") = tag then git cat-file tag "$head" | - sed -n -e '1,/^$/d' -e '/^-----BEGIN PGP /q' -e p + sed -n -e '1,/^$/d' -e '/^-----BEGIN \(PGP\|SSH\|SIGNED\) /q' -e p echo echo "----------------------------------------------------------------" fi && diff --git a/git-send-email.perl b/git-send-email.perl index 5861e99a6e..07f2a0cbea 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -220,6 +220,10 @@ my $compose_filename; my $force = 0; my $dump_aliases = 0; +# Variables to prevent short format-patch options from being captured +# as abbreviated send-email options +my $reroll_count; + # Handle interactive edition of files. my $multiedit; my $editor; @@ -542,6 +546,7 @@ my %options = ( "batch-size=i" => \$batch_size, "relogin-delay=i" => \$relogin_delay, "git-completion-helper" => \$git_completion_helper, + "v=s" => \$reroll_count, ); $rc = GetOptions(%options); @@ -782,7 +787,9 @@ if (@rev_list_opts) { die __("Cannot run git format-patch from outside a repository\n") unless $repo; require File::Temp; - push @files, $repo->command('format-patch', '-o', File::Temp::tempdir(CLEANUP => 1), @rev_list_opts); + push @files, $repo->command('format-patch', '-o', File::Temp::tempdir(CLEANUP => 1), + defined $reroll_count ? ('-v', $reroll_count) : (), + @rev_list_opts); } @files = handle_backup_files(@files); @@ -507,7 +507,6 @@ static struct cmd_struct commands[] = { { "diff-index", cmd_diff_index, RUN_SETUP | NO_PARSEOPT }, { "diff-tree", cmd_diff_tree, RUN_SETUP | NO_PARSEOPT }, { "difftool", cmd_difftool, RUN_SETUP_GENTLY }, - { "env--helper", cmd_env__helper }, { "fast-export", cmd_fast_export, RUN_SETUP }, { "fast-import", cmd_fast_import, RUN_SETUP | NO_PARSEOPT }, { "fetch", cmd_fetch, RUN_SETUP }, diff --git a/gpg-interface.c b/gpg-interface.c index f877a1ea56..687236430b 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -998,6 +998,7 @@ static int sign_buffer_ssh(struct strbuf *buffer, struct strbuf *signature, char *ssh_signing_key_file = NULL; struct strbuf ssh_signature_filename = STRBUF_INIT; const char *literal_key = NULL; + int literal_ssh_key = 0; if (!signing_key || signing_key[0] == '\0') return error( @@ -1005,6 +1006,7 @@ static int sign_buffer_ssh(struct strbuf *buffer, struct strbuf *signature, if (is_literal_ssh_key(signing_key, &literal_key)) { /* A literal ssh key */ + literal_ssh_key = 1; key_file = mks_tempfile_t(".git_signing_key_tmpXXXXXX"); if (!key_file) return error_errno( @@ -1039,8 +1041,10 @@ static int sign_buffer_ssh(struct strbuf *buffer, struct strbuf *signature, "-Y", "sign", "-n", "git", "-f", ssh_signing_key_file, - buffer_file->filename.buf, NULL); + if (literal_ssh_key) + strvec_push(&signer.args, "-U"); + strvec_push(&signer.args, buffer_file->filename.buf); sigchain_push(SIGPIPE, SIG_IGN); ret = pipe_command(&signer, NULL, 0, NULL, 0, &signer_stderr, 0); @@ -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; @@ -293,7 +318,7 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt options |= PCRE2_CASELESS; } if (!opt->ignore_locale && is_utf8_locale() && !literal) - options |= (PCRE2_UTF | PCRE2_MATCH_INVALID_UTF); + options |= (PCRE2_UTF | PCRE2_UCP | PCRE2_MATCH_INVALID_UTF); #ifndef GIT_PCRE2_VERSION_10_36_OR_HIGHER /* Work around https://bugs.exim.org/show_bug.cgi?id=2642 fixed in 10.36 */ @@ -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) 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/ll-merge.c b/ll-merge.c index 22a603e8af..130d26501c 100644 --- a/ll-merge.c +++ b/ll-merge.c @@ -391,7 +391,7 @@ enum ll_merge_result ll_merge(mmbuffer_t *result_buf, normalize_file(theirs, path, istate); } - git_check_attr(istate, path, check); + git_check_attr(istate, NULL, path, check); ll_driver_name = check->items[0].value; if (check->items[1].value) { marker_size = atoi(check->items[1].value); @@ -419,7 +419,7 @@ int ll_merge_marker_size(struct index_state *istate, const char *path) if (!check) check = attr_check_initl("conflict-marker-size", NULL); - git_check_attr(istate, path, check); + git_check_attr(istate, NULL, path, check); if (check->items[0].value) { marker_size = atoi(check->items[0].value); if (marker_size <= 0) diff --git a/merge-recursive.c b/merge-recursive.c index 2fd0aa9687..ae469f8cc8 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -412,7 +412,7 @@ static int unpack_trees_start(struct merge_options *opt, { int rc; struct tree_desc t[3]; - struct index_state tmp_index = { NULL }; + struct index_state tmp_index = INDEX_STATE_INIT(opt->repo); memset(&opt->priv->unpack_opts, 0, sizeof(opt->priv->unpack_opts)); if (opt->priv->call_depth) diff --git a/object-file.c b/object-file.c index ce9efae994..939865c1ae 100644 --- a/object-file.c +++ b/object-file.c @@ -33,6 +33,7 @@ #include "object-store.h" #include "promisor-remote.h" #include "submodule.h" +#include "fsck.h" /* The maximum size for an object header. */ #define MAX_HEADER_LEN 32 @@ -2284,32 +2285,21 @@ int repo_has_object_file(struct repository *r, return repo_has_object_file_with_flags(r, oid, 0); } -static void check_tree(const void *buf, size_t size) -{ - struct tree_desc desc; - struct name_entry entry; - - init_tree_desc(&desc, buf, size); - while (tree_entry(&desc, &entry)) - /* do nothing - * tree_entry() will die() on malformed entries */ - ; -} - -static void check_commit(const void *buf, size_t size) -{ - struct commit c; - memset(&c, 0, sizeof(c)); - if (parse_commit_buffer(the_repository, &c, buf, size, 0)) - die(_("corrupt commit")); -} - -static void check_tag(const void *buf, size_t size) -{ - struct tag t; - memset(&t, 0, sizeof(t)); - if (parse_tag_buffer(the_repository, &t, buf, size)) - die(_("corrupt tag")); +/* + * We can't use the normal fsck_error_function() for index_mem(), + * because we don't yet have a valid oid for it to report. Instead, + * report the minimal fsck error here, and rely on the caller to + * give more context. + */ +static int hash_format_check_report(struct fsck_options *opts, + const struct object_id *oid, + enum object_type object_type, + enum fsck_msg_type msg_type, + enum fsck_msg_id msg_id, + const char *message) +{ + error(_("object fails fsck: %s"), message); + return 1; } static int index_mem(struct index_state *istate, @@ -2336,12 +2326,13 @@ static int index_mem(struct index_state *istate, } } if (flags & HASH_FORMAT_CHECK) { - if (type == OBJ_TREE) - check_tree(buf, size); - if (type == OBJ_COMMIT) - check_commit(buf, size); - if (type == OBJ_TAG) - check_tag(buf, size); + struct fsck_options opts = FSCK_OPTIONS_DEFAULT; + + opts.strict = 1; + opts.error_func = hash_format_check_report; + if (fsck_buffer(null_oid(), type, buf, size, &opts)) + die(_("refusing to create malformed object")); + fsck_finish(&opts); } if (write_object) diff --git a/pathspec.c b/pathspec.c index e038481dc4..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"), @@ -730,7 +730,7 @@ int match_pathspec_attrs(struct index_state *istate, if (name[namelen]) name = to_free = xmemdupz(name, namelen); - git_check_attr(istate, name, item->attr_check); + git_check_attr(istate, NULL, name, item->attr_check); free(to_free); diff --git a/read-cache.c b/read-cache.c index cf87ad7097..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) @@ -2300,12 +2301,10 @@ static void set_new_index_sparsity(struct index_state *istate) * If the index's repo exists, mark it sparse according to * repo settings. */ - if (istate->repo) { - prepare_repo_settings(istate->repo); - if (!istate->repo->settings.command_requires_full_index && - is_sparse_index_allowed(istate, 0)) - istate->sparse_index = 1; - } + prepare_repo_settings(istate->repo); + if (!istate->repo->settings.command_requires_full_index && + is_sparse_index_allowed(istate, 0)) + istate->sparse_index = 1; } /* remember to discard_cache() before reading a different cache! */ @@ -2330,8 +2329,6 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist) fd = open(path, O_RDONLY); if (fd < 0) { if (!must_exist && errno == ENOENT) { - if (!istate->repo) - istate->repo = the_repository; set_new_index_sparsity(istate); return 0; } @@ -2433,9 +2430,6 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist) trace2_data_intmax("index", the_repository, "read/cache_nr", istate->cache_nr); - if (!istate->repo) - istate->repo = the_repository; - /* * If the command explicitly requires a full index, force it * to be full. Otherwise, correct the sparsity based on repository @@ -2498,9 +2492,10 @@ int read_index_from(struct index_state *istate, const char *path, trace_performance_enter(); if (split_index->base) - discard_index(split_index->base); + release_index(split_index->base); else - CALLOC_ARRAY(split_index->base, 1); + ALLOC_ARRAY(split_index->base, 1); + index_state_init(split_index->base, istate->repo); base_oid_hex = oid_to_hex(&split_index->base_oid); base_path = xstrfmt("%s/sharedindex.%s", gitdir, base_oid_hex); @@ -2539,7 +2534,13 @@ int is_index_unborn(struct index_state *istate) return (!istate->cache_nr && !istate->timestamp.sec); } -void discard_index(struct index_state *istate) +void index_state_init(struct index_state *istate, struct repository *r) +{ + struct index_state blank = INDEX_STATE_INIT(r); + memcpy(istate, &blank, sizeof(*istate)); +} + +void release_index(struct index_state *istate) { /* * Cache entries in istate->cache[] should have been allocated @@ -2551,20 +2552,12 @@ void discard_index(struct index_state *istate) validate_cache_entries(istate); resolve_undo_clear_index(istate); - istate->cache_nr = 0; - istate->cache_changed = 0; - istate->timestamp.sec = 0; - istate->timestamp.nsec = 0; free_name_hash(istate); cache_tree_free(&(istate->cache_tree)); - istate->initialized = 0; - istate->fsmonitor_has_run_once = 0; - FREE_AND_NULL(istate->fsmonitor_last_update); - FREE_AND_NULL(istate->cache); - istate->cache_alloc = 0; + free(istate->fsmonitor_last_update); + free(istate->cache); discard_split_index(istate); free_untracked_cache(istate->untracked); - istate->untracked = NULL; if (istate->sparse_checkout_patterns) { clear_pattern_list(istate->sparse_checkout_patterns); @@ -2577,6 +2570,12 @@ void discard_index(struct index_state *istate) } } +void discard_index(struct index_state *istate) +{ + release_index(istate); + index_state_init(istate, istate->repo); +} + /* * Validate the cache entries of this index. * All cache entries associated with this index @@ -2928,7 +2927,7 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, int ieot_entries = 1; struct index_entry_offset_table *ieot = NULL; int nr, nr_threads; - struct repository *r = istate->repo ? istate->repo : the_repository; + struct repository *r = istate->repo; f = hashfd(tempfile->fd, tempfile->filename.buf); diff --git a/repository.c b/repository.c index 3427085fd6..937fa974b3 100644 --- a/repository.c +++ b/repository.c @@ -28,6 +28,8 @@ void initialize_the_repository(void) the_repo.remote_state = remote_state_new(); the_repo.parsed_objects = parsed_object_pool_new(); + index_state_init(&the_index, the_repository); + repo_set_hash_algo(&the_repo, GIT_HASH_SHA1); } @@ -302,14 +304,13 @@ int repo_read_index(struct repository *repo) { int res; - if (!repo->index) - CALLOC_ARRAY(repo->index, 1); - /* Complete the double-reference */ - if (!repo->index->repo) - repo->index->repo = repo; - else if (repo->index->repo != repo) + if (!repo->index) { + ALLOC_ARRAY(repo->index, 1); + index_state_init(repo->index, repo); + } else if (repo->index->repo != repo) { BUG("repo's index should point back at itself"); + } res = read_index_from(repo->index, repo->index_file, repo->gitdir); diff --git a/revision.c b/revision.c index 100e5ad511..21f5f572c2 100644 --- a/revision.c +++ b/revision.c @@ -1813,7 +1813,7 @@ void add_index_objects_to_pending(struct rev_info *revs, unsigned int flags) worktrees = get_worktrees(); for (p = worktrees; *p; p++) { struct worktree *wt = *p; - struct index_state istate = { NULL }; + struct index_state istate = INDEX_STATE_INIT(revs->repo); if (wt->is_current) continue; /* current index already taken care of */ @@ -262,7 +262,7 @@ static int register_dir(void) return error(_("could not set recommended config")); if (toggle_maintenance(1)) - return error(_("could not turn on maintenance")); + warning(_("could not turn on maintenance")); if (have_fsmonitor_support() && start_fsmonitor_daemon()) { return error(_("could not start the FSMonitor daemon")); @@ -405,7 +405,7 @@ void load_builtin_commands(const char *prefix, struct cmdnames *cmds) static int cmd_clone(int argc, const char **argv) { const char *branch = NULL; - int full_clone = 0, single_branch = 0; + int full_clone = 0, single_branch = 0, show_progress = isatty(2); struct option clone_options[] = { OPT_STRING('b', "branch", &branch, N_("<branch>"), N_("branch to checkout after clone")), @@ -500,7 +500,9 @@ static int cmd_clone(int argc, const char **argv) if (set_recommended_config(0)) return error(_("could not configure '%s'"), dir); - if ((res = run_git("fetch", "--quiet", "origin", NULL))) { + if ((res = run_git("fetch", "--quiet", + show_progress ? "--progress" : "--no-progress", + "origin", NULL))) { warning(_("partial clone failed; attempting full clone")); if (set_config("remote.origin.promisor") || @@ -509,7 +511,9 @@ static int cmd_clone(int argc, const char **argv) goto cleanup; } - if ((res = run_git("fetch", "--quiet", "origin", NULL))) + if ((res = run_git("fetch", "--quiet", + show_progress ? "--progress" : "--no-progress", + "origin", NULL))) goto cleanup; } diff --git a/sequencer.c b/sequencer.c index d8d59d05dd..65a34f9676 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; 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) diff --git a/sparse-index.c b/sparse-index.c index 8c269dab80..147a13386a 100644 --- a/sparse-index.c +++ b/sparse-index.c @@ -128,9 +128,6 @@ int is_sparse_index_allowed(struct index_state *istate, int flags) if (!core_apply_sparse_checkout || !core_sparse_checkout_cone) return 0; - if (!istate->repo) - istate->repo = the_repository; - if (!(flags & SPARSE_INDEX_MEMORY_ONLY)) { int test_env; @@ -299,7 +296,7 @@ void expand_index(struct index_state *istate, struct pattern_list *pl) * If the index is already full, then keep it full. We will convert * it to a sparse index on write, if possible. */ - if (!istate || istate->sparse_index == INDEX_EXPANDED) + if (istate->sparse_index == INDEX_EXPANDED) return; /* @@ -327,9 +324,6 @@ void expand_index(struct index_state *istate, struct pattern_list *pl) pl = NULL; } - if (!istate->repo) - istate->repo = the_repository; - /* * A NULL pattern set indicates we are expanding a full index, so * we use a special region name that indicates the full expansion. @@ -424,6 +418,8 @@ void expand_index(struct index_state *istate, struct pattern_list *pl) void ensure_full_index(struct index_state *istate) { + if (!istate) + BUG("ensure_full_index() must get an index!"); expand_index(istate, NULL); } @@ -547,12 +543,9 @@ void expand_to_path(struct index_state *istate, if (in_expand_to_path) return; - if (!istate || !istate->sparse_index) + if (!istate->sparse_index) return; - if (!istate->repo) - istate->repo = the_repository; - in_expand_to_path = 1; /* diff --git a/split-index.c b/split-index.c index 9d0ccc30d0..5d0f04763e 100644 --- a/split-index.c +++ b/split-index.c @@ -90,7 +90,8 @@ void move_cache_to_base_index(struct index_state *istate) mem_pool_combine(istate->ce_mem_pool, istate->split_index->base->ce_mem_pool); } - CALLOC_ARRAY(si->base, 1); + ALLOC_ARRAY(si->base, 1); + index_state_init(si->base, istate->repo); si->base->version = istate->version; /* zero timestamp disables racy test in ce_write_index() */ si->base->timestamp = istate->timestamp; @@ -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/builtin/env--helper.c b/t/helper/test-env-helper.c index ea04c16636..66c88b8ff3 100644 --- a/builtin/env--helper.c +++ b/t/helper/test-env-helper.c @@ -1,9 +1,9 @@ -#include "builtin.h" +#include "test-tool.h" #include "config.h" #include "parse-options.h" static char const * const env__helper_usage[] = { - N_("git env--helper --type=[bool|ulong] <options> <env-var>"), + "test-tool env-helper --type=[bool|ulong] <options> <env-var>", NULL }; @@ -24,12 +24,12 @@ static int option_parse_type(const struct option *opt, const char *arg, else if (!strcmp(arg, "ulong")) *cmdmode = ENV_HELPER_TYPE_ULONG; else - die(_("unrecognized --type argument, %s"), arg); + die("unrecognized --type argument, %s", arg); return 0; } -int cmd_env__helper(int argc, const char **argv, const char *prefix) +int cmd__env_helper(int argc, const char **argv) { int exit_code = 0; const char *env_variable = NULL; @@ -39,17 +39,17 @@ int cmd_env__helper(int argc, const char **argv, const char *prefix) unsigned long ret_ulong, default_ulong; enum cmdmode cmdmode = 0; struct option opts[] = { - OPT_CALLBACK_F(0, "type", &cmdmode, N_("type"), - N_("value is given this type"), PARSE_OPT_NONEG, + OPT_CALLBACK_F(0, "type", &cmdmode, "type", + "value is given this type", PARSE_OPT_NONEG, option_parse_type), - OPT_STRING(0, "default", &env_default, N_("value"), - N_("default for git_env_*(...) to fall back on")), + OPT_STRING(0, "default", &env_default, "value", + "default for git_env_*(...) to fall back on"), OPT_BOOL(0, "exit-code", &exit_code, - N_("be quiet only use git_env_*() value as exit code")), + "be quiet only use git_env_*() value as exit code"), OPT_END(), }; - argc = parse_options(argc, argv, prefix, opts, env__helper_usage, + argc = parse_options(argc, argv, NULL, opts, env__helper_usage, PARSE_OPT_KEEP_UNKNOWN_OPT); if (env_default && !*env_default) usage_with_options(env__helper_usage, opts); @@ -64,7 +64,7 @@ int cmd_env__helper(int argc, const char **argv, const char *prefix) if (env_default) { default_int = git_parse_maybe_bool(env_default); if (default_int == -1) { - error(_("option `--default' expects a boolean value with `--type=bool`, not `%s`"), + error("option `--default' expects a boolean value with `--type=bool`, not `%s`", env_default); usage_with_options(env__helper_usage, opts); } @@ -79,7 +79,7 @@ int cmd_env__helper(int argc, const char **argv, const char *prefix) case ENV_HELPER_TYPE_ULONG: if (env_default) { if (!git_parse_ulong(env_default, &default_ulong)) { - error(_("option `--default' expects an unsigned long value with `--type=ulong`, not `%s`"), + error("option `--default' expects an unsigned long value with `--type=ulong`, not `%s`", env_default); usage_with_options(env__helper_usage, opts); } diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c index 7eb1a26a30..abe8a785eb 100644 --- a/t/helper/test-tool.c +++ b/t/helper/test-tool.c @@ -28,6 +28,7 @@ static struct test_cmd cmds[] = { { "dump-fsmonitor", cmd__dump_fsmonitor }, { "dump-split-index", cmd__dump_split_index }, { "dump-untracked-cache", cmd__dump_untracked_cache }, + { "env-helper", cmd__env_helper }, { "example-decorate", cmd__example_decorate }, { "fast-rebase", cmd__fast_rebase }, { "fsmonitor-client", cmd__fsmonitor_client }, diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h index 2e20a16eb8..ea2672436c 100644 --- a/t/helper/test-tool.h +++ b/t/helper/test-tool.h @@ -22,6 +22,7 @@ int cmd__dump_fsmonitor(int argc, const char **argv); int cmd__dump_split_index(int argc, const char **argv); int cmd__dump_untracked_cache(int argc, const char **argv); int cmd__dump_reftable(int argc, const char **argv); +int cmd__env_helper(int argc, const char **argv); int cmd__example_decorate(int argc, const char **argv); int cmd__fast_rebase(int argc, const char **argv); int cmd__fsmonitor_client(int argc, const char **argv); diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh index 608949ea80..5d2d56c445 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh @@ -99,16 +99,19 @@ then fi HTTPD_VERSION=$($LIB_HTTPD_PATH -v | \ - sed -n 's/^Server version: Apache\/\([0-9]*\)\..*$/\1/p; q') + sed -n 's/^Server version: Apache\/\([0-9.]*\).*$/\1/p; q') +HTTPD_VERSION_MAJOR=$(echo $HTTPD_VERSION | cut -d. -f1) +HTTPD_VERSION_MINOR=$(echo $HTTPD_VERSION | cut -d. -f2) -if test -n "$HTTPD_VERSION" +if test -n "$HTTPD_VERSION_MAJOR" then if test -z "$LIB_HTTPD_MODULE_PATH" then - if ! test $HTTPD_VERSION -ge 2 + if ! test "$HTTPD_VERSION_MAJOR" -eq 2 || + ! test "$HTTPD_VERSION_MINOR" -ge 4 then test_skip_or_die GIT_TEST_HTTPD \ - "at least Apache version 2 is required" + "at least Apache version 2.4 is required" fi if ! test -d "$DEFAULT_HTTPD_MODULE_PATH" then diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 0294739a77..51a4fbcf62 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -34,17 +34,6 @@ LoadModule http2_module modules/mod_http2.so Protocols h2c </IfDefine> -<IfVersion < 2.4> -LockFile accept.lock -</IfVersion> - -<IfVersion < 2.1> -<IfModule !mod_auth.c> - LoadModule auth_module modules/mod_auth.so -</IfModule> -</IfVersion> - -<IfVersion >= 2.1> <IfModule !mod_auth_basic.c> LoadModule auth_basic_module modules/mod_auth_basic.so </IfModule> @@ -57,9 +46,7 @@ LockFile accept.lock <IfModule !mod_authz_host.c> LoadModule authz_host_module modules/mod_authz_host.so </IfModule> -</IfVersion> -<IfVersion >= 2.4> <IfModule !mod_authn_core.c> LoadModule authn_core_module modules/mod_authn_core.so </IfModule> @@ -83,7 +70,6 @@ LockFile accept.lock LoadModule mpm_prefork_module modules/mod_mpm_prefork.so </IfModule> </IfDefine> -</IfVersion> PassEnv GIT_VALGRIND PassEnv GIT_VALGRIND_OPTIONS @@ -123,6 +109,10 @@ Alias /auth/dumb/ www/auth/dumb/ Header set Set-Cookie name=value </LocationMatch> <LocationMatch /smart_headers/> + <RequireAll> + Require expr %{HTTP:x-magic-one} == 'abra' + Require expr %{HTTP:x-magic-two} == 'cadabra' + </RequireAll> SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH} SetEnv GIT_HTTP_EXPORT_ALL </LocationMatch> @@ -205,18 +195,6 @@ RewriteRule ^/intern-redir/(.*)/foo$ /smart/$1 [PT] RewriteRule ^/redir-objects/(.*/info/refs)$ /dumb/$1 [PT] RewriteRule ^/redir-objects/(.*/objects/.*)$ /dumb/$1 [R=301] -# Apache 2.2 does not understand <RequireAll>, so we use RewriteCond. -# And as RewriteCond does not allow testing for non-matches, we match -# the desired case first (one has abra, two has cadabra), and let it -# pass by marking the RewriteRule as [L], "last rule, do not process -# any other matching RewriteRules after this"), and then have another -# RewriteRule that matches all other cases and lets them fail via '[F]', -# "fail the request". -RewriteCond %{HTTP:x-magic-one} =abra -RewriteCond %{HTTP:x-magic-two} =cadabra -RewriteRule ^/smart_headers/.* - [L] -RewriteRule ^/smart_headers/.* - [F] - <IfDefine SSL> LoadModule ssl_module modules/mod_ssl.so @@ -225,7 +203,6 @@ SSLCertificateKeyFile httpd.pem SSLRandomSeed startup file:/dev/urandom 512 SSLRandomSeed connect file:/dev/urandom 512 SSLSessionCache none -SSLMutex file:ssl_mutex SSLEngine On </IfDefine> diff --git a/t/lib-httpd/ssl.cnf b/t/lib-httpd/ssl.cnf index 6dab2579cb..812e8253f0 100644 --- a/t/lib-httpd/ssl.cnf +++ b/t/lib-httpd/ssl.cnf @@ -1,7 +1,7 @@ RANDFILE = $ENV::RANDFILE_PATH [ req ] -default_bits = 1024 +default_bits = 2048 distinguished_name = req_distinguished_name prompt = no [ req_distinguished_name ] diff --git a/t/perf/p7822-grep-perl-character.sh b/t/perf/p7822-grep-perl-character.sh new file mode 100755 index 0000000000..87009c60df --- /dev/null +++ b/t/perf/p7822-grep-perl-character.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +test_description="git-grep's perl regex + +If GIT_PERF_GREP_THREADS is set to a list of threads (e.g. '1 4 8' +etc.) we will test the patterns under those numbers of threads. +" + +. ./perf-lib.sh + +test_perf_large_repo +test_checkout_worktree + +if test -n "$GIT_PERF_GREP_THREADS" +then + test_set_prereq PERF_GREP_ENGINES_THREADS +fi + +for pattern in \ + '\\bhow' \ + '\\bÆvar' \ + '\\d+ \\bÆvar' \ + '\\bBelón\\b' \ + '\\w{12}\\b' +do + echo '$pattern' >pat + if ! test_have_prereq PERF_GREP_ENGINES_THREADS + then + test_perf "grep -P '$pattern'" --prereq PCRE " + git -P grep -f pat || : + " + else + for threads in $GIT_PERF_GREP_THREADS + do + test_perf "grep -P '$pattern' with $threads threads" --prereq PTHREADS,PCRE " + git -c grep.threads=$threads -P grep -f pat || : + " + done + fi +done + +test_done diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh index d0284fe2d7..89b306cb11 100755 --- a/t/t0003-attributes.sh +++ b/t/t0003-attributes.sh @@ -25,7 +25,15 @@ attr_check_quote () { git check-attr test -- "$path" >actual && echo "\"$quoted_path\": test: $expect" >expect && test_cmp expect actual +} + +attr_check_source () { + path="$1" expect="$2" source="$3" git_opts="$4" && + git $git_opts check-attr --source $source test -- "$path" >actual 2>err && + echo "$path: test: $expect" >expect && + test_cmp expect actual && + test_must_be_empty err } test_expect_success 'open-quoted pathname' ' @@ -33,7 +41,6 @@ test_expect_success 'open-quoted pathname' ' attr_check a unspecified ' - test_expect_success 'setup' ' mkdir -p a/b/d a/c b && ( @@ -80,12 +87,23 @@ test_expect_success 'setup' ' EOF ' +test_expect_success 'setup branches' ' + mkdir -p foo/bar && + test_commit --printf "add .gitattributes" foo/bar/.gitattributes \ + "f test=f\na/i test=n\n" tag-1 && + test_commit --printf "add .gitattributes" foo/bar/.gitattributes \ + "g test=g\na/i test=m\n" tag-2 && + rm foo/bar/.gitattributes +' + test_expect_success 'command line checks' ' test_must_fail git check-attr && test_must_fail git check-attr -- && test_must_fail git check-attr test && test_must_fail git check-attr test -- && test_must_fail git check-attr -- f && + test_must_fail git check-attr --source && + test_must_fail git check-attr --source not-a-valid-ref && echo "f" | test_must_fail git check-attr --stdin && echo "f" | test_must_fail git check-attr --stdin -- f && echo "f" | test_must_fail git check-attr --stdin test -- f && @@ -203,9 +221,12 @@ test_expect_success 'attribute test: read paths from stdin' ' test_cmp expect actual ' -test_expect_success 'attribute test: --all option' ' +test_expect_success 'setup --all option' ' grep -v unspecified <expect-all | sort >specified-all && - sed -e "s/:.*//" <expect-all | uniq >stdin-all && + sed -e "s/:.*//" <expect-all | uniq >stdin-all +' + +test_expect_success 'attribute test: --all option' ' git check-attr --stdin --all <stdin-all >tmp && sort tmp >actual && test_cmp specified-all actual @@ -284,6 +305,15 @@ test_expect_success 'using --git-dir and --work-tree' ' ) ' +test_expect_success 'using --source' ' + attr_check_source foo/bar/f f tag-1 && + attr_check_source foo/bar/a/i n tag-1 && + attr_check_source foo/bar/f unspecified tag-2 && + attr_check_source foo/bar/a/i m tag-2 && + attr_check_source foo/bar/g g tag-2 && + attr_check_source foo/bar/g unspecified tag-1 +' + test_expect_success 'setup bare' ' git clone --template= --bare . bare.git ' @@ -303,6 +333,18 @@ test_expect_success 'bare repository: check that .gitattribute is ignored' ' ) ' +test_expect_success 'bare repository: with --source' ' + ( + cd bare.git && + attr_check_source foo/bar/f f tag-1 && + attr_check_source foo/bar/a/i n tag-1 && + attr_check_source foo/bar/f unspecified tag-2 && + attr_check_source foo/bar/a/i m tag-2 && + attr_check_source foo/bar/g g tag-2 && + attr_check_source foo/bar/g unspecified tag-1 + ) +' + test_expect_success 'bare repository: check that --cached honors index' ' ( cd bare.git && @@ -400,7 +442,7 @@ test_expect_success 'large attributes line ignores trailing content in tree' ' test_expect_success EXPENSIVE 'large attributes file ignored in tree' ' test_when_finished "rm .gitattributes" && - dd if=/dev/zero of=.gitattributes bs=101M count=1 2>/dev/null && + dd if=/dev/zero of=.gitattributes bs=1048576 count=101 2>/dev/null && git check-attr --all path >/dev/null 2>err && echo "warning: ignoring overly large gitattributes file ${SQ}.gitattributes${SQ}" >expect && test_cmp expect err @@ -428,7 +470,7 @@ test_expect_success 'large attributes line ignores trailing content in index' ' test_expect_success EXPENSIVE 'large attributes file ignored in index' ' test_when_finished "git update-index --remove .gitattributes" && - blob=$(dd if=/dev/zero bs=101M count=1 2>/dev/null | git hash-object -w --stdin) && + blob=$(dd if=/dev/zero bs=1048576 count=101 2>/dev/null | git hash-object -w --stdin) && git update-index --add --cacheinfo 100644,$blob,.gitattributes && git check-attr --cached --all path >/dev/null 2>err && echo "warning: ignoring overly large gitattributes blob ${SQ}.gitattributes${SQ}" >expect && diff --git a/t/t0017-env-helper.sh b/t/t0017-env-helper.sh index 2e42fba956..fc14ba091c 100755 --- a/t/t0017-env-helper.sh +++ b/t/t0017-env-helper.sh @@ -1,87 +1,87 @@ #!/bin/sh -test_description='test env--helper' +test_description='test test-tool env-helper' TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh -test_expect_success 'env--helper usage' ' - test_must_fail git env--helper && - test_must_fail git env--helper --type=bool && - test_must_fail git env--helper --type=ulong && - test_must_fail git env--helper --type=bool && - test_must_fail git env--helper --type=bool --default && - test_must_fail git env--helper --type=bool --default= && - test_must_fail git env--helper --defaultxyz +test_expect_success 'test-tool env-helper usage' ' + test_must_fail test-tool env-helper && + test_must_fail test-tool env-helper --type=bool && + test_must_fail test-tool env-helper --type=ulong && + test_must_fail test-tool env-helper --type=bool && + test_must_fail test-tool env-helper --type=bool --default && + test_must_fail test-tool env-helper --type=bool --default= && + test_must_fail test-tool env-helper --defaultxyz ' -test_expect_success 'env--helper bad default values' ' - test_must_fail git env--helper --type=bool --default=1xyz MISSING && - test_must_fail git env--helper --type=ulong --default=1xyz MISSING +test_expect_success 'test-tool env-helper bad default values' ' + test_must_fail test-tool env-helper --type=bool --default=1xyz MISSING && + test_must_fail test-tool env-helper --type=ulong --default=1xyz MISSING ' -test_expect_success 'env--helper --type=bool' ' +test_expect_success 'test-tool env-helper --type=bool' ' # Test various --default bool values echo true >expected && - git env--helper --type=bool --default=1 MISSING >actual && + test-tool env-helper --type=bool --default=1 MISSING >actual && test_cmp expected actual && - git env--helper --type=bool --default=yes MISSING >actual && + test-tool env-helper --type=bool --default=yes MISSING >actual && test_cmp expected actual && - git env--helper --type=bool --default=true MISSING >actual && + test-tool env-helper --type=bool --default=true MISSING >actual && test_cmp expected actual && echo false >expected && - test_must_fail git env--helper --type=bool --default=0 MISSING >actual && + test_must_fail test-tool env-helper --type=bool --default=0 MISSING >actual && test_cmp expected actual && - test_must_fail git env--helper --type=bool --default=no MISSING >actual && + test_must_fail test-tool env-helper --type=bool --default=no MISSING >actual && test_cmp expected actual && - test_must_fail git env--helper --type=bool --default=false MISSING >actual && + test_must_fail test-tool env-helper --type=bool --default=false MISSING >actual && test_cmp expected actual && # No output with --exit-code - git env--helper --type=bool --default=true --exit-code MISSING >actual.out 2>actual.err && + test-tool env-helper --type=bool --default=true --exit-code MISSING >actual.out 2>actual.err && test_must_be_empty actual.out && test_must_be_empty actual.err && - test_must_fail git env--helper --type=bool --default=false --exit-code MISSING >actual.out 2>actual.err && + test_must_fail test-tool env-helper --type=bool --default=false --exit-code MISSING >actual.out 2>actual.err && test_must_be_empty actual.out && test_must_be_empty actual.err && # Existing variable - EXISTS=true git env--helper --type=bool --default=false --exit-code EXISTS >actual.out 2>actual.err && + EXISTS=true test-tool env-helper --type=bool --default=false --exit-code EXISTS >actual.out 2>actual.err && test_must_be_empty actual.out && test_must_be_empty actual.err && test_must_fail \ env EXISTS=false \ - git env--helper --type=bool --default=true --exit-code EXISTS >actual.out 2>actual.err && + test-tool env-helper --type=bool --default=true --exit-code EXISTS >actual.out 2>actual.err && test_must_be_empty actual.out && test_must_be_empty actual.err ' -test_expect_success 'env--helper --type=ulong' ' +test_expect_success 'test-tool env-helper --type=ulong' ' echo 1234567890 >expected && - git env--helper --type=ulong --default=1234567890 MISSING >actual.out 2>actual.err && + test-tool env-helper --type=ulong --default=1234567890 MISSING >actual.out 2>actual.err && test_cmp expected actual.out && test_must_be_empty actual.err && echo 0 >expected && - test_must_fail git env--helper --type=ulong --default=0 MISSING >actual && + test_must_fail test-tool env-helper --type=ulong --default=0 MISSING >actual && test_cmp expected actual && - git env--helper --type=ulong --default=1234567890 --exit-code MISSING >actual.out 2>actual.err && + test-tool env-helper --type=ulong --default=1234567890 --exit-code MISSING >actual.out 2>actual.err && test_must_be_empty actual.out && test_must_be_empty actual.err && - EXISTS=1234567890 git env--helper --type=ulong --default=0 EXISTS --exit-code >actual.out 2>actual.err && + EXISTS=1234567890 test-tool env-helper --type=ulong --default=0 EXISTS --exit-code >actual.out 2>actual.err && test_must_be_empty actual.out && test_must_be_empty actual.err && echo 1234567890 >expected && - EXISTS=1234567890 git env--helper --type=ulong --default=0 EXISTS >actual.out 2>actual.err && + EXISTS=1234567890 test-tool env-helper --type=ulong --default=0 EXISTS >actual.out 2>actual.err && test_cmp expected actual.out && test_must_be_empty actual.err ' -test_expect_success 'env--helper reads config thanks to trace2' ' +test_expect_success 'test-tool env-helper reads config thanks to trace2' ' mkdir home && git config -f home/.gitconfig include.path cycle && git config -f home/cycle include.path .gitconfig && @@ -93,7 +93,7 @@ test_expect_success 'env--helper reads config thanks to trace2' ' test_must_fail \ env HOME="$(pwd)/home" GIT_TEST_ENV_HELPER=true \ - git -C cycle env--helper --type=bool --default=0 --exit-code GIT_TEST_ENV_HELPER 2>err && + test-tool -C cycle env-helper --type=bool --default=0 --exit-code GIT_TEST_ENV_HELPER 2>err && grep "exceeded maximum include depth" err ' 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..04b811622b 100755 --- a/t/t0066-dir-iterator.sh +++ b/t/t0066-dir-iterator.sh @@ -110,7 +110,9 @@ test_expect_success SYMLINKS 'setup dirs with symlinks' ' 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 ../../ dir5/a/b/f && + + ln -s dir4 dir6 ' test_expect_success SYMLINKS 'dir-iterator should not follow symlinks by default' ' @@ -146,4 +148,27 @@ test_expect_success SYMLINKS 'dir-iterator should follow symlinks w/ follow flag test_cmp expected-follow-sorted-output actual-follow-sorted-output ' +test_expect_success SYMLINKS 'dir-iterator does not resolve top-level symlinks' ' + test_must_fail test-tool dir-iterator ./dir6 >out && + + grep "ENOTDIR" out +' + +test_expect_success SYMLINKS 'dir-iterator resolves top-level symlinks w/ follow flag' ' + cat >expected-follow-sorted-output <<-EOF && + [d] (a) [a] ./dir6/a + [d] (a/f) [f] ./dir6/a/f + [d] (a/f/c) [c] ./dir6/a/f/c + [d] (b) [b] ./dir6/b + [d] (b/c) [c] ./dir6/b/c + [f] (a/d) [d] ./dir6/a/d + [f] (a/e) [e] ./dir6/a/e + EOF + + test-tool dir-iterator --follow-symlinks ./dir6 >out && + sort out >actual-follow-sorted-output && + + test_cmp expected-follow-sorted-output actual-follow-sorted-output +' + test_done diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh index 23b8942edb..2d875b17d8 100755 --- a/t/t1006-cat-file.sh +++ b/t/t1006-cat-file.sh @@ -292,8 +292,8 @@ commit_message="Initial commit" commit_sha1=$(echo_without_newline "$commit_message" | git commit-tree $tree_sha1) commit_size=$(($(test_oid hexsz) + 137)) commit_content="tree $tree_sha1 -author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> 0000000000 +0000 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 0000000000 +0000 +author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> 0 +0000 +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 0 +0000 $commit_message" @@ -304,7 +304,7 @@ type blob tag hellotag tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" tag_description="This is a tag" -tag_content="$tag_header_without_timestamp 0000000000 +0000 +tag_content="$tag_header_without_timestamp 0 +0000 $tag_description" diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh index ac5ad8c740..ac3d173767 100755 --- a/t/t1007-hash-object.sh +++ b/t/t1007-hash-object.sh @@ -203,23 +203,34 @@ done test_expect_success 'too-short tree' ' echo abc >malformed-tree && test_must_fail git hash-object -t tree malformed-tree 2>err && - test_i18ngrep "too-short tree object" err + grep "too-short tree object" err ' test_expect_success 'malformed mode in tree' ' - hex_sha1=$(echo foo | git hash-object --stdin -w) && - bin_sha1=$(echo $hex_sha1 | hex2oct) && - printf "9100644 \0$bin_sha1" >tree-with-malformed-mode && + hex_oid=$(echo foo | git hash-object --stdin -w) && + bin_oid=$(echo $hex_oid | hex2oct) && + printf "9100644 \0$bin_oid" >tree-with-malformed-mode && test_must_fail git hash-object -t tree tree-with-malformed-mode 2>err && - test_i18ngrep "malformed mode in tree entry" err + grep "malformed mode in tree entry" err ' test_expect_success 'empty filename in tree' ' - hex_sha1=$(echo foo | git hash-object --stdin -w) && - bin_sha1=$(echo $hex_sha1 | hex2oct) && - printf "100644 \0$bin_sha1" >tree-with-empty-filename && + hex_oid=$(echo foo | git hash-object --stdin -w) && + bin_oid=$(echo $hex_oid | hex2oct) && + printf "100644 \0$bin_oid" >tree-with-empty-filename && test_must_fail git hash-object -t tree tree-with-empty-filename 2>err && - test_i18ngrep "empty filename in tree entry" err + grep "empty filename in tree entry" err +' + +test_expect_success 'duplicate filename in tree' ' + hex_oid=$(echo foo | git hash-object --stdin -w) && + bin_oid=$(echo $hex_oid | hex2oct) && + { + printf "100644 file\0$bin_oid" && + printf "100644 file\0$bin_oid" + } >tree-with-duplicate-filename && + test_must_fail git hash-object -t tree tree-with-duplicate-filename 2>err && + grep "duplicateEntries" err ' test_expect_success 'corrupt commit' ' 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/t1450-fsck.sh b/t/t1450-fsck.sh index de0f6d5e7f..fdb886dfe4 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -212,7 +212,7 @@ test_expect_success 'email without @ is okay' ' test_expect_success 'email with embedded > is not okay' ' git cat-file commit HEAD >basis && sed "s/@[a-z]/&>/" basis >bad-email && - new=$(git hash-object -t commit -w --stdin <bad-email) && + new=$(git hash-object --literally -t commit -w --stdin <bad-email) && test_when_finished "remove_object $new" && git update-ref refs/heads/bogus "$new" && test_when_finished "git update-ref -d refs/heads/bogus" && @@ -223,7 +223,7 @@ test_expect_success 'email with embedded > is not okay' ' test_expect_success 'missing < email delimiter is reported nicely' ' git cat-file commit HEAD >basis && sed "s/<//" basis >bad-email-2 && - new=$(git hash-object -t commit -w --stdin <bad-email-2) && + new=$(git hash-object --literally -t commit -w --stdin <bad-email-2) && test_when_finished "remove_object $new" && git update-ref refs/heads/bogus "$new" && test_when_finished "git update-ref -d refs/heads/bogus" && @@ -234,7 +234,7 @@ test_expect_success 'missing < email delimiter is reported nicely' ' test_expect_success 'missing email is reported nicely' ' git cat-file commit HEAD >basis && sed "s/[a-z]* <[^>]*>//" basis >bad-email-3 && - new=$(git hash-object -t commit -w --stdin <bad-email-3) && + new=$(git hash-object --literally -t commit -w --stdin <bad-email-3) && test_when_finished "remove_object $new" && git update-ref refs/heads/bogus "$new" && test_when_finished "git update-ref -d refs/heads/bogus" && @@ -245,7 +245,7 @@ test_expect_success 'missing email is reported nicely' ' test_expect_success '> in name is reported' ' git cat-file commit HEAD >basis && sed "s/ </> </" basis >bad-email-4 && - new=$(git hash-object -t commit -w --stdin <bad-email-4) && + new=$(git hash-object --literally -t commit -w --stdin <bad-email-4) && test_when_finished "remove_object $new" && git update-ref refs/heads/bogus "$new" && test_when_finished "git update-ref -d refs/heads/bogus" && @@ -258,7 +258,7 @@ test_expect_success 'integer overflow in timestamps is reported' ' git cat-file commit HEAD >basis && sed "s/^\\(author .*>\\) [0-9]*/\\1 18446744073709551617/" \ <basis >bad-timestamp && - new=$(git hash-object -t commit -w --stdin <bad-timestamp) && + new=$(git hash-object --literally -t commit -w --stdin <bad-timestamp) && test_when_finished "remove_object $new" && git update-ref refs/heads/bogus "$new" && test_when_finished "git update-ref -d refs/heads/bogus" && @@ -269,7 +269,7 @@ test_expect_success 'integer overflow in timestamps is reported' ' test_expect_success 'commit with NUL in header' ' git cat-file commit HEAD >basis && sed "s/author ./author Q/" <basis | q_to_nul >commit-NUL-header && - new=$(git hash-object -t commit -w --stdin <commit-NUL-header) && + new=$(git hash-object --literally -t commit -w --stdin <commit-NUL-header) && test_when_finished "remove_object $new" && git update-ref refs/heads/bogus "$new" && test_when_finished "git update-ref -d refs/heads/bogus" && @@ -292,7 +292,7 @@ test_expect_success 'tree object with duplicate entries' ' git cat-file tree $T && git cat-file tree $T ) | - git hash-object -w -t tree --stdin + git hash-object --literally -w -t tree --stdin ) && test_must_fail git fsck 2>out && test_i18ngrep "error in tree .*contains duplicate file entries" out @@ -426,7 +426,7 @@ test_expect_success 'tag with incorrect tag name & missing tagger' ' This is an invalid tag. EOF - tag=$(git hash-object -t tag -w --stdin <wrong-tag) && + tag=$(git hash-object --literally -t tag -w --stdin <wrong-tag) && test_when_finished "remove_object $tag" && echo $tag >.git/refs/tags/wrong && test_when_finished "git update-ref -d refs/tags/wrong" && @@ -558,7 +558,7 @@ test_expect_success 'rev-list --verify-objects with commit graph (parent)' ' test_expect_success 'force fsck to ignore double author' ' git cat-file commit HEAD >basis && sed "s/^author .*/&,&/" <basis | tr , \\n >multiple-authors && - new=$(git hash-object -t commit -w --stdin <multiple-authors) && + new=$(git hash-object --literally -t commit -w --stdin <multiple-authors) && test_when_finished "remove_object $new" && git update-ref refs/heads/bogus "$new" && test_when_finished "git update-ref -d refs/heads/bogus" && @@ -573,7 +573,7 @@ test_expect_success 'fsck notices blob entry pointing to null sha1' ' (git init null-blob && cd null-blob && sha=$(printf "100644 file$_bz$_bzoid" | - git hash-object -w --stdin -t tree) && + git hash-object --literally -w --stdin -t tree) && git fsck 2>out && test_i18ngrep "warning.*null sha1" out ) @@ -583,7 +583,7 @@ test_expect_success 'fsck notices submodule entry pointing to null sha1' ' (git init null-commit && cd null-commit && sha=$(printf "160000 submodule$_bz$_bzoid" | - git hash-object -w --stdin -t tree) && + git hash-object --literally -w --stdin -t tree) && git fsck 2>out && test_i18ngrep "warning.*null sha1" out ) @@ -648,7 +648,7 @@ test_expect_success 'NUL in commit' ' git commit --allow-empty -m "initial commitQNUL after message" && git cat-file commit HEAD >original && q_to_nul <original >munged && - git hash-object -w -t commit --stdin <munged >name && + git hash-object --literally -w -t commit --stdin <munged >name && git branch bad $(cat name) && test_must_fail git -c fsck.nulInCommit=error fsck 2>warn.1 && @@ -794,8 +794,8 @@ test_expect_success 'fsck errors in packed objects' ' git cat-file commit HEAD >basis && sed "s/</one/" basis >one && sed "s/</foo/" basis >two && - one=$(git hash-object -t commit -w one) && - two=$(git hash-object -t commit -w two) && + one=$(git hash-object --literally -t commit -w one) && + two=$(git hash-object --literally -t commit -w two) && pack=$( { echo $one && diff --git a/t/t1451-fsck-buffer.sh b/t/t1451-fsck-buffer.sh new file mode 100755 index 0000000000..3413da40e4 --- /dev/null +++ b/t/t1451-fsck-buffer.sh @@ -0,0 +1,142 @@ +#!/bin/sh + +test_description='fsck on buffers without NUL termination + +The goal here is to make sure that the various fsck parsers never look +past the end of the buffer they are given, even when encountering broken +or truncated objects. + +We have to use "hash-object" for this because most code paths that read objects +append an extra NUL for safety after the buffer. But hash-object, since it is +reading straight from a file (and possibly even mmap-ing it) cannot always do +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 +# progressively, and then test new truncations on top of it. +reset () { + test_expect_success 'reset input to empty' ' + >base + ' +} + +add () { + content="$1" + type=${content%% *} + test_expect_success "add $type line" ' + echo "$content" >>base + ' +} + +check () { + type=$1 + fsck=$2 + content=$3 + test_expect_success "truncated $type ($fsck, \"$content\")" ' + # do not pipe into hash-object here; we want to increase + # the chance that it uses a fixed-size buffer or mmap, + # and a pipe would be read into a strbuf. + { + cat base && + echo "$content" + } >input && + test_must_fail git hash-object -t "$type" input 2>err && + grep "$fsck" err + ' +} + +test_expect_success 'create valid objects' ' + git commit --allow-empty -m foo && + commit=$(git rev-parse --verify HEAD) && + tree=$(git rev-parse --verify HEAD^{tree}) +' + +reset +check commit missingTree "" +check commit missingTree "tr" +check commit missingTree "tree" +check commit badTreeSha1 "tree " +check commit badTreeSha1 "tree 1234" +add "tree $tree" + +# these expect missingAuthor because "parent" is optional +check commit missingAuthor "" +check commit missingAuthor "par" +check commit missingAuthor "parent" +check commit badParentSha1 "parent " +check commit badParentSha1 "parent 1234" +add "parent $commit" + +check commit missingAuthor "" +check commit missingAuthor "au" +check commit missingAuthor "author" +ident_checks () { + check $1 missingEmail "$2 " + check $1 missingEmail "$2 name" + check $1 badEmail "$2 name <" + check $1 badEmail "$2 name <email" + check $1 missingSpaceBeforeDate "$2 name <email>" + check $1 badDate "$2 name <email> " + check $1 badDate "$2 name <email> 1234" + check $1 badTimezone "$2 name <email> 1234 " + check $1 badTimezone "$2 name <email> 1234 +" +} +ident_checks commit author +add "author name <email> 1234 +0000" + +check commit missingCommitter "" +check commit missingCommitter "co" +check commit missingCommitter "committer" +ident_checks commit committer +add "committer name <email> 1234 +0000" + +reset +check tag missingObject "" +check tag missingObject "obj" +check tag missingObject "object" +check tag badObjectSha1 "object " +check tag badObjectSha1 "object 1234" +add "object $commit" + +check tag missingType "" +check tag missingType "ty" +check tag missingType "type" +check tag badType "type " +check tag badType "type com" +add "type commit" + +check tag missingTagEntry "" +check tag missingTagEntry "ta" +check tag missingTagEntry "tag" +check tag badTagName "tag " +add "tag foo" + +check tag missingTagger "" +check tag missingTagger "ta" +check tag missingTagger "tagger" +ident_checks tag tagger + +# trees are a binary format and can't use our earlier helpers +test_expect_success 'truncated tree (short hash)' ' + printf "100644 foo\0\1\1\1\1" >input && + test_must_fail git hash-object -t tree input 2>err && + grep badTree err +' + +test_expect_success 'truncated tree (missing nul)' ' + # these two things are indistinguishable to the parser. The important + # thing about this is example is that there are enough bytes to + # make up a hash, and that there is no NUL (and we confirm that the + # parser does not walk past the end of the buffer). + printf "100644 a long filename, or a hash with missing nul?" >input && + test_must_fail git hash-object -t tree input 2>err && + grep badTree err +' + +test_done diff --git a/t/t1600-index.sh b/t/t1600-index.sh index 0ebbae1305..9368d82f7d 100755 --- a/t/t1600-index.sh +++ b/t/t1600-index.sh @@ -88,6 +88,7 @@ test_expect_success 'index.skipHash config option' ' git -c protocol.file.allow=always submodule add ./ sub && git config index.skipHash false && git -C sub config index.skipHash true && + rm -f .git/modules/sub/index && >sub/file && git -C sub add a && test_trailing_hash .git/modules/sub/index >hash && 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/t3104-ls-tree-format.sh b/t/t3104-ls-tree-format.sh index 74053978f4..3adb206a93 100755 --- a/t/t3104-ls-tree-format.sh +++ b/t/t3104-ls-tree-format.sh @@ -35,6 +35,12 @@ test_ls_tree_format () { ' } +test_expect_success "ls-tree --format='%(path) %(path) %(path)' HEAD top-file" ' + git ls-tree --format="%(path) %(path) %(path)" HEAD top-file.t >actual && + echo top-file.t top-file.t top-file.t >expect && + test_cmp expect actual +' + test_ls_tree_format \ "%(objectmode) %(objecttype) %(objectname)%x09%(path)" \ "" 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/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/t3422-rebase-incompatible-options.sh b/t/t3422-rebase-incompatible-options.sh index 6dabb05a2a..4711b37a28 100755 --- a/t/t3422-rebase-incompatible-options.sh +++ b/t/t3422-rebase-incompatible-options.sh @@ -25,11 +25,11 @@ test_expect_success 'setup' ' ' # -# Rebase has lots of useful options like --whitepsace=fix, which are -# actually all built in terms of flags to git-am. Since neither -# --merge nor --interactive (nor any options that imply those two) use -# git-am, using them together will result in flags like --whitespace=fix -# being ignored. Make sure rebase warns the user and aborts instead. +# Rebase has a couple options which are specific to the apply backend, +# and several options which are specific to the merge backend. Flags +# from the different sets cannot work together, and we do not want to +# just ignore one of the sets of flags. Make sure rebase warns the +# user and aborts instead. # test_rebase_am_only () { @@ -50,6 +50,11 @@ test_rebase_am_only () { test_must_fail git rebase $opt --strategy-option=ours A " + test_expect_success "$opt incompatible with --autosquash" " + git checkout B^0 && + test_must_fail git rebase $opt --autosquash A + " + test_expect_success "$opt incompatible with --interactive" " git checkout B^0 && test_must_fail git rebase $opt --interactive A @@ -60,9 +65,65 @@ test_rebase_am_only () { test_must_fail git rebase $opt --exec 'true' A " + test_expect_success "$opt incompatible with --keep-empty" " + git checkout B^0 && + test_must_fail git rebase $opt --keep-empty A + " + + test_expect_success "$opt incompatible with --empty=..." " + git checkout B^0 && + test_must_fail git rebase $opt --empty=ask A + " + + test_expect_success "$opt incompatible with --no-reapply-cherry-picks" " + git checkout B^0 && + test_must_fail git rebase $opt --no-reapply-cherry-picks A + " + + test_expect_success "$opt incompatible with --reapply-cherry-picks" " + git checkout B^0 && + test_must_fail git rebase $opt --reapply-cherry-picks A + " + + test_expect_success "$opt incompatible with --update-refs" " + git checkout B^0 && + test_must_fail git rebase $opt --update-refs A + " + + test_expect_success "$opt incompatible with --root without --onto" " + git checkout B^0 && + test_must_fail git rebase $opt --root A + " + + test_expect_success "$opt incompatible with rebase.autosquash" " + git checkout B^0 && + test_must_fail git -c rebase.autosquash=true rebase $opt A 2>err && + grep -e --no-autosquash err + " + + test_expect_success "$opt incompatible with rebase.updateRefs" " + git checkout B^0 && + test_must_fail git -c rebase.updateRefs=true rebase $opt A 2>err && + grep -e --no-update-refs err + " + + test_expect_success "$opt okay with overridden rebase.autosquash" " + test_when_finished \"git reset --hard B^0\" && + git checkout B^0 && + git -c rebase.autosquash=true rebase --no-autosquash $opt A + " + + test_expect_success "$opt okay with overridden rebase.updateRefs" " + test_when_finished \"git reset --hard B^0\" && + git checkout B^0 && + git -c rebase.updateRefs=true rebase --no-update-refs $opt A + " } +# Check options which imply --apply test_rebase_am_only --whitespace=fix test_rebase_am_only -C4 +# Also check an explicit --apply +test_rebase_am_only --apply test_done 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/t4054-diff-bogus-tree.sh b/t/t4054-diff-bogus-tree.sh index 294fb55313..05c88f8cdf 100755 --- a/t/t4054-diff-bogus-tree.sh +++ b/t/t4054-diff-bogus-tree.sh @@ -10,7 +10,7 @@ test_expect_success 'create bogus tree' ' bogus_tree=$( printf "100644 fooQ$name" | q_to_nul | - git hash-object -w --stdin -t tree + git hash-object --literally -w --stdin -t tree ) ' diff --git a/t/t4058-diff-duplicates.sh b/t/t4058-diff-duplicates.sh index 54614b814d..2501c89c1c 100755 --- a/t/t4058-diff-duplicates.sh +++ b/t/t4058-diff-duplicates.sh @@ -29,7 +29,7 @@ make_tree () { make_tree_entry "$1" "$2" "$3" shift; shift; shift done | - git hash-object -w -t tree --stdin + git hash-object --literally -w -t tree --stdin } # this is kind of a convoluted setup, but matches 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/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh index 3e7ad9d5de..4cf8a77667 100755 --- a/t/t4205-log-pretty-formats.sh +++ b/t/t4205-log-pretty-formats.sh @@ -1094,4 +1094,31 @@ test_expect_success EXPENSIVE,SIZE_T_IS_64BIT 'log --pretty with huge commit mes test_cmp expect error ' +# pretty-formats note wide char limitations, and add tests +test_expect_failure 'wide and decomposed characters column counting' ' + +# from t/lib-unicode-nfc-nfd.sh hex values converted to octal + utf8_nfc=$(printf "\303\251") && # e acute combined. + utf8_nfd=$(printf "\145\314\201") && # e with a combining acute (i.e. decomposed) + utf8_emoji=$(printf "\360\237\221\250") && + +# replacement character when requesting a wide char fits in a single display colum. +# "half wide" alternative could be a plain ASCII dot `.` + utf8_vert_ell=$(printf "\342\213\256") && + +# use ${xxx} here! + nfc10="${utf8_nfc}${utf8_nfc}${utf8_nfc}${utf8_nfc}${utf8_nfc}${utf8_nfc}${utf8_nfc}${utf8_nfc}${utf8_nfc}${utf8_nfc}" && + nfd10="${utf8_nfd}${utf8_nfd}${utf8_nfd}${utf8_nfd}${utf8_nfd}${utf8_nfd}${utf8_nfd}${utf8_nfd}${utf8_nfd}${utf8_nfd}" && + emoji5="${utf8_emoji}${utf8_emoji}${utf8_emoji}${utf8_emoji}${utf8_emoji}" && +# emoji5 uses 10 display columns + + test_commit "abcdefghij" && + test_commit --no-tag "${nfc10}" && + test_commit --no-tag "${nfd10}" && + test_commit --no-tag "${emoji5}" && + printf "${utf8_emoji}..${utf8_emoji}${utf8_vert_ell}\n${utf8_nfd}..${utf8_nfd}${utf8_nfd}\n${utf8_nfc}..${utf8_nfc}${utf8_nfc}\na..ij\n" >expected && + git log --format="%<(5,mtrunc)%s" -4 >actual && + test_cmp expected actual +' + test_done diff --git a/t/t4212-log-corrupt.sh b/t/t4212-log-corrupt.sh index 30a219894b..e89e1f54b6 100755 --- a/t/t4212-log-corrupt.sh +++ b/t/t4212-log-corrupt.sh @@ -10,7 +10,7 @@ test_expect_success 'setup' ' git cat-file commit HEAD | sed "/^author /s/>/>-<>/" >broken_email.commit && - git hash-object -w -t commit broken_email.commit >broken_email.hash && + git hash-object --literally -w -t commit broken_email.commit >broken_email.hash && git update-ref refs/heads/broken_email $(cat broken_email.hash) ' @@ -46,7 +46,7 @@ test_expect_success 'git log --format with broken author email' ' munge_author_date () { git cat-file commit "$1" >commit.orig && sed "s/^\(author .*>\) [0-9]*/\1 $2/" <commit.orig >commit.munge && - git hash-object -w -t commit commit.munge + git hash-object --literally -w -t commit commit.munge } test_expect_success 'unparsable dates produce sentinel value' ' 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..eb3214bc17 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -402,11 +402,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 b0095ab41d..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' ' @@ -263,7 +265,7 @@ tag guten tag This is an invalid tag. EOF - tag=$(git hash-object -t tag -w --stdin <wrong-tag) && + tag=$(git hash-object -t tag -w --stdin --literally <wrong-tag) && pack1=$(echo $tag $sha | git pack-objects tag-test) && echo remove tag object && thirtyeight=${tag#??} && 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/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 ac4099ca89..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' ' @@ -138,7 +139,7 @@ This commit object intentionally broken EOF test_expect_success 'setup bogus commit' ' - commit="$(git hash-object -t commit -w --stdin <bogus-commit)" + commit="$(git hash-object --literally -t commit -w --stdin <bogus-commit)" ' test_expect_success 'fsck with no skipList input' ' diff --git a/t/t5506-remote-groups.sh b/t/t5506-remote-groups.sh index 5bac03ede8..0e176175a3 100755 --- a/t/t5506-remote-groups.sh +++ b/t/t5506-remote-groups.sh @@ -99,4 +99,13 @@ test_expect_success 'updating remote name updates that remote' ' ! repo_fetched two ' +test_expect_success 'updating group in parallel with a duplicate remote does not fail (fetch)' ' + mark fetch-group-duplicate && + update_repo one && + git config --add remotes.duplicate one && + git config --add remotes.duplicate one && + git -c fetch.parallel=2 remote update duplicate && + repo_fetched one +' + test_done 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/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/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/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh index b33cd4afca..e4db7513f4 100755 --- a/t/t5702-protocol-v2.sh +++ b/t/t5702-protocol-v2.sh @@ -1114,7 +1114,7 @@ test_expect_success 'packfile-uri with transfer.fsckobjects fails on bad object' This commit object intentionally broken EOF - BOGUS=$(git -C "$P" hash-object -t commit -w --stdin <bogus-commit) && + BOGUS=$(git -C "$P" hash-object -t commit -w --stdin --literally <bogus-commit) && git -C "$P" branch bogus-branch "$BOGUS" && echo my-blob >"$P/my-blob" && 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/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 2ae1fc721b..c466fd989f 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -606,7 +606,7 @@ test_expect_success 'create tag without tagger' ' git tag -a -m "Broken tag" taggerless && git tag -f taggerless $(git cat-file tag taggerless | sed -e "/^tagger /d" | - git hash-object --stdin -w -t tag) + git hash-object --literally --stdin -w -t tag) ' test_atom refs/tags/taggerless type 'commit' diff --git a/t/t6426-merge-skip-unneeded-updates.sh b/t/t6426-merge-skip-unneeded-updates.sh index 2bb8e7f09b..fd21c1a486 100755 --- a/t/t6426-merge-skip-unneeded-updates.sh +++ b/t/t6426-merge-skip-unneeded-updates.sh @@ -378,42 +378,30 @@ test_expect_success '2c: Modify b & add c VS rename b->c' ' test_i18ngrep "CONFLICT (.*/add):" out && test_must_be_empty err && - # Make sure c WAS updated + git ls-files -s >index_files && + test_line_count = 2 index_files && + + # Ensure b was removed + test_path_is_missing b && + + # Make sure c WAS updated... test-tool chmtime --get c >new-mtime && - test $(cat old-mtime) -lt $(cat new-mtime) - - # FIXME: rename/add conflicts are horribly broken right now; - # when I get back to my patch series fixing it and - # rename/rename(2to1) conflicts to bring them in line with - # how add/add conflicts behave, then checks like the below - # could be added. But that patch series is waiting until - # the rename-directory-detection series lands, which this - # is part of. And in the mean time, I do not want to further - # enforce broken behavior. So for now, the main test is the - # one above that err is an empty file. - - #git ls-files -s >index_files && - #test_line_count = 2 index_files && - - #git rev-parse >actual :2:c :3:c && - #git rev-parse >expect A:b A:c && - #test_cmp expect actual && - - #git cat-file -p A:b >>merged && - #git cat-file -p A:c >>merge-me && - #>empty && - #test_must_fail git merge-file \ - # -L "Temporary merge branch 1" \ - # -L "" \ - # -L "Temporary merge branch 2" \ - # merged empty merge-me && - #sed -e "s/^\([<=>]\)/\1\1\1/" merged >merged-internal && - - #git hash-object c >actual && - #git hash-object merged-internal >expect && - #test_cmp expect actual && - - #test_path_is_missing b + test $(cat old-mtime) -lt $(cat new-mtime) && + + # ...and has correct index entries and working tree contents + git rev-parse >actual :2:c :3:c && + git rev-parse >expect A:c A:b && + test_cmp expect actual && + + git cat-file -p A:b >>merge-me && + git cat-file -p A:c >>merged && + >empty && + test_must_fail git merge-file \ + -L "HEAD" \ + -L "" \ + -L "B^0" \ + merged empty merge-me && + test_cmp merged c ) ' 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/t7030-verify-tag.sh b/t/t7030-verify-tag.sh index 10faa64515..6f526c37c2 100755 --- a/t/t7030-verify-tag.sh +++ b/t/t7030-verify-tag.sh @@ -115,7 +115,7 @@ test_expect_success GPGSM 'verify and show signatures x509 with high minTrustLev test_expect_success GPG 'detect fudged signature' ' git cat-file tag seventh-signed >raw && - sed -e "/^tag / s/seventh/7th forged/" raw >forged1 && + sed -e "/^tag / s/seventh/7th-forged/" raw >forged1 && git hash-object -w -t tag forged1 >forged1.tag && test_must_fail git verify-tag $(cat forged1.tag) 2>actual1 && grep "BAD signature from" actual1 && diff --git a/t/t7031-verify-tag-signed-ssh.sh b/t/t7031-verify-tag-signed-ssh.sh index 1cb36b9ab8..36eb86a4b1 100755 --- a/t/t7031-verify-tag-signed-ssh.sh +++ b/t/t7031-verify-tag-signed-ssh.sh @@ -125,7 +125,7 @@ test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-tag failes with tag date ou test_expect_success GPGSSH 'detect fudged ssh signature' ' test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && git cat-file tag seventh-signed >raw && - sed -e "/^tag / s/seventh/7th forged/" raw >forged1 && + sed -e "/^tag / s/seventh/7th-forged/" raw >forged1 && git hash-object -w -t tag forged1 >forged1.tag && test_must_fail git verify-tag $(cat forged1.tag) 2>actual1 && grep "${GPGSSH_BAD_SIGNATURE}" actual1 && 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/t7509-commit-authorship.sh b/t/t7509-commit-authorship.sh index 21c668f75e..5d890949f7 100755 --- a/t/t7509-commit-authorship.sh +++ b/t/t7509-commit-authorship.sh @@ -105,7 +105,7 @@ test_expect_success '--amend option with empty author' ' test_expect_success '--amend option with missing author' ' git cat-file commit Initial >tmp && sed "s/author [^<]* </author </" tmp >malformed && - sha=$(git hash-object -t commit -w malformed) && + sha=$(git hash-object --literally -t commit -w malformed) && test_when_finished "remove_object $sha" && git checkout $sha && test_when_finished "git checkout Initial" && diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh index 8593b7e3cb..bc7a31ba3e 100755 --- a/t/t7510-signed-commit.sh +++ b/t/t7510-signed-commit.sh @@ -202,7 +202,7 @@ test_expect_success GPG 'detect fudged signature with NUL' ' git cat-file commit seventh-signed >raw && cat raw >forged2 && echo Qwik | tr "Q" "\000" >>forged2 && - git hash-object -w -t commit forged2 >forged2.commit && + git hash-object --literally -w -t commit forged2 >forged2.commit && test_must_fail git verify-commit $(cat forged2.commit) && git show --pretty=short --show-signature $(cat forged2.commit) >actual2 && grep "BAD signature from" actual2 && diff --git a/t/t7528-signed-commit-ssh.sh b/t/t7528-signed-commit-ssh.sh index f47e995179..065f780636 100755 --- a/t/t7528-signed-commit-ssh.sh +++ b/t/t7528-signed-commit-ssh.sh @@ -270,7 +270,7 @@ test_expect_success GPGSSH 'detect fudged signature with NUL' ' git cat-file commit seventh-signed >raw && cat raw >forged2 && echo Qwik | tr "Q" "\000" >>forged2 && - git hash-object -w -t commit forged2 >forged2.commit && + git hash-object --literally -w -t commit forged2 >forged2.commit && test_must_fail git verify-commit $(cat forged2.commit) && git show --pretty=short --show-signature $(cat forged2.commit) >actual2 && grep "${GPGSSH_BAD_SIGNATURE}" actual2 && 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/t8003-blame-corner-cases.sh b/t/t8003-blame-corner-cases.sh index d751d48b7d..8bcd39e81b 100755 --- a/t/t8003-blame-corner-cases.sh +++ b/t/t8003-blame-corner-cases.sh @@ -201,7 +201,7 @@ committer David Reiss <dreiss@facebook.com> 1234567890 +0000 some message EOF - COMMIT=$(git hash-object -t commit -w badcommit) && + COMMIT=$(git hash-object --literally -t commit -w badcommit) && git --no-pager blame $COMMIT -- uno >/dev/null ' diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index 1130ef21b3..323952a572 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -2334,6 +2334,12 @@ test_expect_success $PREREQ 'test that send-email works outside a repo' ' "$(pwd)/0001-add-main.patch" ' +test_expect_success $PREREQ 'send-email relays -v 3 to format-patch' ' + test_when_finished "rm -f out" && + git send-email --dry-run -v 3 -1 >out && + grep "PATCH v3" out +' + test_expect_success $PREREQ 'test that sendmail config is rejected' ' test_config sendmail.program sendmail && test_must_fail git send-email \ 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/t9210-scalar.sh b/t/t9210-scalar.sh index 25f500cf68..4432a30d10 100755 --- a/t/t9210-scalar.sh +++ b/t/t9210-scalar.sh @@ -104,6 +104,13 @@ test_expect_success FSMONITOR_DAEMON 'scalar register starts fsmon daemon' ' test_cmp_config -C test/src true core.fsmonitor ' +test_expect_success 'scalar register warns when background maintenance fails' ' + git init register-repo && + GIT_TEST_MAINT_SCHEDULER="crontab:false,launchctl:false,schtasks:false" \ + scalar register register-repo 2>err && + grep "could not turn on maintenance" err +' + test_expect_success 'scalar unregister' ' git init vanish/src && scalar register vanish/src && diff --git a/t/t9211-scalar-clone.sh b/t/t9211-scalar-clone.sh index dd33d87e9b..872ad1c9c2 100755 --- a/t/t9211-scalar-clone.sh +++ b/t/t9211-scalar-clone.sh @@ -3,6 +3,7 @@ test_description='test the `scalar clone` subcommand' . ./test-lib.sh +. "${TEST_DIRECTORY}/lib-terminal.sh" GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab cron.txt,launchctl:true,schtasks:true" export GIT_TEST_MAINT_SCHEDULER @@ -148,4 +149,35 @@ test_expect_success '--no-single-branch clones all branches' ' cleanup_clone $enlistment ' +test_expect_success TTY 'progress with tty' ' + enlistment=progress1 && + + test_config -C to-clone uploadpack.allowfilter true && + test_config -C to-clone uploadpack.allowanysha1inwant true && + + test_terminal env GIT_PROGRESS_DELAY=0 \ + scalar clone "file://$(pwd)/to-clone" "$enlistment" 2>stderr && + grep "Enumerating objects" stderr >actual && + test_line_count = 2 actual && + cleanup_clone $enlistment +' + +test_expect_success 'progress without tty' ' + enlistment=progress2 && + + test_config -C to-clone uploadpack.allowfilter true && + test_config -C to-clone uploadpack.allowanysha1inwant true && + + GIT_PROGRESS_DELAY=0 scalar clone "file://$(pwd)/to-clone" "$enlistment" 2>stderr && + ! grep "Enumerating objects" stderr && + ! grep "Updating files" stderr && + cleanup_clone $enlistment +' + +test_expect_success 'scalar clone warns when background maintenance fails' ' + GIT_TEST_MAINT_SCHEDULER="crontab:false,launchctl:false,schtasks:false" \ + scalar clone "file://$(pwd)/to-clone" maint-fail 2>err && + grep "could not turn on maintenance" err +' + test_done diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh index ff21a12ee6..26c25c0eb2 100755 --- a/t/t9350-fast-export.sh +++ b/t/t9350-fast-export.sh @@ -373,7 +373,7 @@ EOF test_expect_success 'cope with tagger-less tags' ' - TAG=$(git hash-object -t tag -w tag-content) && + TAG=$(git hash-object --literally -t tag -w tag-content) && git update-ref refs/tags/sonnenschein $TAG && git fast-export -C -C --signed-tags=strip --all > output && test $(grep -c "^tag " output) = 4 && diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 1b8d4344a5..58cfd2f1fd 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -1016,7 +1016,7 @@ test_must_fail_acceptable () { fi case "$1" in - git|__git*|test-tool|test_terminal) + git|__git*|scalar|test-tool|test_terminal) return 0 ;; *) @@ -1422,7 +1422,7 @@ test_bool_env () { BUG "test_bool_env requires two parameters (variable name and default value)" fi - git env--helper --type=bool --default="$2" --exit-code "$1" + test-tool env-helper --type=bool --default="$2" --exit-code "$1" ret=$? case $ret in 0|1) # unset or valid bool value @@ -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 4fab1c1984..d272cca008 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -1542,8 +1542,8 @@ then # Normalize with test_bool_env passes_sanitize_leak= - # We need to see TEST_PASSES_SANITIZE_LEAK in "git - # env--helper" (via test_bool_env) + # We need to see TEST_PASSES_SANITIZE_LEAK in "test-tool + # env-helper" (via test_bool_env) export TEST_PASSES_SANITIZE_LEAK if test_bool_env TEST_PASSES_SANITIZE_LEAK false then @@ -1682,7 +1682,7 @@ yes () { # The GIT_TEST_FAIL_PREREQS code hooks into test_set_prereq(), and # thus needs to be set up really early, and set an internal variable # for convenience so the hot test_set_prereq() codepath doesn't need -# to call "git env--helper" (via test_bool_env). Only do that work +# to call "test-tool env-helper" (via test_bool_env). Only do that work # if needed by seeing if GIT_TEST_FAIL_PREREQS is set at all. GIT_TEST_FAIL_PREREQS_INTERNAL= if test -n "$GIT_TEST_FAIL_PREREQS" @@ -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/unpack-trees.c b/unpack-trees.c index ea09023b01..3d05e45a27 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -1880,7 +1880,8 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options prepare_repo_settings(repo); if (repo->settings.command_requires_full_index) { ensure_full_index(o->src_index); - ensure_full_index(o->dst_index); + if (o->dst_index) + ensure_full_index(o->dst_index); } if (o->reset == UNPACK_RESET_OVERWRITE_UNTRACKED && @@ -1904,7 +1905,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options populate_from_existing_patterns(o, &pl); } - memset(&o->result, 0, sizeof(o->result)); + index_state_init(&o->result, o->src_index->repo); o->result.initialized = 1; o->result.timestamp.sec = o->src_index->timestamp.sec; o->result.timestamp.nsec = o->src_index->timestamp.nsec; diff --git a/userdiff.c b/userdiff.c index e25356a061..94cca1a2a8 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]*\\([^;]*)$", @@ -413,7 +413,7 @@ struct userdiff_driver *userdiff_find_by_path(struct index_state *istate, check = attr_check_initl("diff", NULL); if (!path) return NULL; - git_check_attr(istate, path, check); + git_check_attr(istate, NULL, path, check); if (ATTR_TRUE(check->items[0].value)) return &driver_true; @@ -79,7 +79,7 @@ unsigned whitespace_rule(struct index_state *istate, const char *pathname) if (!attr_whitespace_rule) attr_whitespace_rule = attr_check_initl("whitespace", NULL); - git_check_attr(istate, pathname, attr_whitespace_rule); + git_check_attr(istate, NULL, pathname, attr_whitespace_rule); value = attr_whitespace_rule->items[0].value; if (ATTR_TRUE(value)) { /* true (whitespace) */ |