diff options
198 files changed, 4243 insertions, 1392 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 73856bafc9..47876a4f02 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -81,44 +81,21 @@ jobs: if: needs.ci-config.outputs.enabled == 'yes' runs-on: windows-latest steps: - - uses: actions/checkout@v1 - - name: download git-sdk-64-minimal - shell: bash - run: | - ## Get artifact - urlbase=https://dev.azure.com/git-for-windows/git/_apis/build/builds - id=$(curl "$urlbase?definitions=22&statusFilter=completed&resultFilter=succeeded&\$top=1" | - jq -r ".value[] | .id") - download_url="$(curl "$urlbase/$id/artifacts" | - jq -r '.value[] | select(.name == "git-sdk-64-minimal").resource.downloadUrl')" - curl --connect-timeout 10 --retry 5 --retry-delay 0 --retry-max-time 240 \ - -o artifacts.zip "$download_url" - - ## Unzip and remove the artifact - unzip artifacts.zip - rm artifacts.zip + - uses: actions/checkout@v2 + - uses: git-for-windows/setup-git-for-windows-sdk@v1 - name: build - shell: powershell + shell: bash env: HOME: ${{runner.workspace}} - MSYSTEM: MINGW64 NO_PERL: 1 - run: | - & .\git-sdk-64-minimal\usr\bin\bash.exe -lc @" - printf '%s\n' /git-sdk-64-minimal/ >>.git/info/exclude - - ci/make-test-artifacts.sh artifacts - "@ - - name: upload build artifacts - uses: actions/upload-artifact@v1 + run: ci/make-test-artifacts.sh artifacts + - name: zip up tracked files + run: git archive -o artifacts/tracked.tar.gz HEAD + - name: upload tracked files and build artifacts + uses: actions/upload-artifact@v2 with: name: windows-artifacts path: artifacts - - name: upload git-sdk-64-minimal - uses: actions/upload-artifact@v1 - with: - name: git-sdk-64-minimal - path: git-sdk-64-minimal windows-test: runs-on: windows-latest needs: [windows-build] @@ -127,37 +104,25 @@ jobs: matrix: nr: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] steps: - - uses: actions/checkout@v1 - - name: download build artifacts - uses: actions/download-artifact@v1 + - name: download tracked files and build artifacts + uses: actions/download-artifact@v2 with: name: windows-artifacts path: ${{github.workspace}} - - name: extract build artifacts + - name: extract tracked files and build artifacts shell: bash - run: tar xf artifacts.tar.gz - - name: download git-sdk-64-minimal - uses: actions/download-artifact@v1 - with: - name: git-sdk-64-minimal - path: ${{github.workspace}}/git-sdk-64-minimal/ + run: tar xf artifacts.tar.gz && tar xf tracked.tar.gz + - uses: git-for-windows/setup-git-for-windows-sdk@v1 - name: test - shell: powershell - run: | - & .\git-sdk-64-minimal\usr\bin\bash.exe -lc @" - # Let Git ignore the SDK - printf '%s\n' /git-sdk-64-minimal/ >>.git/info/exclude - - ci/run-test-slice.sh ${{matrix.nr}} 10 - "@ + shell: bash + run: ci/run-test-slice.sh ${{matrix.nr}} 10 - name: ci/print-test-failures.sh if: failure() - shell: powershell - run: | - & .\git-sdk-64-minimal\usr\bin\bash.exe -lc ci/print-test-failures.sh + shell: bash + run: ci/print-test-failures.sh - name: Upload failed tests' directories if: failure() && env.FAILED_TEST_ARTIFACTS != '' - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 with: name: failed-tests-windows path: ${{env.FAILED_TEST_ARTIFACTS}} @@ -165,27 +130,12 @@ jobs: needs: ci-config if: needs.ci-config.outputs.enabled == 'yes' env: - MSYSTEM: MINGW64 NO_PERL: 1 GIT_CONFIG_PARAMETERS: "'user.name=CI' 'user.email=ci@git'" runs-on: windows-latest steps: - - uses: actions/checkout@v1 - - name: download git-sdk-64-minimal - shell: bash - run: | - ## Get artifact - urlbase=https://dev.azure.com/git-for-windows/git/_apis/build/builds - id=$(curl "$urlbase?definitions=22&statusFilter=completed&resultFilter=succeeded&\$top=1" | - jq -r ".value[] | .id") - download_url="$(curl "$urlbase/$id/artifacts" | - jq -r '.value[] | select(.name == "git-sdk-64-minimal").resource.downloadUrl')" - curl --connect-timeout 10 --retry 5 --retry-delay 0 --retry-max-time 240 \ - -o artifacts.zip "$download_url" - - ## Unzip and remove the artifact - unzip artifacts.zip - rm artifacts.zip + - uses: actions/checkout@v2 + - uses: git-for-windows/setup-git-for-windows-sdk@v1 - name: initialize vcpkg uses: actions/checkout@v2 with: @@ -203,75 +153,60 @@ jobs: - name: add msbuild to PATH uses: microsoft/setup-msbuild@v1 - name: copy dlls to root - shell: powershell - run: | - & compat\vcbuild\vcpkg_copy_dlls.bat release - if (!$?) { exit(1) } + shell: cmd + run: compat\vcbuild\vcpkg_copy_dlls.bat release - name: generate Visual Studio solution shell: bash run: | cmake `pwd`/contrib/buildsystems/ -DCMAKE_PREFIX_PATH=`pwd`/compat/vcbuild/vcpkg/installed/x64-windows \ - -DMSGFMT_EXE=`pwd`/git-sdk-64-minimal/mingw64/bin/msgfmt.exe -DPERL_TESTS=OFF -DPYTHON_TESTS=OFF -DCURL_NO_CURL_CMAKE=ON + -DNO_GETTEXT=YesPlease -DPERL_TESTS=OFF -DPYTHON_TESTS=OFF -DCURL_NO_CURL_CMAKE=ON - name: MSBuild run: msbuild git.sln -property:Configuration=Release -property:Platform=x64 -maxCpuCount:4 -property:PlatformToolset=v142 - name: bundle artifact tar - shell: powershell + shell: bash env: MSVC: 1 VCPKG_ROOT: ${{github.workspace}}\compat\vcbuild\vcpkg run: | - & git-sdk-64-minimal\usr\bin\bash.exe -lc @" - mkdir -p artifacts && - eval \"`$(make -n artifacts-tar INCLUDE_DLLS_IN_ARTIFACTS=YesPlease ARTIFACTS_DIRECTORY=artifacts 2>&1 | grep ^tar)\" - "@ - - name: upload build artifacts - uses: actions/upload-artifact@v1 + mkdir -p artifacts && + eval "$(make -n artifacts-tar INCLUDE_DLLS_IN_ARTIFACTS=YesPlease ARTIFACTS_DIRECTORY=artifacts NO_GETTEXT=YesPlease 2>&1 | grep ^tar)" + - name: zip up tracked files + run: git archive -o artifacts/tracked.tar.gz HEAD + - name: upload tracked files and build artifacts + uses: actions/upload-artifact@v2 with: name: vs-artifacts path: artifacts vs-test: runs-on: windows-latest - needs: [vs-build, windows-build] + needs: vs-build strategy: fail-fast: false matrix: nr: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] steps: - - uses: actions/checkout@v1 - - name: download git-sdk-64-minimal - uses: actions/download-artifact@v1 - with: - name: git-sdk-64-minimal - path: ${{github.workspace}}/git-sdk-64-minimal/ - - name: download build artifacts - uses: actions/download-artifact@v1 + - uses: git-for-windows/setup-git-for-windows-sdk@v1 + - name: download tracked files and build artifacts + uses: actions/download-artifact@v2 with: name: vs-artifacts path: ${{github.workspace}} - - name: extract build artifacts + - name: extract tracked files and build artifacts shell: bash - run: tar xf artifacts.tar.gz + run: tar xf artifacts.tar.gz && tar xf tracked.tar.gz - name: test - shell: powershell + shell: bash env: - MSYSTEM: MINGW64 NO_SVN_TESTS: 1 GIT_TEST_SKIP_REBASE_P: 1 - run: | - & .\git-sdk-64-minimal\usr\bin\bash.exe -lc @" - # Let Git ignore the SDK and the test-cache - printf '%s\n' /git-sdk-64-minimal/ /test-cache/ >>.git/info/exclude - - ci/run-test-slice.sh ${{matrix.nr}} 10 - "@ + run: ci/run-test-slice.sh ${{matrix.nr}} 10 - name: ci/print-test-failures.sh if: failure() - shell: powershell - run: | - & .\git-sdk-64-minimal\usr\bin\bash.exe -lc ci/print-test-failures.sh + shell: bash + run: ci/print-test-failures.sh - name: Upload failed tests' directories if: failure() && env.FAILED_TEST_ARTIFACTS != '' - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 with: name: failed-tests-windows path: ${{env.FAILED_TEST_ARTIFACTS}} @@ -302,14 +237,14 @@ jobs: jobname: ${{matrix.vector.jobname}} runs-on: ${{matrix.vector.pool}} steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - run: ci/install-dependencies.sh - run: ci/run-build-and-tests.sh - run: ci/print-test-failures.sh if: failure() - name: Upload failed tests' directories if: failure() && env.FAILED_TEST_ARTIFACTS != '' - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 with: name: failed-tests-${{matrix.vector.jobname}} path: ${{env.FAILED_TEST_ARTIFACTS}} @@ -336,7 +271,7 @@ jobs: if: failure() - name: Upload failed tests' directories if: failure() && env.FAILED_TEST_ARTIFACTS != '' - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 with: name: failed-tests-${{matrix.vector.jobname}} path: ${{env.FAILED_TEST_ARTIFACTS}} @@ -347,9 +282,29 @@ jobs: jobname: StaticAnalysis runs-on: ubuntu-18.04 steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - run: ci/install-dependencies.sh - run: ci/run-static-analysis.sh + sparse: + needs: ci-config + if: needs.ci-config.outputs.enabled == 'yes' + env: + jobname: sparse + runs-on: ubuntu-20.04 + steps: + - name: Download a current `sparse` package + # Ubuntu's `sparse` version is too old for us + uses: git-for-windows/get-azure-pipelines-artifact@v0 + with: + repository: git/git + definitionId: 10 + artifact: sparse-20.04 + - name: Install the current `sparse` package + run: sudo dpkg -i sparse-20.04/sparse_*.deb + - uses: actions/checkout@v2 + - name: Install other dependencies + run: ci/install-dependencies.sh + - run: make sparse documentation: needs: ci-config if: needs.ci-config.outputs.enabled == 'yes' @@ -357,6 +312,6 @@ jobs: jobname: Documentation runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - run: ci/install-dependencies.sh - run: ci/test-documentation.sh diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index e3af089ecf..711cb9171e 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines @@ -551,6 +551,51 @@ Writing Documentation: documentation, please see the documentation-related advice in the Documentation/SubmittingPatches file). + In order to ensure the documentation is inclusive, avoid assuming + that an unspecified example person is male or female, and think + twice before using "he", "him", "she", or "her". Here are some + tips to avoid use of gendered pronouns: + + - Prefer succinctness and matter-of-factly describing functionality + in the abstract. E.g. + + --short:: Emit output in the short-format. + + and avoid something like these overly verbose alternatives: + + --short:: Use this to emit output in the short-format. + --short:: You can use this to get output in the short-format. + --short:: A user who prefers shorter output could.... + --short:: Should a person and/or program want shorter output, he + she/they/it can... + + This practice often eliminates the need to involve human actors in + your description, but it is a good practice regardless of the + avoidance of gendered pronouns. + + - When it becomes awkward to stick to this style, prefer "you" when + addressing the the hypothetical user, and possibly "we" when + discussing how the program might react to the user. E.g. + + You can use this option instead of --xyz, but we might remove + support for it in future versions. + + while keeping in mind that you can probably be less verbose, e.g. + + Use this instead of --xyz. This option might be removed in future + versions. + + - If you still need to refer to an example person that is + third-person singular, you may resort to "singular they" to avoid + "he/she/him/her", e.g. + + A contributor asks their upstream to pull from them. + + Note that this sounds ungrammatical and unnatural to those who + learned that "they" is only used for third-person plural, e.g. + those who learn English as a second language in some parts of the + world. + Every user-visible change should be reflected in the documentation. The same general rule as for code applies -- imitate the existing conventions. diff --git a/Documentation/RelNotes/2.33.0.txt b/Documentation/RelNotes/2.33.0.txt index 4749bee61e..a69531c1ef 100644 --- a/Documentation/RelNotes/2.33.0.txt +++ b/Documentation/RelNotes/2.33.0.txt @@ -30,6 +30,9 @@ UI, Workflows & Features * The userdiff pattern for C# learned the token "record". + * "git rev-list" learns to omit the "commit <object-name>" header + lines from the output with the `--no-commit-header` option. + Performance, Internal Implementation, Development Support etc. @@ -44,6 +47,21 @@ Performance, Internal Implementation, Development Support etc. * Repeated rename detections in a sequence of mergy operations have been optimize out. + * Preliminary clean-up of tests before the main reftable changes + hits the codebase. + + * The backend for "diff -G/-S" has been updated to use pcre2 engine + when available. + + * Use ".DELETE_ON_ERROR" pseudo target to simplify our Makefile. + + * Code cleanup around struct_type_init() functions. + + * "git send-email" optimization. + + * GitHub Actions / CI update. + (merge 0dc787a9f2 js/ci-windows-update later to maint). + Fixes since v2.32 ----------------- @@ -128,6 +146,43 @@ Fixes since v2.32 fall back on the hardcoded default value for our own use. (merge 9b6e2c8b98 js/stop-exporting-bogus-columns later to maint). + * "git cat-file --batch-all-objects"" misbehaved when "--batch" is in + use and did not ask for certain object traits. + (merge ee02ac6164 zh/cat-file-batch-fix later to maint). + + * Some code and doc clarification around "git push". + + * The "union" conflict resultion variant misbehaved when used with + binary merge driver. + (merge 382b601acd jk/union-merge-binary later to maint). + + * Prevent "git p4" from failing to submit changes to binary file. + (merge 54662d5958 dc/p4-binary-submit-fix later to maint). + + * "git grep --and -e foo" ought to have been diagnosed as an error + but instead segfaulted, which has been corrected. + (merge fe7fe62d8d rs/grep-parser-fix later to maint). + + * The merge code had funny interactions between content based rename + detection and directory rename detection. + (merge 3585d0ea23 en/merge-dir-rename-corner-case-fix later to maint). + + * When rebuilding the multi-pack index file reusing an existing one, + we used to blindly trust the existing file and ended up carrying + corrupted data into the updated file, which has been corrected. + (merge f89ecf7988 tb/midx-use-checksum later to maint). + + * Update the location of system-side configuration file on Windows. + (merge e355307692 js/gfw-system-config-loc-fix later to maint). + + * Code recently added to support common ancestry negotiation during + "git push" did not sanity check its arguments carefully enough. + (merge eff40457a4 ab/fetch-negotiate-segv-fix later to maint). + + * Update the documentation not to assume users are of certain gender + and adds to guidelines to do so. + (merge 46a237f42f ds/gender-neutral-doc later to maint). + * Other code cleanup, docfix, build fix, etc. (merge bfe35a6165 ah/doc-describe later to maint). (merge f302c1e4aa jc/clarify-revision-range later to maint). @@ -153,3 +208,13 @@ Fixes since v2.32 (merge d94f9b8e90 ba/object-info later to maint). (merge 52ff891c03 ar/test-code-cleanup later to maint). (merge a0538e5c8b dd/document-log-decorate-default later to maint). + (merge ce24797d38 mr/cmake later to maint). + (merge 9eb542f2ee ab/pre-auto-gc-hook-test later to maint). + (merge 9fffc38583 bk/doc-commit-typofix later to maint). + (merge 1cf823d8f0 ks/submodule-cleanup later to maint). + (merge ebbf5d2b70 js/config-mak-windows-pcre-fix later to maint). + (merge 617480d75b hn/refs-iterator-peel-returns-boolean later to maint). + (merge 6a24cc71ed ar/submodule-helper-include-cleanup later to maint). + (merge 5632e838f8 rs/khash-alloc-cleanup later to maint). + (merge b1d87fbaf1 jk/typofix later to maint). + (merge e04170697a ab/gitignore-discovery-doc later to maint). diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index 55287d72e0..3e215f4d80 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -373,9 +373,8 @@ If you like, you can put extra tags at the end: . `Acked-by:` says that the person who is more familiar with the area the patch attempts to modify liked the patch. . `Reviewed-by:`, unlike the other tags, can only be offered by the - reviewer and means that she is completely satisfied that the patch - is ready for application. It is usually offered only after a - detailed review. + reviewers themselves when they are completely satisfied with the + patch after a detailed analysis. . `Tested-by:` is used to indicate that the person applied the patch and found it to have the desired effect. diff --git a/Documentation/config/fetch.txt b/Documentation/config/fetch.txt index 6af6f5edb2..63748c02b7 100644 --- a/Documentation/config/fetch.txt +++ b/Documentation/config/fetch.txt @@ -69,7 +69,8 @@ fetch.negotiationAlgorithm:: setting defaults to "skipping". Unknown values will cause 'git fetch' to error out. + -See also the `--negotiation-tip` option for linkgit:git-fetch[1]. +See also the `--negotiate-only` and `--negotiation-tip` options to +linkgit:git-fetch[1]. fetch.showForcedUpdates:: Set to false to enable `--no-show-forced-updates` in diff --git a/Documentation/config/push.txt b/Documentation/config/push.txt index f2667b2689..632033638c 100644 --- a/Documentation/config/push.txt +++ b/Documentation/config/push.txt @@ -24,15 +24,14 @@ push.default:: * `tracking` - This is a deprecated synonym for `upstream`. -* `simple` - in centralized workflow, work like `upstream` with an - added safety to refuse to push if the upstream branch's name is - different from the local one. +* `simple` - pushes the current branch with the same name on the remote. + -When pushing to a remote that is different from the remote you normally -pull from, work as `current`. This is the safest option and is suited -for beginners. +If you are working on a centralized workflow (pushing to the same repository you +pull from, which is typically `origin`), then you need to configure an upstream +branch with the same name. + -This mode has become the default in Git 2.0. +This mode is the default since Git 2.0, and is the safest option suited for +beginners. * `matching` - push all branches having the same name on both ends. This makes the repository you are pushing to remember the set of diff --git a/Documentation/config/sendemail.txt b/Documentation/config/sendemail.txt index cbc5af42fd..50baa5d6bf 100644 --- a/Documentation/config/sendemail.txt +++ b/Documentation/config/sendemail.txt @@ -8,9 +8,6 @@ sendemail.smtpEncryption:: See linkgit:git-send-email[1] for description. Note that this setting is not subject to the 'identity' mechanism. -sendemail.smtpssl (deprecated):: - Deprecated alias for 'sendemail.smtpEncryption = ssl'. - sendemail.smtpsslcertpath:: Path to ca-certificates (either a directory or a single file). Set it to an empty string to disable certificate verification. diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index 9e7b4e189c..e967ff1874 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -62,8 +62,17 @@ The argument to this option may be a glob on ref names, a ref, or the (possibly abbreviated) SHA-1 of a commit. Specifying a glob is equivalent to specifying this option multiple times, one for each matching ref name. + -See also the `fetch.negotiationAlgorithm` configuration variable -documented in linkgit:git-config[1]. +See also the `fetch.negotiationAlgorithm` and `push.negotiate` +configuration variables documented in linkgit:git-config[1], and the +`--negotiate-only` option below. + +--negotiate-only:: + Do not fetch anything from the server, and instead print the + ancestors of the provided `--negotiation-tip=*` arguments, + which we have in common with the server. ++ +Internally this is used to implement the `push.negotiate` option, see +linkgit:git-config[1]. --dry-run:: Show what would be done, without making any changes. diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 340c5fbb48..95fec5f069 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -72,7 +72,7 @@ OPTIONS -p:: --patch:: - Use the interactive patch selection interface to chose + Use the interactive patch selection interface to choose which changes to commit. See linkgit:git-add[1] for details. diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index 7f4c8a8ce7..6236c75c9b 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -51,16 +51,20 @@ files on disk. --staged is a synonym of --cached. + If --merge-base is given, instead of using <commit>, use the merge base -of <commit> and HEAD. `git diff --merge-base A` is equivalent to -`git diff $(git merge-base A HEAD)`. +of <commit> and HEAD. `git diff --cached --merge-base A` is equivalent to +`git diff --cached $(git merge-base A HEAD)`. -'git diff' [<options>] <commit> [--] [<path>...]:: +'git diff' [<options>] [--merge-base] <commit> [--] [<path>...]:: This form is to view the changes you have in your working tree relative to the named <commit>. You can use HEAD to compare it with the latest commit, or a branch name to compare with the tip of a different branch. ++ +If --merge-base is given, instead of using <commit>, use the merge base +of <commit> and HEAD. `git diff --merge-base A` is equivalent to +`git diff $(git merge-base A HEAD)`. 'git diff' [<options>] [--merge-base] <commit> <commit> [--] [<path>...]:: diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index a953c7c387..2f25aa3a29 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -244,8 +244,8 @@ Imagine that you have to rebase what you have already published. You will have to bypass the "must fast-forward" rule in order to replace the history you originally published with the rebased history. If somebody else built on top of your original history while you are -rebasing, the tip of the branch at the remote may advance with her -commit, and blindly pushing with `--force` will lose her work. +rebasing, the tip of the branch at the remote may advance with their +commit, and blindly pushing with `--force` will lose their work. + This option allows you to say that you expect the history you are updating is what you rebased and want to replace. If the remote ref diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt index 66e67e6cbf..8a7cbdd19c 100644 --- a/Documentation/git-worktree.txt +++ b/Documentation/git-worktree.txt @@ -9,7 +9,7 @@ git-worktree - Manage multiple working trees SYNOPSIS -------- [verse] -'git worktree add' [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<commit-ish>] +'git worktree add' [-f] [--detach] [--checkout] [--lock [--reason <string>]] [-b <new-branch>] <path> [<commit-ish>] 'git worktree list' [--porcelain] 'git worktree lock' [--reason <string>] <worktree> 'git worktree move' <worktree> <new-path> @@ -242,7 +242,7 @@ With `list`, annotate missing working trees as prunable if they are older than `<time>`. --reason <string>:: - With `lock`, an explanation why the working tree is locked. + With `lock` or with `add --lock`, an explanation why the working tree is locked. <worktree>:: Working trees can be identified by path, either relative or diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt index 53e7d5c914..f8a1fc2014 100644 --- a/Documentation/gitignore.txt +++ b/Documentation/gitignore.txt @@ -27,12 +27,11 @@ precedence, the last matching pattern decides the outcome): them. * Patterns read from a `.gitignore` file in the same directory - as the path, or in any parent directory, with patterns in the - higher level files (up to the toplevel of the work tree) being overridden - by those in lower level files down to the directory containing the file. - These patterns match relative to the location of the - `.gitignore` file. A project normally includes such - `.gitignore` files in its repository, containing patterns for + as the path, or in any parent directory (up to the top-level of the working + tree), with patterns in the higher level files being overridden by those in + lower level files down to the directory containing the file. These patterns + match relative to the location of the `.gitignore` file. A project normally + includes such `.gitignore` files in its repository, containing patterns for files generated as part of the project build. * Patterns read from `$GIT_DIR/info/exclude`. diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 5bf2a85f69..24569b06d1 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -897,7 +897,7 @@ which are not of the requested type. + The form '--filter=sparse:oid=<blob-ish>' uses a sparse-checkout specification contained in the blob (or blob-expression) '<blob-ish>' -to omit blobs that would not be not required for a sparse checkout on +to omit blobs that would not be required for a sparse checkout on the requested refs. + The form '--filter=tree:<depth>' omits all blobs and trees whose depth @@ -1064,6 +1064,14 @@ ifdef::git-rev-list[] --header:: Print the contents of the commit in raw-format; each record is separated with a NUL character. + +--no-commit-header:: + Suppress the header line containing "commit" and the object ID printed before + the specified format. This has no effect on the built-in formats; only custom + formats are affected. + +--commit-header:: + Overrides a previous `--no-commit-header`. endif::git-rev-list[] --parents:: diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index f9e54b8674..96240598e3 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -2792,7 +2792,7 @@ A fast-forward looks something like this: In some cases it is possible that the new head will *not* actually be a descendant of the old head. For example, the developer may have -realized she made a serious mistake, and decided to backtrack, +realized a serious mistake was made and decided to backtrack, resulting in a situation like: ................................................ @@ -726,9 +726,11 @@ TEST_BUILTINS_OBJS += test-mergesort.o TEST_BUILTINS_OBJS += test-mktemp.o TEST_BUILTINS_OBJS += test-oid-array.o TEST_BUILTINS_OBJS += test-oidmap.o +TEST_BUILTINS_OBJS += test-oidtree.o TEST_BUILTINS_OBJS += test-online-cpus.o TEST_BUILTINS_OBJS += test-parse-options.o TEST_BUILTINS_OBJS += test-parse-pathspec-file.o +TEST_BUILTINS_OBJS += test-partial-clone.o TEST_BUILTINS_OBJS += test-path-utils.o TEST_BUILTINS_OBJS += test-pcre2-config.o TEST_BUILTINS_OBJS += test-pkt-line.o @@ -849,6 +851,7 @@ LIB_OBJS += branch.o LIB_OBJS += bulk-checkin.o LIB_OBJS += bundle.o LIB_OBJS += cache-tree.o +LIB_OBJS += cbtree.o LIB_OBJS += chdir-notify.o LIB_OBJS += checkout.o LIB_OBJS += chunk-format.o @@ -944,6 +947,7 @@ LIB_OBJS += object.o LIB_OBJS += oid-array.o LIB_OBJS += oidmap.o LIB_OBJS += oidset.o +LIB_OBJS += oidtree.o LIB_OBJS += pack-bitmap-write.o LIB_OBJS += pack-bitmap.o LIB_OBJS += pack-check.o @@ -2164,6 +2168,16 @@ shell_compatibility_test: please_set_SHELL_PATH_to_a_more_modern_shell strip: $(PROGRAMS) git$X $(STRIP) $(STRIP_OPTS) $^ +### Flags affecting all rules + +# A GNU make extension since gmake 3.72 (released in late 1994) to +# remove the target of rules if commands in those rules fail. The +# default is to only do that if make itself receives a signal. Affects +# all targets, see: +# +# info make --index-search=.DELETE_ON_ERROR +.DELETE_ON_ERROR: + ### Target-specific flags and dependencies # The generic compilation pattern rule and automatically @@ -2247,7 +2261,6 @@ SCRIPT_DEFINES = $(SHELL_PATH_SQ):$(DIFF_SQ):$(GIT_VERSION):\ $(gitwebdir_SQ):$(PERL_PATH_SQ):$(SANE_TEXT_GREP):$(PAGER_ENV):\ $(perllibdir_SQ) define cmd_munge_script -$(RM) $@ $@+ && \ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \ -e 's|@@DIFF@@|$(DIFF_SQ)|' \ @@ -2317,7 +2330,7 @@ endif PERL_DEFINES += $(gitexecdir) $(perllibdir) $(localedir) $(SCRIPT_PERL_GEN): % : %.perl GIT-PERL-DEFINES GIT-PERL-HEADER GIT-VERSION-FILE - $(QUIET_GEN)$(RM) $@ $@+ && \ + $(QUIET_GEN) \ sed -e '1{' \ -e ' s|#!.*perl|#!$(PERL_PATH_SQ)|' \ -e ' r GIT-PERL-HEADER' \ @@ -2337,7 +2350,7 @@ GIT-PERL-DEFINES: FORCE fi GIT-PERL-HEADER: $(PERL_HEADER_TEMPLATE) GIT-PERL-DEFINES Makefile - $(QUIET_GEN)$(RM) $@ && \ + $(QUIET_GEN) \ INSTLIBDIR='$(perllibdir_SQ)' && \ INSTLIBDIR_EXTRA='$(PERLLIB_EXTRA_SQ)' && \ INSTLIBDIR="$$INSTLIBDIR$${INSTLIBDIR_EXTRA:+:$$INSTLIBDIR_EXTRA}" && \ @@ -2363,7 +2376,7 @@ git-instaweb: git-instaweb.sh GIT-SCRIPT-DEFINES mv $@+ $@ else # NO_PERL $(SCRIPT_PERL_GEN) git-instaweb: % : unimplemented.sh - $(QUIET_GEN)$(RM) $@ $@+ && \ + $(QUIET_GEN) \ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's|@@REASON@@|NO_PERL=$(NO_PERL)|g' \ unimplemented.sh >$@+ && \ @@ -2377,14 +2390,14 @@ $(SCRIPT_PYTHON_GEN): GIT-BUILD-OPTIONS ifndef NO_PYTHON $(SCRIPT_PYTHON_GEN): GIT-CFLAGS GIT-PREFIX GIT-PYTHON-VARS $(SCRIPT_PYTHON_GEN): % : %.py - $(QUIET_GEN)$(RM) $@ $@+ && \ + $(QUIET_GEN) \ sed -e '1s|#!.*python|#!$(PYTHON_PATH_SQ)|' \ $< >$@+ && \ chmod +x $@+ && \ mv $@+ $@ else # NO_PYTHON $(SCRIPT_PYTHON_GEN): % : unimplemented.sh - $(QUIET_GEN)$(RM) $@ $@+ && \ + $(QUIET_GEN) \ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's|@@REASON@@|NO_PYTHON=$(NO_PYTHON)|g' \ unimplemented.sh >$@+ && \ @@ -2392,8 +2405,7 @@ $(SCRIPT_PYTHON_GEN): % : unimplemented.sh mv $@+ $@ endif # NO_PYTHON -CONFIGURE_RECIPE = $(RM) configure configure.ac+ && \ - sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ +CONFIGURE_RECIPE = sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ configure.ac >configure.ac+ && \ autoconf -o configure configure.ac+ && \ $(RM) configure.ac+ @@ -2518,7 +2530,6 @@ endif ifeq ($(GENERATE_COMPILATION_DATABASE),yes) all:: compile_commands.json compile_commands.json: - @$(RM) $@ $(QUIET_GEN)sed -e '1s/^/[/' -e '$$s/,$$/]/' $(compdb_dir)/*.o.json > $@+ @if test -s $@+; then mv $@+ $@; else $(RM) $@+; fi endif @@ -2591,10 +2602,10 @@ $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS) $(LIB_FILE): $(LIB_OBJS) - $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^ + $(QUIET_AR)$(AR) $(ARFLAGS) $@ $^ $(XDIFF_LIB): $(XDIFF_OBJS) - $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^ + $(QUIET_AR)$(AR) $(ARFLAGS) $@ $^ export DEFAULT_EDITOR DEFAULT_PAGER @@ -2679,10 +2690,13 @@ po/git.pot: $(GENERATED_H) FORCE .PHONY: pot pot: po/git.pot +ifdef NO_GETTEXT +POFILES := +MOFILES := +else POFILES := $(wildcard po/*.po) MOFILES := $(patsubst po/%.po,po/build/locale/%/LC_MESSAGES/git.mo,$(POFILES)) -ifndef NO_GETTEXT all:: $(MOFILES) endif diff --git a/add-patch.c b/add-patch.c index 2fad92ca37..8c41cdfe39 100644 --- a/add-patch.c +++ b/add-patch.c @@ -280,6 +280,7 @@ static void add_p_state_clear(struct add_p_state *s) clear_add_i_state(&s->s); } +__attribute__((format (printf, 2, 3))) static void err(struct add_p_state *s, const char *fmt, ...) { va_list args; @@ -90,6 +90,7 @@ int advice_enabled(enum advice_type type); /** * Checks the visibility of the advice before printing. */ +__attribute__((format (printf, 2, 3))) void advise_if_enabled(enum advice_type type, const char *advice, ...); int error_resolve_conflict(const char *me); @@ -101,9 +101,9 @@ int init_apply_state(struct apply_state *state, state->ws_error_action = warn_on_ws_error; state->ws_ignore_action = ignore_ws_none; state->linenr = 1; - string_list_init(&state->fn_table, 0); - string_list_init(&state->limit_by_name, 0); - string_list_init(&state->symlink_changes, 0); + string_list_init_nodup(&state->fn_table); + string_list_init_nodup(&state->limit_by_name); + string_list_init_nodup(&state->symlink_changes); strbuf_init(&state->root, 0); git_apply_config(); @@ -645,7 +645,7 @@ int write_archive(int argc, const char **argv, const char *prefix, args.pretty_ctx = &ctx; args.repo = repo; args.prefix = prefix; - string_list_init(&args.extra_files, 1); + string_list_init_dup(&args.extra_files); argc = parse_archive_args(argc, argv, &ar, &args, name_hint, remote); if (!startup_info->have_repository) { /* diff --git a/builtin/add.c b/builtin/add.c index b773b5a499..09e684585d 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -470,7 +470,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) { int exit_status = 0; struct pathspec pathspec; - struct dir_struct dir; + struct dir_struct dir = DIR_INIT; int flags; int add_new_files; int require_pathspec; @@ -577,7 +577,6 @@ int cmd_add(int argc, const char **argv, const char *prefix) die_in_unpopulated_submodule(&the_index, prefix); die_path_inside_submodule(&the_index, &pathspec); - dir_init(&dir); if (add_new_files) { int baselen; diff --git a/builtin/am.c b/builtin/am.c index 0b2d886c81..0c2ad96b70 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -210,6 +210,7 @@ static void write_state_bool(const struct am_state *state, * If state->quiet is false, calls fprintf(fp, fmt, ...), and appends a newline * at the end. */ +__attribute__((format (printf, 3, 4))) static void say(const struct am_state *state, FILE *fp, const char *fmt, ...) { va_list ap; diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index 9d9540a0ab..f184eaeac6 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -117,6 +117,7 @@ static int write_in_file(const char *path, const char *mode, const char *format, return fclose(fp); } +__attribute__((format (printf, 2, 3))) static int write_to_file(const char *path, const char *format, ...) { int res; @@ -129,6 +130,7 @@ static int write_to_file(const char *path, const char *format, ...) return res; } +__attribute__((format (printf, 2, 3))) static int append_to_file(const char *path, const char *format, ...) { int res; diff --git a/builtin/bundle.c b/builtin/bundle.c index ea6948110b..053a51bea1 100644 --- a/builtin/bundle.c +++ b/builtin/bundle.c @@ -46,7 +46,7 @@ static int parse_options_cmd_bundle(int argc, const char* prefix, const char * const usagestr[], const struct option options[], - const char **bundle_file) { + char **bundle_file) { int newargc; newargc = parse_options(argc, argv, NULL, options, usagestr, PARSE_OPT_STOP_AT_NON_OPTION); @@ -61,7 +61,7 @@ static int cmd_bundle_create(int argc, const char **argv, const char *prefix) { int progress = isatty(STDERR_FILENO); struct strvec pack_opts; int version = -1; - + int ret; struct option options[] = { OPT_SET_INT('q', "quiet", &progress, N_("do not show progress meter"), 0), @@ -76,7 +76,7 @@ static int cmd_bundle_create(int argc, const char **argv, const char *prefix) { N_("specify bundle format version")), OPT_END() }; - const char* bundle_file; + char *bundle_file; argc = parse_options_cmd_bundle(argc, argv, prefix, builtin_bundle_create_usage, options, &bundle_file); @@ -94,75 +94,95 @@ static int cmd_bundle_create(int argc, const char **argv, const char *prefix) { if (!startup_info->have_repository) die(_("Need a repository to create a bundle.")); - return !!create_bundle(the_repository, bundle_file, argc, argv, &pack_opts, version); + ret = !!create_bundle(the_repository, bundle_file, argc, argv, &pack_opts, version); + free(bundle_file); + return ret; } static int cmd_bundle_verify(int argc, const char **argv, const char *prefix) { - struct bundle_header header; + struct bundle_header header = BUNDLE_HEADER_INIT; int bundle_fd = -1; int quiet = 0; - + int ret; struct option options[] = { OPT_BOOL('q', "quiet", &quiet, N_("do not show bundle details")), OPT_END() }; - const char* bundle_file; + char *bundle_file; argc = parse_options_cmd_bundle(argc, argv, prefix, builtin_bundle_verify_usage, options, &bundle_file); /* bundle internals use argv[1] as further parameters */ - memset(&header, 0, sizeof(header)); - if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) - return 1; + if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) { + ret = 1; + goto cleanup; + } close(bundle_fd); - if (verify_bundle(the_repository, &header, !quiet)) - return 1; + if (verify_bundle(the_repository, &header, !quiet)) { + ret = 1; + goto cleanup; + } + fprintf(stderr, _("%s is okay\n"), bundle_file); - return 0; + ret = 0; +cleanup: + free(bundle_file); + bundle_header_release(&header); + return ret; } static int cmd_bundle_list_heads(int argc, const char **argv, const char *prefix) { - struct bundle_header header; + struct bundle_header header = BUNDLE_HEADER_INIT; int bundle_fd = -1; - + int ret; struct option options[] = { OPT_END() }; - const char* bundle_file; + char *bundle_file; argc = parse_options_cmd_bundle(argc, argv, prefix, builtin_bundle_list_heads_usage, options, &bundle_file); /* bundle internals use argv[1] as further parameters */ - memset(&header, 0, sizeof(header)); - if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) - return 1; + if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) { + ret = 1; + goto cleanup; + } close(bundle_fd); - return !!list_bundle_refs(&header, argc, argv); + ret = !!list_bundle_refs(&header, argc, argv); +cleanup: + free(bundle_file); + bundle_header_release(&header); + return ret; } static int cmd_bundle_unbundle(int argc, const char **argv, const char *prefix) { - struct bundle_header header; + struct bundle_header header = BUNDLE_HEADER_INIT; int bundle_fd = -1; - + int ret; struct option options[] = { OPT_END() }; - const char* bundle_file; + char *bundle_file; argc = parse_options_cmd_bundle(argc, argv, prefix, builtin_bundle_unbundle_usage, options, &bundle_file); /* bundle internals use argv[1] as further parameters */ - memset(&header, 0, sizeof(header)); - if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) - return 1; + if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) { + ret = 1; + goto cleanup; + } if (!startup_info->have_repository) die(_("Need a repository to unbundle.")); - return !!unbundle(the_repository, &header, bundle_fd, 0) || + ret = !!unbundle(the_repository, &header, bundle_fd, 0) || list_bundle_refs(&header, argc, argv); + bundle_header_release(&header); +cleanup: + free(bundle_file); + return ret; } int cmd_bundle(int argc, const char **argv, const char *prefix) diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 5ebf13359e..243fe6844b 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -512,12 +512,6 @@ static int batch_objects(struct batch_options *opt) if (opt->cmdmode) data.split_on_whitespace = 1; - if (opt->all_objects) { - struct object_info empty = OBJECT_INFO_INIT; - if (!memcmp(&data.info, &empty, sizeof(empty))) - data.skip_object_info = 1; - } - /* * If we are printing out the object, then always fill in the type, * since we will want to decide whether or not to stream. @@ -527,6 +521,10 @@ static int batch_objects(struct batch_options *opt) if (opt->all_objects) { struct object_cb_data cb; + struct object_info empty = OBJECT_INFO_INIT; + + if (!memcmp(&data.info, &empty, sizeof(empty))) + data.skip_object_info = 1; if (has_promisor_remote()) warning("This repository uses promisor remotes. Some objects may not be loaded."); diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c index 81234552b7..2191256965 100644 --- a/builtin/check-ignore.c +++ b/builtin/check-ignore.c @@ -153,7 +153,7 @@ static int check_ignore_stdin_paths(struct dir_struct *dir, const char *prefix) int cmd_check_ignore(int argc, const char **argv, const char *prefix) { int num_ignored; - struct dir_struct dir; + struct dir_struct dir = DIR_INIT; git_config(git_default_config, NULL); @@ -182,7 +182,6 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix) if (!no_index && read_cache() < 0) die(_("index file corrupt")); - dir_init(&dir); setup_standard_excludes(&dir); if (stdin_paths) { diff --git a/builtin/clean.c b/builtin/clean.c index 4944cf440b..98a2860409 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -641,7 +641,7 @@ static int clean_cmd(void) static int filter_by_patterns_cmd(void) { - struct dir_struct dir; + struct dir_struct dir = DIR_INIT; struct strbuf confirm = STRBUF_INIT; struct strbuf **ignore_list; struct string_list_item *item; @@ -665,7 +665,6 @@ static int filter_by_patterns_cmd(void) if (!confirm.len) break; - dir_init(&dir); pl = add_pattern_list(&dir, EXC_CMDL, "manual exclude"); ignore_list = strbuf_split_max(&confirm, ' ', 0); @@ -890,7 +889,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) int ignored_only = 0, config_set = 0, errors = 0, gone = 1; int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT; struct strbuf abs_path = STRBUF_INIT; - struct dir_struct dir; + struct dir_struct dir = DIR_INIT; struct pathspec pathspec; struct strbuf buf = STRBUF_INIT; struct string_list exclude_list = STRING_LIST_INIT_NODUP; @@ -921,7 +920,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, builtin_clean_usage, 0); - dir_init(&dir); if (!interactive && !dry_run && !force) { if (config_set) die(_("clean.requireForce set to true and neither -i, -n, nor -f given; " diff --git a/builtin/commit.c b/builtin/commit.c index 190d215d43..7436262aae 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -889,7 +889,22 @@ static int prepare_to_commit(const char *index_file, const char *prefix, int ident_shown = 0; int saved_color_setting; struct ident_split ci, ai; - + const char *hint_cleanup_all = allow_empty_message ? + _("Please enter the commit message for your changes." + " Lines starting\nwith '%c' will be ignored.\n") : + _("Please enter the commit message for your changes." + " Lines starting\nwith '%c' will be ignored, and an empty" + " message aborts the commit.\n"); + const char *hint_cleanup_space = allow_empty_message ? + _("Please enter the commit message for your changes." + " Lines starting\n" + "with '%c' will be kept; you may remove them" + " yourself if you want to.\n") : + _("Please enter the commit message for your changes." + " Lines starting\n" + "with '%c' will be kept; you may remove them" + " yourself if you want to.\n" + "An empty message aborts the commit.\n"); if (whence != FROM_COMMIT) { if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS && !merge_contains_scissors) @@ -911,20 +926,12 @@ static int prepare_to_commit(const char *index_file, const char *prefix, fprintf(s->fp, "\n"); if (cleanup_mode == COMMIT_MSG_CLEANUP_ALL) - status_printf(s, GIT_COLOR_NORMAL, - _("Please enter the commit message for your changes." - " Lines starting\nwith '%c' will be ignored, and an empty" - " message aborts the commit.\n"), comment_line_char); + status_printf(s, GIT_COLOR_NORMAL, hint_cleanup_all, comment_line_char); else if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) { if (whence == FROM_COMMIT && !merge_contains_scissors) wt_status_add_cut_line(s->fp); } else /* COMMIT_MSG_CLEANUP_SPACE, that is. */ - status_printf(s, GIT_COLOR_NORMAL, - _("Please enter the commit message for your changes." - " Lines starting\n" - "with '%c' will be kept; you may remove them" - " yourself if you want to.\n" - "An empty message aborts the commit.\n"), comment_line_char); + status_printf(s, GIT_COLOR_NORMAL, hint_cleanup_space, comment_line_char); /* * These should never fail because they come from our own @@ -1510,6 +1517,9 @@ int cmd_status(int argc, const char **argv, const char *prefix) if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_status_usage, builtin_status_options); + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + status_init_config(&s, git_status_config); argc = parse_options(argc, argv, prefix, builtin_status_options, diff --git a/builtin/diff.c b/builtin/diff.c index 2d87c37a17..dd8ce688ba 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -26,8 +26,8 @@ static const char builtin_diff_usage[] = "git diff [<options>] [<commit>] [--] [<path>...]\n" -" or: git diff [<options>] --cached [<commit>] [--] [<path>...]\n" -" or: git diff [<options>] <commit> [--merge-base] [<commit>...] <commit> [--] [<path>...]\n" +" or: git diff [<options>] --cached [--merge-base] [<commit>] [--] [<path>...]\n" +" or: git diff [<options>] [--merge-base] <commit> [<commit>...] <commit> [--] [<path>...]\n" " or: git diff [<options>] <commit>...<commit>] [--] [<path>...]\n" " or: git diff [<options>] <blob> <blob>]\n" " or: git diff [<options>] --no-index [--] <path> <path>]\n" diff --git a/builtin/fetch.c b/builtin/fetch.c index 9191620e50..25740c13df 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -1990,6 +1990,9 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) fetch_config_from_gitmodules(sfjc, rs); } + if (negotiate_only && !negotiation_tip.nr) + die(_("--negotiate-only needs one or more --negotiate-tip=*")); + if (deepen_relative) { if (deepen_relative < 0) die(_("Negative depth in --deepen is not supported")); diff --git a/builtin/grep.c b/builtin/grep.c index ab8822e68f..7d2f8e5adb 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -704,10 +704,9 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec, static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec, int exc_std, int use_index) { - struct dir_struct dir; + struct dir_struct dir = DIR_INIT; int i, hit = 0; - dir_init(&dir); if (!use_index) dir.flags |= DIR_NO_GITLINKS; if (exc_std) diff --git a/builtin/help.c b/builtin/help.c index bb339f0fc8..b7eec06c3d 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -436,10 +436,9 @@ static void exec_viewer(const char *name, const char *page) warning(_("'%s': unknown man viewer."), name); } -static void show_man_page(const char *git_cmd) +static void show_man_page(const char *page) { struct man_viewer_list *viewer; - const char *page = cmd_to_page(git_cmd); const char *fallback = getenv("GIT_MAN_VIEWER"); setup_man_path(); @@ -453,9 +452,8 @@ static void show_man_page(const char *git_cmd) die(_("no man viewer handled the request")); } -static void show_info_page(const char *git_cmd) +static void show_info_page(const char *page) { - const char *page = cmd_to_page(git_cmd); setenv("INFOPATH", system_path(GIT_INFO_PATH), 1); execlp("info", "info", "gitman", page, (char *)NULL); die(_("no info viewer handled the request")); @@ -486,9 +484,8 @@ static void open_html(const char *path) execl_git_cmd("web--browse", "-c", "help.browser", path, (char *)NULL); } -static void show_html_page(const char *git_cmd) +static void show_html_page(const char *page) { - const char *page = cmd_to_page(git_cmd); struct strbuf page_path; /* it leaks but we exec bellow */ get_html_page_path(&page_path, page); @@ -548,6 +545,7 @@ int cmd_help(int argc, const char **argv, const char *prefix) { int nongit; enum help_format parsed_help_format; + const char *page; argc = parse_options(argc, argv, prefix, builtin_help_options, builtin_help_usage, 0); @@ -606,16 +604,17 @@ int cmd_help(int argc, const char **argv, const char *prefix) argv[0] = check_git_cmd(argv[0]); + page = cmd_to_page(argv[0]); switch (help_format) { case HELP_FORMAT_NONE: case HELP_FORMAT_MAN: - show_man_page(argv[0]); + show_man_page(page); break; case HELP_FORMAT_INFO: - show_info_page(argv[0]); + show_info_page(page); break; case HELP_FORMAT_WEB: - show_html_page(argv[0]); + show_html_page(page); break; } diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 3fbc5d7077..8336466865 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -369,9 +369,7 @@ static void parse_pack_header(void) use(sizeof(struct pack_header)); } -static NORETURN void bad_object(off_t offset, const char *format, - ...) __attribute__((format (printf, 2, 3))); - +__attribute__((format (printf, 2, 3))) static NORETURN void bad_object(off_t offset, const char *format, ...) { va_list params; diff --git a/builtin/log.c b/builtin/log.c index 516a1142dd..3d7717ba5c 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -245,6 +245,9 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, rev->abbrev_commit = 0; } + if (rev->commit_format == CMIT_FMT_USERFORMAT && !w.decorate) + decoration_style = 0; + if (decoration_style) { const struct string_list *config_exclude = repo_config_get_value_multi(the_repository, diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 45cc3b23dd..29a26ad8ae 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -608,7 +608,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) { int require_work_tree = 0, show_tag = 0, i; char *max_prefix; - struct dir_struct dir; + struct dir_struct dir = DIR_INIT; struct pattern_list *pl; struct string_list exclude_list = STRING_LIST_INIT_NODUP; struct option builtin_ls_files_options[] = { @@ -678,7 +678,6 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(ls_files_usage, builtin_ls_files_options); - dir_init(&dir); prefix = cmd_prefix; if (prefix) prefix_len = strlen(prefix); diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index de8520778d..5dc94d6f88 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -107,15 +107,12 @@ static void show_diff(struct merge_list *entry) mmfile_t src, dst; xpparam_t xpp; xdemitconf_t xecfg; - xdemitcb_t ecb; + xdemitcb_t ecb = { .out_line = show_outf }; memset(&xpp, 0, sizeof(xpp)); xpp.flags = 0; memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 3; - ecb.out_hunk = NULL; - ecb.out_line = show_outf; - ecb.priv = NULL; src.ptr = origin(entry, &size); if (!src.ptr) diff --git a/builtin/push.c b/builtin/push.c index 194967ed79..e8b10a9b7e 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -185,82 +185,73 @@ static const char message_detached_head_die[] = "\n" " git push %s HEAD:<name-of-remote-branch>\n"); -static void setup_push_upstream(struct remote *remote, struct branch *branch, - int triangular, int simple) +static const char *get_upstream_ref(struct branch *branch, const char *remote_name) { - if (!branch) - die(_(message_detached_head_die), remote->name); if (!branch->merge_nr || !branch->merge || !branch->remote_name) die(_("The current branch %s has no upstream branch.\n" "To push the current branch and set the remote as upstream, use\n" "\n" " git push --set-upstream %s %s\n"), branch->name, - remote->name, + remote_name, branch->name); if (branch->merge_nr != 1) die(_("The current branch %s has multiple upstream branches, " "refusing to push."), branch->name); - if (triangular) - die(_("You are pushing to remote '%s', which is not the upstream of\n" - "your current branch '%s', without telling me what to push\n" - "to update which remote branch."), - remote->name, branch->name); - - if (simple) { - /* Additional safety */ - if (strcmp(branch->refname, branch->merge[0]->src)) - die_push_simple(branch, remote); - } - - refspec_appendf(&rs, "%s:%s", branch->refname, branch->merge[0]->src); -} - -static void setup_push_current(struct remote *remote, struct branch *branch) -{ - if (!branch) - die(_(message_detached_head_die), remote->name); - refspec_appendf(&rs, "%s:%s", branch->refname, branch->refname); -} -static int is_workflow_triangular(struct remote *remote) -{ - struct remote *fetch_remote = remote_get(NULL); - return (fetch_remote && fetch_remote != remote); + return branch->merge[0]->src; } static void setup_default_push_refspecs(struct remote *remote) { - struct branch *branch = branch_get(NULL); - int triangular = is_workflow_triangular(remote); + struct branch *branch; + const char *dst; + int same_remote; switch (push_default) { - default: case PUSH_DEFAULT_MATCHING: refspec_append(&rs, ":"); + return; + + case PUSH_DEFAULT_NOTHING: + die(_("You didn't specify any refspecs to push, and " + "push.default is \"nothing\".")); + return; + default: break; + } + branch = branch_get(NULL); + if (!branch) + die(_(message_detached_head_die), remote->name); + + dst = branch->refname; + same_remote = !strcmp(remote->name, remote_for_branch(branch, NULL)); + + switch (push_default) { + default: case PUSH_DEFAULT_UNSPECIFIED: case PUSH_DEFAULT_SIMPLE: - if (triangular) - setup_push_current(remote, branch); - else - setup_push_upstream(remote, branch, triangular, 1); + if (!same_remote) + break; + if (strcmp(branch->refname, get_upstream_ref(branch, remote->name))) + die_push_simple(branch, remote); break; case PUSH_DEFAULT_UPSTREAM: - setup_push_upstream(remote, branch, triangular, 0); + if (!same_remote) + die(_("You are pushing to remote '%s', which is not the upstream of\n" + "your current branch '%s', without telling me what to push\n" + "to update which remote branch."), + remote->name, branch->name); + dst = get_upstream_ref(branch, remote->name); break; case PUSH_DEFAULT_CURRENT: - setup_push_current(remote, branch); - break; - - case PUSH_DEFAULT_NOTHING: - die(_("You didn't specify any refspecs to push, and " - "push.default is \"nothing\".")); break; } + + refspec_appendf(&rs, "%s:%s", branch->refname, dst); } static const char message_advice_pull_before_push[] = diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index a34742513a..2d1f97e1ca 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -425,9 +425,6 @@ static int proc_receive_ref_matches(struct command *cmd) return 0; } -static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2))); -static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2))); - static void report_message(const char *prefix, const char *err, va_list params) { int sz; @@ -445,6 +442,7 @@ static void report_message(const char *prefix, const char *err, va_list params) xwrite(2, msg, sz); } +__attribute__((format (printf, 1, 2))) static void rp_warning(const char *err, ...) { va_list params; @@ -453,6 +451,7 @@ static void rp_warning(const char *err, ...) va_end(params); } +__attribute__((format (printf, 1, 2))) static void rp_error(const char *err, ...) { va_list params; diff --git a/builtin/rerere.c b/builtin/rerere.c index fd3be17b97..83d7a778e3 100644 --- a/builtin/rerere.c +++ b/builtin/rerere.c @@ -28,7 +28,7 @@ static int diff_two(const char *file1, const char *label1, { xpparam_t xpp; xdemitconf_t xecfg; - xdemitcb_t ecb; + xdemitcb_t ecb = { .out_line = outf }; mmfile_t minus, plus; int ret; @@ -41,8 +41,6 @@ static int diff_two(const char *file1, const char *label1, xpp.flags = 0; memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 3; - ecb.out_hunk = NULL; - ecb.out_line = outf; ret = xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb); free(minus.ptr); diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 7677b1af5a..36cb909eba 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -127,13 +127,15 @@ static void show_commit(struct commit *commit, void *data) if (info->header_prefix) fputs(info->header_prefix, stdout); - if (!revs->graph) - fputs(get_revision_mark(revs, commit), stdout); - if (revs->abbrev_commit && revs->abbrev) - fputs(find_unique_abbrev(&commit->object.oid, revs->abbrev), - stdout); - else - fputs(oid_to_hex(&commit->object.oid), stdout); + if (revs->include_header) { + if (!revs->graph) + fputs(get_revision_mark(revs, commit), stdout); + if (revs->abbrev_commit && revs->abbrev) + fputs(find_unique_abbrev(&commit->object.oid, revs->abbrev), + stdout); + else + fputs(oid_to_hex(&commit->object.oid), stdout); + } if (revs->print_parents) { struct commit_list *parents = commit->parents; while (parents) { @@ -153,7 +155,7 @@ static void show_commit(struct commit *commit, void *data) show_decorations(revs, commit); if (revs->commit_format == CMIT_FMT_ONELINE) putchar(' '); - else + else if (revs->include_header) putchar('\n'); if (revs->verbose_header) { @@ -512,6 +514,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) repo_init_revisions(the_repository, &revs, prefix); revs.abbrev = DEFAULT_ABBREV; revs.commit_format = CMIT_FMT_UNSPECIFIED; + revs.include_header = 1; /* * Scan the argument list before invoking setup_revisions(), so that we @@ -627,6 +630,16 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) continue; } + if (!strcmp(arg, ("--commit-header"))) { + revs.include_header = 1; + continue; + } + + if (!strcmp(arg, ("--no-commit-header"))) { + revs.include_header = 0; + continue; + } + if (!strcmp(arg, "--disk-usage")) { show_disk_usage = 1; info.flags |= REV_LIST_QUIET; @@ -636,10 +649,12 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) usage(rev_list_usage); } + if (revs.commit_format != CMIT_FMT_USERFORMAT) + revs.include_header = 1; if (revs.commit_format != CMIT_FMT_UNSPECIFIED) { /* The command line has a --pretty */ info.hdr_termination = '\n'; - if (revs.commit_format == CMIT_FMT_ONELINE) + if (revs.commit_format == CMIT_FMT_ONELINE || !revs.include_header) info.header_prefix = ""; else info.header_prefix = "commit "; diff --git a/builtin/show-branch.c b/builtin/show-branch.c index d6d2dabeca..d77ce7aeb3 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -939,9 +939,12 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) mark = '*'; else mark = '+'; - printf("%s%c%s", - get_color_code(i), - mark, get_color_reset_code()); + if (mark == ' ') + putchar(mark); + else + printf("%s%c%s", + get_color_code(i), + mark, get_color_reset_code()); } putchar(' '); } diff --git a/builtin/stash.c b/builtin/stash.c index 9c72e4b125..8f42360ca9 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -991,9 +991,8 @@ static int get_untracked_files(const struct pathspec *ps, int include_untracked, { int i; int found = 0; - struct dir_struct dir; + struct dir_struct dir = DIR_INIT; - dir_init(&dir); if (include_untracked != INCLUDE_ALL_FILES) setup_standard_excludes(&dir); diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index ae6174ab05..f73963ad67 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -19,7 +19,6 @@ #include "diffcore.h" #include "diff.h" #include "object-store.h" -#include "dir.h" #include "advice.h" #define OPT_QUIET (1 << 0) diff --git a/builtin/worktree.c b/builtin/worktree.c index 976bf8ed06..0d0a80da61 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -30,7 +30,7 @@ struct add_opts { int detach; int quiet; int checkout; - int keep_locked; + const char *keep_locked; }; static int show_only; @@ -302,10 +302,10 @@ static int add_worktree(const char *path, const char *refname, * after the preparation is over. */ strbuf_addf(&sb, "%s/locked", sb_repo.buf); - if (!opts->keep_locked) - write_file(sb.buf, "initializing"); + if (opts->keep_locked) + write_file(sb.buf, "%s", opts->keep_locked); else - write_file(sb.buf, "added with --lock"); + write_file(sb.buf, _("initializing")); strbuf_addf(&sb_git, "%s/.git", path); if (safe_create_leading_directories_const(sb_git.buf)) @@ -475,6 +475,8 @@ static int add(int ac, const char **av, const char *prefix) const char *branch; const char *new_branch = NULL; const char *opt_track = NULL; + const char *lock_reason = NULL; + int keep_locked = 0; struct option options[] = { OPT__FORCE(&opts.force, N_("checkout <branch> even if already checked out in other worktree"), @@ -485,7 +487,9 @@ static int add(int ac, const char **av, const char *prefix) N_("create or reset a branch")), OPT_BOOL('d', "detach", &opts.detach, N_("detach HEAD at named commit")), OPT_BOOL(0, "checkout", &opts.checkout, N_("populate the new working tree")), - OPT_BOOL(0, "lock", &opts.keep_locked, N_("keep the new working tree locked")), + OPT_BOOL(0, "lock", &keep_locked, N_("keep the new working tree locked")), + OPT_STRING(0, "reason", &lock_reason, N_("string"), + N_("reason for locking")), OPT__QUIET(&opts.quiet, N_("suppress progress reporting")), OPT_PASSTHRU(0, "track", &opt_track, NULL, N_("set up tracking mode (see git-branch(1))"), @@ -500,6 +504,13 @@ static int add(int ac, const char **av, const char *prefix) ac = parse_options(ac, av, prefix, options, worktree_usage, 0); if (!!opts.detach + !!new_branch + !!new_branch_force > 1) die(_("-b, -B, and --detach are mutually exclusive")); + if (lock_reason && !keep_locked) + die(_("--reason requires --lock")); + if (lock_reason) + opts.keep_locked = lock_reason; + else if (keep_locked) + opts.keep_locked = _("added with --lock"); + if (ac < 1 || ac > 2) usage_with_options(worktree_usage, options); @@ -23,13 +23,16 @@ static struct { { 3, v3_bundle_signature }, }; -static void add_to_ref_list(const struct object_id *oid, const char *name, - struct ref_list *list) +void bundle_header_init(struct bundle_header *header) { - ALLOC_GROW(list->list, list->nr + 1, list->alloc); - oidcpy(&list->list[list->nr].oid, oid); - list->list[list->nr].name = xstrdup(name); - list->nr++; + struct bundle_header blank = BUNDLE_HEADER_INIT; + memcpy(header, &blank, sizeof(*header)); +} + +void bundle_header_release(struct bundle_header *header) +{ + string_list_clear(&header->prerequisites, 1); + string_list_clear(&header->references, 1); } static int parse_capability(struct bundle_header *header, const char *capability) @@ -112,10 +115,11 @@ static int parse_bundle_header(int fd, struct bundle_header *header, status = -1; break; } else { + struct object_id *dup = oiddup(&oid); if (is_prereq) - add_to_ref_list(&oid, "", &header->prerequisites); + string_list_append(&header->prerequisites, "")->util = dup; else - add_to_ref_list(&oid, p + 1, &header->references); + string_list_append(&header->references, p + 1)->util = dup; } } @@ -139,33 +143,38 @@ int read_bundle_header(const char *path, struct bundle_header *header) int is_bundle(const char *path, int quiet) { - struct bundle_header header; + struct bundle_header header = BUNDLE_HEADER_INIT; int fd = open(path, O_RDONLY); if (fd < 0) return 0; - memset(&header, 0, sizeof(header)); fd = parse_bundle_header(fd, &header, quiet ? NULL : path); if (fd >= 0) close(fd); + bundle_header_release(&header); return (fd >= 0); } -static int list_refs(struct ref_list *r, int argc, const char **argv) +static int list_refs(struct string_list *r, int argc, const char **argv) { int i; for (i = 0; i < r->nr; i++) { + struct object_id *oid; + const char *name; + if (argc > 1) { int j; for (j = 1; j < argc; j++) - if (!strcmp(r->list[i].name, argv[j])) + if (!strcmp(r->items[i].string, argv[j])) break; if (j == argc) continue; } - printf("%s %s\n", oid_to_hex(&r->list[i].oid), - r->list[i].name); + + oid = r->items[i].util; + name = r->items[i].string; + printf("%s %s\n", oid_to_hex(oid), name); } return 0; } @@ -181,7 +190,7 @@ int verify_bundle(struct repository *r, * Do fast check, then if any prereqs are missing then go line by line * to be verbose about the errors */ - struct ref_list *p = &header->prerequisites; + struct string_list *p = &header->prerequisites; struct rev_info revs; const char *argv[] = {NULL, "--all", NULL}; struct commit *commit; @@ -193,16 +202,18 @@ int verify_bundle(struct repository *r, repo_init_revisions(r, &revs, NULL); for (i = 0; i < p->nr; i++) { - struct ref_list_entry *e = p->list + i; - struct object *o = parse_object(r, &e->oid); + 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, e->name); + add_pending_object(&revs, o, name); continue; } if (++ret == 1) error("%s", message); - error("%s %s", oid_to_hex(&e->oid), e->name); + error("%s %s", oid_to_hex(oid), name); } if (revs.pending.nr != p->nr) return ret; @@ -218,26 +229,29 @@ int verify_bundle(struct repository *r, i--; for (i = 0; i < p->nr; i++) { - struct ref_list_entry *e = p->list + i; - struct object *o = parse_object(r, &e->oid); + 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; if (++ret == 1) error("%s", message); - error("%s %s", oid_to_hex(&e->oid), e->name); + error("%s %s", oid_to_hex(oid), name); } /* Clean up objects used, as they will be reused. */ for (i = 0; i < p->nr; i++) { - struct ref_list_entry *e = p->list + i; - commit = lookup_commit_reference_gently(r, &e->oid, 1); + 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); } if (verbose) { - struct ref_list *r; + struct string_list *r; r = &header->references; printf_ln(Q_("The bundle contains this ref:", @@ -3,22 +3,23 @@ #include "strvec.h" #include "cache.h" - -struct ref_list { - unsigned int nr, alloc; - struct ref_list_entry { - struct object_id oid; - char *name; - } *list; -}; +#include "string-list.h" struct bundle_header { unsigned version; - struct ref_list prerequisites; - struct ref_list references; + struct string_list prerequisites; + struct string_list references; const struct git_hash_algo *hash_algo; }; +#define BUNDLE_HEADER_INIT \ +{ \ + .prerequisites = STRING_LIST_INIT_DUP, \ + .references = STRING_LIST_INIT_DUP, \ +} +void bundle_header_init(struct bundle_header *header); +void bundle_header_release(struct bundle_header *header); + int is_bundle(const char *path, int quiet); int read_bundle_header(const char *path, struct bundle_header *header); int create_bundle(struct repository *r, const char *path, @@ -1385,6 +1385,7 @@ enum get_oid_result { }; int repo_get_oid(struct repository *r, const char *str, struct object_id *oid); +__attribute__((format (printf, 2, 3))) int get_oidf(struct object_id *oid, const char *fmt, ...); int repo_get_oid_commit(struct repository *r, const char *str, struct object_id *oid); int repo_get_oid_committish(struct repository *r, const char *str, struct object_id *oid); diff --git a/cbtree.c b/cbtree.c new file mode 100644 index 0000000000..b0c65d810f --- /dev/null +++ b/cbtree.c @@ -0,0 +1,167 @@ +/* + * crit-bit tree implementation, does no allocations internally + * For more information on crit-bit trees: https://cr.yp.to/critbit.html + * Based on Adam Langley's adaptation of Dan Bernstein's public domain code + * git clone https://github.com/agl/critbit.git + */ +#include "cbtree.h" + +static struct cb_node *cb_node_of(const void *p) +{ + return (struct cb_node *)((uintptr_t)p - 1); +} + +/* locate the best match, does not do a final comparision */ +static struct cb_node *cb_internal_best_match(struct cb_node *p, + const uint8_t *k, size_t klen) +{ + while (1 & (uintptr_t)p) { + struct cb_node *q = cb_node_of(p); + uint8_t c = q->byte < klen ? k[q->byte] : 0; + size_t direction = (1 + (q->otherbits | c)) >> 8; + + p = q->child[direction]; + } + return p; +} + +/* returns NULL if successful, existing cb_node if duplicate */ +struct cb_node *cb_insert(struct cb_tree *t, struct cb_node *node, size_t klen) +{ + size_t newbyte, newotherbits; + uint8_t c; + int newdirection; + struct cb_node **wherep, *p; + + assert(!((uintptr_t)node & 1)); /* allocations must be aligned */ + + if (!t->root) { /* insert into empty tree */ + t->root = node; + return NULL; /* success */ + } + + /* see if a node already exists */ + p = cb_internal_best_match(t->root, node->k, klen); + + /* find first differing byte */ + for (newbyte = 0; newbyte < klen; newbyte++) { + if (p->k[newbyte] != node->k[newbyte]) + goto different_byte_found; + } + return p; /* element exists, let user deal with it */ + +different_byte_found: + newotherbits = p->k[newbyte] ^ node->k[newbyte]; + newotherbits |= newotherbits >> 1; + newotherbits |= newotherbits >> 2; + newotherbits |= newotherbits >> 4; + newotherbits = (newotherbits & ~(newotherbits >> 1)) ^ 255; + c = p->k[newbyte]; + newdirection = (1 + (newotherbits | c)) >> 8; + + node->byte = newbyte; + node->otherbits = newotherbits; + node->child[1 - newdirection] = node; + + /* find a place to insert it */ + wherep = &t->root; + for (;;) { + struct cb_node *q; + size_t direction; + + p = *wherep; + if (!(1 & (uintptr_t)p)) + break; + q = cb_node_of(p); + if (q->byte > newbyte) + break; + if (q->byte == newbyte && q->otherbits > newotherbits) + break; + c = q->byte < klen ? node->k[q->byte] : 0; + direction = (1 + (q->otherbits | c)) >> 8; + wherep = q->child + direction; + } + + node->child[newdirection] = *wherep; + *wherep = (struct cb_node *)(1 + (uintptr_t)node); + + return NULL; /* success */ +} + +struct cb_node *cb_lookup(struct cb_tree *t, const uint8_t *k, size_t klen) +{ + struct cb_node *p = cb_internal_best_match(t->root, k, klen); + + return p && !memcmp(p->k, k, klen) ? p : NULL; +} + +struct cb_node *cb_unlink(struct cb_tree *t, const uint8_t *k, size_t klen) +{ + struct cb_node **wherep = &t->root; + struct cb_node **whereq = NULL; + struct cb_node *q = NULL; + size_t direction = 0; + uint8_t c; + struct cb_node *p = t->root; + + if (!p) return NULL; /* empty tree, nothing to delete */ + + /* traverse to find best match, keeping link to parent */ + while (1 & (uintptr_t)p) { + whereq = wherep; + q = cb_node_of(p); + c = q->byte < klen ? k[q->byte] : 0; + direction = (1 + (q->otherbits | c)) >> 8; + wherep = q->child + direction; + p = *wherep; + } + + if (memcmp(p->k, k, klen)) + return NULL; /* no match, nothing unlinked */ + + /* found an exact match */ + if (whereq) /* update parent */ + *whereq = q->child[1 - direction]; + else + t->root = NULL; + return p; +} + +static enum cb_next cb_descend(struct cb_node *p, cb_iter fn, void *arg) +{ + if (1 & (uintptr_t)p) { + struct cb_node *q = cb_node_of(p); + enum cb_next n = cb_descend(q->child[0], fn, arg); + + return n == CB_BREAK ? n : cb_descend(q->child[1], fn, arg); + } else { + return fn(p, arg); + } +} + +void cb_each(struct cb_tree *t, const uint8_t *kpfx, size_t klen, + cb_iter fn, void *arg) +{ + struct cb_node *p = t->root; + struct cb_node *top = p; + size_t i = 0; + + if (!p) return; /* empty tree */ + + /* Walk tree, maintaining top pointer */ + while (1 & (uintptr_t)p) { + struct cb_node *q = cb_node_of(p); + uint8_t c = q->byte < klen ? kpfx[q->byte] : 0; + size_t direction = (1 + (q->otherbits | c)) >> 8; + + p = q->child[direction]; + if (q->byte < klen) + top = p; + } + + for (i = 0; i < klen; i++) { + if (p->k[i] != kpfx[i]) + return; /* "best" match failed */ + } + cb_descend(top, fn, arg); +} diff --git a/cbtree.h b/cbtree.h new file mode 100644 index 0000000000..fe4587087e --- /dev/null +++ b/cbtree.h @@ -0,0 +1,56 @@ +/* + * crit-bit tree implementation, does no allocations internally + * For more information on crit-bit trees: https://cr.yp.to/critbit.html + * Based on Adam Langley's adaptation of Dan Bernstein's public domain code + * git clone https://github.com/agl/critbit.git + * + * This is adapted to store arbitrary data (not just NUL-terminated C strings + * and allocates no memory internally. The user needs to allocate + * "struct cb_node" and fill cb_node.k[] with arbitrary match data + * for memcmp. + * If "klen" is variable, then it should be embedded into "c_node.k[]" + * Recursion is bound by the maximum value of "klen" used. + */ +#ifndef CBTREE_H +#define CBTREE_H + +#include "git-compat-util.h" + +struct cb_node; +struct cb_node { + struct cb_node *child[2]; + /* + * n.b. uint32_t for `byte' is excessive for OIDs, + * we may consider shorter variants if nothing else gets stored. + */ + uint32_t byte; + uint8_t otherbits; + uint8_t k[FLEX_ARRAY]; /* arbitrary data */ +}; + +struct cb_tree { + struct cb_node *root; +}; + +enum cb_next { + CB_CONTINUE = 0, + CB_BREAK = 1 +}; + +#define CBTREE_INIT { .root = NULL } + +static inline void cb_init(struct cb_tree *t) +{ + t->root = NULL; +} + +struct cb_node *cb_lookup(struct cb_tree *, const uint8_t *k, size_t klen); +struct cb_node *cb_insert(struct cb_tree *, struct cb_node *, size_t klen); +struct cb_node *cb_unlink(struct cb_tree *t, const uint8_t *k, size_t klen); + +typedef enum cb_next (*cb_iter)(struct cb_node *, void *arg); + +void cb_each(struct cb_tree *, const uint8_t *kpfx, size_t klen, + cb_iter, void *arg); + +#endif /* CBTREE_H */ diff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh index 67852d0d37..5772081b6e 100755 --- a/ci/install-dependencies.sh +++ b/ci/install-dependencies.sh @@ -65,6 +65,11 @@ StaticAnalysis) sudo apt-get -q -y install coccinelle libcurl4-openssl-dev libssl-dev \ libexpat-dev gettext make ;; +sparse) + sudo apt-get -q update -q + sudo apt-get -q -y install libssl-dev libcurl4-openssl-dev \ + libexpat-dev gettext zlib1g-dev + ;; Documentation) sudo apt-get -q update sudo apt-get -q -y install asciidoc xmlto docbook-xsl-ns make diff --git a/combine-diff.c b/combine-diff.c index 7d925ce9ce..d93782daeb 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -403,11 +403,11 @@ static void consume_hunk(void *state_, state->sline[state->nb-1].p_lno[state->n] = state->ob; } -static void consume_line(void *state_, char *line, unsigned long len) +static int consume_line(void *state_, char *line, unsigned long len) { struct combine_diff_state *state = state_; if (!state->lost_bucket) - return; /* not in any hunk yet */ + return 0; /* not in any hunk yet */ switch (line[0]) { case '-': append_lost(state->lost_bucket, state->n, line+1, len-1); @@ -417,6 +417,7 @@ static void consume_line(void *state_, char *line, unsigned long len) state->lno++; break; } + return 0; } static void combine_diff(struct repository *r, diff --git a/commit-graph.c b/commit-graph.c index 2bcb4e0f89..3860a0d847 100644 --- a/commit-graph.c +++ b/commit-graph.c @@ -2408,6 +2408,7 @@ cleanup: #define VERIFY_COMMIT_GRAPH_ERROR_HASH 2 static int verify_commit_graph_error; +__attribute__((format (printf, 1, 2))) static void graph_report(const char *fmt, ...) { va_list ap; @@ -2422,14 +2423,16 @@ static void graph_report(const char *fmt, ...) #define GENERATION_ZERO_EXISTS 1 #define GENERATION_NUMBER_EXISTS 2 +static int commit_graph_checksum_valid(struct commit_graph *g) +{ + return hashfile_checksum_valid(g->data, g->data_len); +} + int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags) { uint32_t i, cur_fanout_pos = 0; struct object_id prev_oid, cur_oid; - unsigned char checksum[GIT_MAX_HEXSZ]; int generation_zero = 0; - struct hashfile *f; - int devnull; struct progress *progress = NULL; int local_error = 0; @@ -2442,11 +2445,7 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags) if (verify_commit_graph_error) return verify_commit_graph_error; - devnull = open("/dev/null", O_WRONLY); - f = hashfd(devnull, NULL); - hashwrite(f, g->data, g->data_len - g->hash_len); - finalize_hashfile(f, checksum, CSUM_CLOSE); - if (!hasheq(checksum, g->data + g->data_len - g->hash_len)) { + if (!commit_graph_checksum_valid(g)) { graph_report(_("the commit-graph file has incorrect checksum and is likely corrupt")); verify_commit_graph_error = VERIFY_COMMIT_GRAPH_ERROR_HASH; } @@ -1178,7 +1178,7 @@ static void handle_signed_tag(struct commit *parent, struct commit_extra_header /* * We could verify this signature and either omit the tag when * it does not validate, but the integrator may not have the - * public key of the signer of the tag he is merging, while a + * public key of the signer of the tag being merged, while a * later auditor may have it while auditing, so let's not run * verify-signed-buffer here for now... * @@ -1833,9 +1833,10 @@ static int git_config_from_blob_ref(config_fn_t fn, char *git_system_config(void) { char *system_config = xstrdup_or_null(getenv("GIT_CONFIG_SYSTEM")); - if (system_config) - return system_config; - return system_path(ETC_GITCONFIG); + if (!system_config) + system_config = system_path(ETC_GITCONFIG); + normalize_path_copy(system_config, system_config); + return system_config; } void git_global_config(char **user_out, char **xdg_out) @@ -2072,7 +2073,7 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha e = xmalloc(sizeof(*e)); hashmap_entry_init(&e->ent, strhash(key)); e->key = xstrdup(key); - string_list_init(&e->value_list, 1); + string_list_init_dup(&e->value_list); hashmap_add(&cs->config_hash, &e->ent); } si = string_list_append_nodup(&e->value_list, xstrdup_or_null(value)); @@ -2837,7 +2838,7 @@ static void maybe_remove_section(struct config_store_data *store, begin = store->parsed[i].begin; /* - * Next, make sure that we are removing he last key(s) in the section, + * Next, make sure that we are removing the last key(s) in the section, * and that there are no comments that are possibly about the current * section. */ @@ -3051,7 +3052,8 @@ int git_config_set_multivar_in_file_gently(const char *config_filename, if (contents == MAP_FAILED) { if (errno == ENODEV && S_ISDIR(st.st_mode)) errno = EISDIR; - error_errno(_("unable to mmap '%s'"), config_filename); + error_errno(_("unable to mmap '%s'%s"), + config_filename, mmap_os_err()); ret = CONFIG_INVALID_FILE; contents = NULL; goto out_free; @@ -450,8 +450,8 @@ void git_configset_init(struct config_set *cs); /** * Parses the file and adds the variable-value pairs to the `config_set`, * dies if there is an error in parsing the file. Returns 0 on success, or - * -1 if the file does not exist or is inaccessible. The user has to decide - * if he wants to free the incomplete configset or continue using it when + * -1 if the file does not exist or is inaccessible. The caller decides + * whether to free the incomplete configset or continue using it when * the function returns -1. */ int git_configset_add_file(struct config_set *cs, const char *filename); diff --git a/config.mak.uname b/config.mak.uname index cb443b4e02..69413fb3dc 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -437,6 +437,11 @@ ifeq ($(uname_S),Windows) NO_POSIX_GOODIES = UnfortunatelyYes NATIVE_CRLF = YesPlease DEFAULT_HELP_FORMAT = html +ifeq (/mingw64,$(subst 32,64,$(prefix))) + # Move system config into top-level /etc/ + ETC_GITCONFIG = ../etc/gitconfig + ETC_GITATTRIBUTES = ../etc/gitattributes +endif CC = compat/vcbuild/scripts/clink.pl AR = compat/vcbuild/scripts/lib.pl @@ -668,9 +673,14 @@ else HAVE_LIBCHARSET_H = YesPlease NO_GETTEXT = USE_GETTEXT_SCHEME = fallthrough - USE_LIBPCRE= YesPlease + USE_LIBPCRE = YesPlease NO_CURL = 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 diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt index a87841340e..171b4124af 100644 --- a/contrib/buildsystems/CMakeLists.txt +++ b/contrib/buildsystems/CMakeLists.txt @@ -43,14 +43,27 @@ NOTE: By default CMake uses Makefile as the build tool on Linux and Visual Studi to use another tool say `ninja` add this to the command line when configuring. `-G Ninja` +NOTE: By default CMake will install vcpkg locally to your source tree on configuration, +to avoid this, add `-DNO_VCPKG=TRUE` to the command line when configuring. + ]] cmake_minimum_required(VERSION 3.14) #set the source directory to root of git set(CMAKE_SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/../..) -if(WIN32) + +option(USE_VCPKG "Whether or not to use vcpkg for obtaining dependencies. Only applicable to Windows platforms" ON) +if(NOT WIN32) + set(USE_VCPKG OFF CACHE BOOL FORCE) +endif() + +if(NOT DEFINED CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) +endif() + +if(USE_VCPKG) set(VCPKG_DIR "${CMAKE_SOURCE_DIR}/compat/vcbuild/vcpkg") - if(MSVC AND NOT EXISTS ${VCPKG_DIR}) + if(NOT EXISTS ${VCPKG_DIR}) message("Initializing vcpkg and building the Git's dependencies (this will take a while...)") execute_process(COMMAND ${CMAKE_SOURCE_DIR}/compat/vcbuild/vcpkg_install.bat) endif() @@ -176,12 +189,18 @@ if(WIN32 AND NOT MSVC)#not required for visual studio builds endif() endif() -find_program(MSGFMT_EXE msgfmt) -if(NOT MSGFMT_EXE) - set(MSGFMT_EXE ${CMAKE_SOURCE_DIR}/compat/vcbuild/vcpkg/downloads/tools/msys2/msys64/usr/bin/msgfmt.exe) - if(NOT EXISTS ${MSGFMT_EXE}) - message(WARNING "Text Translations won't be built") - unset(MSGFMT_EXE) +if(NO_GETTEXT) + message(STATUS "msgfmt not used under NO_GETTEXT") +else() + find_program(MSGFMT_EXE msgfmt) + if(NOT MSGFMT_EXE) + if(USE_VCPKG) + set(MSGFMT_EXE ${CMAKE_SOURCE_DIR}/compat/vcbuild/vcpkg/downloads/tools/msys2/msys64/usr/bin/msgfmt.exe) + endif() + if(NOT EXISTS ${MSGFMT_EXE}) + message(WARNING "Text Translations won't be built") + unset(MSGFMT_EXE) + endif() endif() endif() @@ -204,8 +223,6 @@ list(APPEND compat_SOURCES sha1dc_git.c sha1dc/sha1.c sha1dc/ubc_check.c block-s add_compile_definitions(PAGER_ENV="LESS=FRX LV=-c" - ETC_GITATTRIBUTES="etc/gitattributes" - ETC_GITCONFIG="etc/gitconfig" GIT_EXEC_PATH="libexec/git-core" GIT_LOCALE_PATH="share/locale" GIT_MAN_PATH="share/man" @@ -220,10 +237,15 @@ add_compile_definitions(PAGER_ENV="LESS=FRX LV=-c" if(WIN32) set(FALLBACK_RUNTIME_PREFIX /mingw64) - add_compile_definitions(FALLBACK_RUNTIME_PREFIX="${FALLBACK_RUNTIME_PREFIX}") + # Move system config into top-level /etc/ + add_compile_definitions(FALLBACK_RUNTIME_PREFIX="${FALLBACK_RUNTIME_PREFIX}" + ETC_GITATTRIBUTES="../etc/gitattributes" + ETC_GITCONFIG="../etc/gitconfig") else() set(FALLBACK_RUNTIME_PREFIX /home/$ENV{USER}) - add_compile_definitions(FALLBACK_RUNTIME_PREFIX="${FALLBACK_RUNTIME_PREFIX}") + add_compile_definitions(FALLBACK_RUNTIME_PREFIX="${FALLBACK_RUNTIME_PREFIX}" + ETC_GITATTRIBUTES="etc/gitattributes" + ETC_GITCONFIG="etc/gitconfig") endif() @@ -982,7 +1004,7 @@ file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_GETTEXT='${NO_GETTEXT}'\n" file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "RUNTIME_PREFIX='${RUNTIME_PREFIX}'\n") file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_PYTHON='${NO_PYTHON}'\n") file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "SUPPORTS_SIMPLE_IPC='${SUPPORTS_SIMPLE_IPC}'\n") -if(WIN32) +if(USE_VCPKG) file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "PATH=\"$PATH:$TEST_DIRECTORY/../compat/vcbuild/vcpkg/installed/x64-windows/bin\"\n") endif() diff --git a/contrib/credential/osxkeychain/git-credential-osxkeychain.c b/contrib/credential/osxkeychain/git-credential-osxkeychain.c index bcd3f575a3..0b44a9b7cc 100644 --- a/contrib/credential/osxkeychain/git-credential-osxkeychain.c +++ b/contrib/credential/osxkeychain/git-credential-osxkeychain.c @@ -10,6 +10,7 @@ static char *username; static char *password; static UInt16 port; +__attribute__((format (printf, 1, 2))) static void die(const char *err, ...) { char msg[4096]; diff --git a/contrib/credential/wincred/git-credential-wincred.c b/contrib/credential/wincred/git-credential-wincred.c index 5bdad41de1..5091048f9c 100644 --- a/contrib/credential/wincred/git-credential-wincred.c +++ b/contrib/credential/wincred/git-credential-wincred.c @@ -11,6 +11,7 @@ #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) +__attribute__((format (printf, 1, 2))) static void die(const char *err, ...) { char msg[4096]; diff --git a/credential.c b/credential.c index e5202fbef2..3c05c7c669 100644 --- a/credential.c +++ b/credential.c @@ -10,8 +10,8 @@ void credential_init(struct credential *c) { - memset(c, 0, sizeof(*c)); - c->helpers.strdup_strings = 1; + struct credential blank = CREDENTIAL_INIT; + memcpy(c, &blank, sizeof(*c)); } void credential_clear(struct credential *c) diff --git a/credential.h b/credential.h index c0e17e3554..f430e77fea 100644 --- a/credential.h +++ b/credential.h @@ -128,7 +128,9 @@ struct credential { char *path; }; -#define CREDENTIAL_INIT { STRING_LIST_INIT_DUP } +#define CREDENTIAL_INIT { \ + .helpers = STRING_LIST_INIT_DUP, \ +} /* Initialize a credential structure, setting all fields to empty. */ void credential_init(struct credential *); diff --git a/csum-file.c b/csum-file.c index 3487d28ed7..c951cf8277 100644 --- a/csum-file.c +++ b/csum-file.c @@ -217,3 +217,19 @@ uint32_t crc32_end(struct hashfile *f) f->do_crc = 0; return f->crc32; } + +int hashfile_checksum_valid(const unsigned char *data, size_t total_len) +{ + unsigned char got[GIT_MAX_RAWSZ]; + git_hash_ctx ctx; + size_t data_len = total_len - the_hash_algo->rawsz; + + if (total_len < the_hash_algo->rawsz) + return 0; /* say "too short"? */ + + the_hash_algo->init_fn(&ctx); + the_hash_algo->update_fn(&ctx, data, data_len); + the_hash_algo->final_fn(got, &ctx); + + return hasheq(got, data + data_len); +} diff --git a/csum-file.h b/csum-file.h index 3044bd19ab..291215b34e 100644 --- a/csum-file.h +++ b/csum-file.h @@ -44,6 +44,9 @@ void hashflush(struct hashfile *f); void crc32_begin(struct hashfile *); uint32_t crc32_end(struct hashfile *); +/* Verify checksum validity while reading. Returns non-zero on success. */ +int hashfile_checksum_valid(const unsigned char *data, size_t len); + /* * Returns the total number of bytes fed to the hashfile so far (including ones * that have not been written out to the descriptor yet). @@ -908,7 +908,7 @@ int parse_expiry_date(const char *date, timestamp_t *timestamp) /* * We take over "now" here, which usually translates * to the current timestamp. This is because the user - * really means to expire everything she has done in + * really means to expire everything that was done in * the past, and by definition reflogs are the record * of the past, and there is nothing from the future * to be kept. diff --git a/diff-lib.c b/diff-lib.c index c2ac9250fe..f9eadc4fc1 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -325,6 +325,11 @@ static void show_new_file(struct rev_info *revs, unsigned dirty_submodule = 0; struct index_state *istate = revs->diffopt.repo->index; + if (new_file && S_ISSPARSEDIR(new_file->ce_mode)) { + diff_tree_oid(NULL, &new_file->oid, new_file->name, &revs->diffopt); + return; + } + /* * New file in the index: it might actually be different in * the working tree. @@ -347,6 +352,20 @@ static int show_modified(struct rev_info *revs, unsigned dirty_submodule = 0; struct index_state *istate = revs->diffopt.repo->index; + assert(S_ISSPARSEDIR(old_entry->ce_mode) == + S_ISSPARSEDIR(new_entry->ce_mode)); + + /* + * If both are sparse directory entries, then expand the + * modifications to the file level. If only one was a sparse + * directory, then they appear as an add and delete instead of + * a modification. + */ + if (S_ISSPARSEDIR(new_entry->ce_mode)) { + diff_tree_oid(&old_entry->oid, &new_entry->oid, new_entry->name, &revs->diffopt); + return 0; + } + if (get_stat_data(istate, new_entry, &oid, &mode, cached, match_missing, &dirty_submodule, &revs->diffopt) < 0) { if (report_missing) @@ -2340,7 +2340,7 @@ static void find_lno(const char *line, struct emit_callback *ecbdata) ecbdata->lno_in_postimage = strtol(p + 1, NULL, 10); } -static void fn_out_consume(void *priv, char *line, unsigned long len) +static int fn_out_consume(void *priv, char *line, unsigned long len) { struct emit_callback *ecbdata = priv; struct diff_options *o = ecbdata->opt; @@ -2376,7 +2376,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) len = sane_truncate_line(line, len); find_lno(line, ecbdata); emit_hunk_header(ecbdata, line, len); - return; + return 0; } if (ecbdata->diff_words) { @@ -2386,11 +2386,11 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) if (line[0] == '-') { diff_words_append(line, len, &ecbdata->diff_words->minus); - return; + return 0; } else if (line[0] == '+') { diff_words_append(line, len, &ecbdata->diff_words->plus); - return; + return 0; } else if (starts_with(line, "\\ ")) { /* * Eat the "no newline at eof" marker as if we @@ -2399,11 +2399,11 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) * defer processing. If this is the end of * preimage, more "+" lines may come after it. */ - return; + return 0; } diff_words_flush(ecbdata); emit_diff_symbol(o, s, line, len, 0); - return; + return 0; } switch (line[0]) { @@ -2427,6 +2427,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) line, len, 0); break; } + return 0; } static void pprint_rename(struct strbuf *name, const char *a, const char *b) @@ -2526,7 +2527,7 @@ static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat, return x; } -static void diffstat_consume(void *priv, char *line, unsigned long len) +static int diffstat_consume(void *priv, char *line, unsigned long len) { struct diffstat_t *diffstat = priv; struct diffstat_file *x = diffstat->files[diffstat->nr - 1]; @@ -2535,6 +2536,7 @@ static void diffstat_consume(void *priv, char *line, unsigned long len) x->added++; else if (line[0] == '-') x->deleted++; + return 0; } const char mime_boundary_leader[] = "------------"; @@ -3212,7 +3214,7 @@ static void checkdiff_consume_hunk(void *priv, data->lineno = nb - 1; } -static void checkdiff_consume(void *priv, char *line, unsigned long len) +static int checkdiff_consume(void *priv, char *line, unsigned long len) { struct checkdiff_t *data = priv; int marker_size = data->conflict_marker_size; @@ -3236,7 +3238,7 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) } bad = ws_check(line + 1, len - 1, data->ws_rule); if (!bad) - return; + return 0; data->status |= bad; err = whitespace_error_string(bad); fprintf(data->o->file, "%s%s:%d: %s.\n", @@ -3248,6 +3250,7 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) } else if (line[0] == ' ') { data->lineno++; } + return 0; } static unsigned char *deflate_it(char *data, @@ -3726,7 +3729,8 @@ static void builtin_diffstat(const char *name_a, const char *name_b, xpp.anchors_nr = o->anchors_nr; xecfg.ctxlen = o->context; xecfg.interhunkctxlen = o->interhunkcontext; - if (xdi_diff_outf(&mf1, &mf2, discard_hunk_line, + xecfg.flags = XDL_EMIT_NO_HUNK_HDR; + if (xdi_diff_outf(&mf1, &mf2, NULL, diffstat_consume, diffstat, &xpp, &xecfg)) die("unable to generate diffstat for %s", one->path); @@ -4632,6 +4636,12 @@ void diff_setup_done(struct diff_options *options) if (HAS_MULTI_BITS(options->pickaxe_opts & DIFF_PICKAXE_KINDS_MASK)) die(_("-G, -S and --find-object are mutually exclusive")); + if (HAS_MULTI_BITS(options->pickaxe_opts & DIFF_PICKAXE_KINDS_G_REGEX_MASK)) + die(_("-G and --pickaxe-regex are mutually exclusive, use --pickaxe-regex with -S")); + + if (HAS_MULTI_BITS(options->pickaxe_opts & DIFF_PICKAXE_KINDS_ALL_OBJFIND_MASK)) + die(_("---pickaxe-all and --find-object are mutually exclusive, use --pickaxe-all with -G and -S")); + /* * Most of the time we can say "there are changes" * only by checking if there are changed paths, but @@ -6119,17 +6129,18 @@ void flush_one_hunk(struct object_id *result, git_hash_ctx *ctx) } } -static void patch_id_consume(void *priv, char *line, unsigned long len) +static int patch_id_consume(void *priv, char *line, unsigned long len) { struct patch_id_t *data = priv; int new_len; if (len > 12 && starts_with(line, "\\ ")) - return; + return 0; new_len = remove_space(line, len); the_hash_algo->update_fn(data->ctx, line, new_len); data->patchlen += new_len; + return 0; } static void patch_id_add_string(git_hash_ctx *ctx, const char *str) @@ -6227,8 +6238,8 @@ static int diff_get_patch_id(struct diff_options *options, struct object_id *oid xpp.flags = 0; xecfg.ctxlen = 3; - xecfg.flags = 0; - if (xdi_diff_outf(&mf1, &mf2, discard_hunk_line, + xecfg.flags = XDL_EMIT_NO_HUNK_HDR; + if (xdi_diff_outf(&mf1, &mf2, NULL, patch_id_consume, &data, &xpp, &xecfg)) return error("unable to generate patch-id diff for %s", p->one->path); @@ -265,6 +265,7 @@ struct diff_options { * postimage of the diff_queue. */ const char *pickaxe; + unsigned pickaxe_opts; /* -I<regex> */ regex_t **ignore_regex; @@ -304,8 +305,6 @@ struct diff_options { /* The output format used when `diff_flush()` is run. */ int output_format; - unsigned pickaxe_opts; - /* Affects the way detection logic for complete rewrites, renames and * copies. */ @@ -556,6 +555,10 @@ int git_config_rename(const char *var, const char *value); #define DIFF_PICKAXE_KINDS_MASK (DIFF_PICKAXE_KIND_S | \ DIFF_PICKAXE_KIND_G | \ DIFF_PICKAXE_KIND_OBJFIND) +#define DIFF_PICKAXE_KINDS_G_REGEX_MASK (DIFF_PICKAXE_KIND_G | \ + DIFF_PICKAXE_REGEX) +#define DIFF_PICKAXE_KINDS_ALL_OBJFIND_MASK (DIFF_PICKAXE_ALL | \ + DIFF_PICKAXE_KIND_OBJFIND) #define DIFF_PICKAXE_IGNORE_CASE 32 diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c index a9c6d60df2..c88e50c632 100644 --- a/diffcore-pickaxe.c +++ b/diffcore-pickaxe.c @@ -19,38 +19,31 @@ struct diffgrep_cb { int hit; }; -static void diffgrep_consume(void *priv, char *line, unsigned long len) +static int diffgrep_consume(void *priv, char *line, unsigned long len) { struct diffgrep_cb *data = priv; regmatch_t regmatch; if (line[0] != '+' && line[0] != '-') - return; + return 0; if (data->hit) - /* - * NEEDSWORK: we should have a way to terminate the - * caller early. - */ - return; - data->hit = !regexec_buf(data->regexp, line + 1, len - 1, 1, - ®match, 0); + BUG("Already matched in diffgrep_consume! Broken xdiff_emit_line_fn?"); + if (!regexec_buf(data->regexp, line + 1, len - 1, 1, + ®match, 0)) { + data->hit = 1; + return 1; + } + return 0; } static int diff_grep(mmfile_t *one, mmfile_t *two, struct diff_options *o, regex_t *regexp, kwset_t kws) { - regmatch_t regmatch; struct diffgrep_cb ecbdata; xpparam_t xpp; xdemitconf_t xecfg; - - if (!one) - return !regexec_buf(regexp, two->ptr, two->size, - 1, ®match, 0); - if (!two) - return !regexec_buf(regexp, one->ptr, one->size, - 1, ®match, 0); + int ret; /* * We have both sides; need to run textual diff and see if @@ -60,38 +53,47 @@ static int diff_grep(mmfile_t *one, mmfile_t *two, memset(&xecfg, 0, sizeof(xecfg)); ecbdata.regexp = regexp; ecbdata.hit = 0; + xecfg.flags = XDL_EMIT_NO_HUNK_HDR; xecfg.ctxlen = o->context; xecfg.interhunkctxlen = o->interhunkcontext; - if (xdi_diff_outf(one, two, discard_hunk_line, diffgrep_consume, - &ecbdata, &xpp, &xecfg)) - return 0; - return ecbdata.hit; + + /* + * An xdiff error might be our "data->hit" from above. See the + * comment for xdiff_emit_line_fn in xdiff-interface.h + */ + ret = xdi_diff_outf(one, two, NULL, diffgrep_consume, + &ecbdata, &xpp, &xecfg); + if (ecbdata.hit) + return 1; + if (ret) + return ret; + return 0; } -static unsigned int contains(mmfile_t *mf, regex_t *regexp, kwset_t kws) +static unsigned int contains(mmfile_t *mf, regex_t *regexp, kwset_t kws, + unsigned int limit) { - unsigned int cnt; - unsigned long sz; - const char *data; - - sz = mf->size; - data = mf->ptr; - cnt = 0; + unsigned int cnt = 0; + unsigned long sz = mf->size; + const char *data = mf->ptr; if (regexp) { regmatch_t regmatch; int flags = 0; - while (sz && *data && + while (sz && !regexec_buf(regexp, data, sz, 1, ®match, flags)) { flags |= REG_NOTBOL; data += regmatch.rm_eo; sz -= regmatch.rm_eo; - if (sz && *data && regmatch.rm_so == regmatch.rm_eo) { + if (sz && regmatch.rm_so == regmatch.rm_eo) { data++; sz--; } cnt++; + + if (limit && cnt == limit) + return cnt; } } else { /* Classic exact string match */ @@ -103,6 +105,9 @@ static unsigned int contains(mmfile_t *mf, regex_t *regexp, kwset_t kws) sz -= offset + kwsm.size[0]; data += offset + kwsm.size[0]; cnt++; + + if (limit && cnt == limit) + return cnt; } } return cnt; @@ -112,9 +117,9 @@ static int has_changes(mmfile_t *one, mmfile_t *two, struct diff_options *o, regex_t *regexp, kwset_t kws) { - unsigned int one_contains = one ? contains(one, regexp, kws) : 0; - unsigned int two_contains = two ? contains(two, regexp, kws) : 0; - return one_contains != two_contains; + unsigned int c1 = one ? contains(one, regexp, kws, 0) : 0; + unsigned int c2 = two ? contains(two, regexp, kws, c1 + 1) : 0; + return c1 != c2; } static int pickaxe_match(struct diff_filepair *p, struct diff_options *o, @@ -136,9 +141,6 @@ static int pickaxe_match(struct diff_filepair *p, struct diff_options *o, oidset_contains(o->objfind, &p->two->oid)); } - if (!o->pickaxe[0]) - return 0; - if (o->flags.allow_textconv) { textconv_one = get_textconv(o->repo, p->one); textconv_two = get_textconv(o->repo, p->two); @@ -163,9 +165,7 @@ static int pickaxe_match(struct diff_filepair *p, struct diff_options *o, mf1.size = fill_textconv(o->repo, textconv_one, p->one, &mf1.ptr); mf2.size = fill_textconv(o->repo, textconv_two, p->two, &mf2.ptr); - ret = fn(DIFF_FILE_VALID(p->one) ? &mf1 : NULL, - DIFF_FILE_VALID(p->two) ? &mf2 : NULL, - o, regexp, kws); + ret = fn(&mf1, &mf2, o, regexp, kws); if (textconv_one) free(mf1.ptr); @@ -232,13 +232,31 @@ void diffcore_pickaxe(struct diff_options *o) int opts = o->pickaxe_opts; regex_t regex, *regexp = NULL; kwset_t kws = NULL; + pickaxe_fn fn; + if (opts & ~DIFF_PICKAXE_KIND_OBJFIND && + (!needle || !*needle)) + BUG("should have needle under -G or -S"); if (opts & (DIFF_PICKAXE_REGEX | DIFF_PICKAXE_KIND_G)) { int cflags = REG_EXTENDED | REG_NEWLINE; if (o->pickaxe_opts & DIFF_PICKAXE_IGNORE_CASE) cflags |= REG_ICASE; regcomp_or_die(®ex, needle, cflags); regexp = ®ex; + + if (opts & DIFF_PICKAXE_KIND_G) + fn = diff_grep; + else if (opts & DIFF_PICKAXE_REGEX) + fn = has_changes; + else + /* + * We don't need to check the combination of + * -G and --pickaxe-regex, by the time we get + * here diff.c has already died if they're + * combined. See the usage tests in + * t4209-log-pickaxe.sh. + */ + BUG("unreachable"); } else if (opts & DIFF_PICKAXE_KIND_S) { if (o->pickaxe_opts & DIFF_PICKAXE_IGNORE_CASE && has_non_ascii(needle)) { @@ -255,10 +273,14 @@ void diffcore_pickaxe(struct diff_options *o) kwsincr(kws, needle, strlen(needle)); kwsprep(kws); } + fn = has_changes; + } else if (opts & DIFF_PICKAXE_KIND_OBJFIND) { + fn = NULL; + } else { + BUG("unknown pickaxe_opts flag"); } - pickaxe(&diff_queued_diff, o, regexp, kws, - (opts & DIFF_PICKAXE_KIND_G) ? diff_grep : has_changes); + pickaxe(&diff_queued_diff, o, regexp, kws, fn); if (regexp) regfree(regexp); diff --git a/diffcore-rename.c b/diffcore-rename.c index 513ba7b05f..2618bb07c1 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -54,7 +54,7 @@ static void register_rename_src(struct diff_filepair *p) if (p->broken_pair) { if (!break_idx) { break_idx = xmalloc(sizeof(*break_idx)); - strintmap_init(break_idx, -1); + strintmap_init_with_options(break_idx, -1, NULL, 0); } strintmap_set(break_idx, p->one->path, rename_dst_nr); } @@ -87,13 +87,13 @@ struct diff_score { short name_score; }; -struct prefetch_options { +struct inexact_prefetch_options { struct repository *repo; int skip_unmodified; }; -static void prefetch(void *prefetch_options) +static void inexact_prefetch(void *prefetch_options) { - struct prefetch_options *options = prefetch_options; + struct inexact_prefetch_options *options = prefetch_options; int i; struct oid_array to_fetch = OID_ARRAY_INIT; @@ -126,7 +126,7 @@ static int estimate_similarity(struct repository *r, struct diff_filespec *src, struct diff_filespec *dst, int minimum_score, - int skip_unmodified) + struct diff_populate_filespec_options *dpf_opt) { /* src points at a file that existed in the original tree (or * optionally a file in the destination tree) and dst points @@ -143,15 +143,6 @@ static int estimate_similarity(struct repository *r, */ unsigned long max_size, delta_size, base_size, src_copied, literal_added; int score; - struct diff_populate_filespec_options dpf_options = { - .check_size_only = 1 - }; - struct prefetch_options prefetch_options = {r, skip_unmodified}; - - if (r == the_repository && has_promisor_remote()) { - dpf_options.missing_object_cb = prefetch; - dpf_options.missing_object_data = &prefetch_options; - } /* We deal only with regular files. Symlink renames are handled * only when they are exact matches --- in other words, no edits @@ -169,11 +160,13 @@ static int estimate_similarity(struct repository *r, * is a possible size - we really should have a flag to * say whether the size is valid or not!) */ + dpf_opt->check_size_only = 1; + if (!src->cnt_data && - diff_populate_filespec(r, src, &dpf_options)) + diff_populate_filespec(r, src, dpf_opt)) return 0; if (!dst->cnt_data && - diff_populate_filespec(r, dst, &dpf_options)) + diff_populate_filespec(r, dst, dpf_opt)) return 0; max_size = ((src->size > dst->size) ? src->size : dst->size); @@ -191,11 +184,11 @@ static int estimate_similarity(struct repository *r, if (max_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE) return 0; - dpf_options.check_size_only = 0; + dpf_opt->check_size_only = 0; - if (!src->cnt_data && diff_populate_filespec(r, src, &dpf_options)) + if (!src->cnt_data && diff_populate_filespec(r, src, dpf_opt)) return 0; - if (!dst->cnt_data && diff_populate_filespec(r, dst, &dpf_options)) + if (!dst->cnt_data && diff_populate_filespec(r, dst, dpf_opt)) return 0; if (diffcore_count_changes(r, src, dst, @@ -823,6 +816,78 @@ static int idx_possible_rename(char *filename, struct dir_rename_info *info) return idx; } +struct basename_prefetch_options { + struct repository *repo; + struct strintmap *relevant_sources; + struct strintmap *sources; + struct strintmap *dests; + struct dir_rename_info *info; +}; +static void basename_prefetch(void *prefetch_options) +{ + struct basename_prefetch_options *options = prefetch_options; + struct strintmap *relevant_sources = options->relevant_sources; + struct strintmap *sources = options->sources; + struct strintmap *dests = options->dests; + struct dir_rename_info *info = options->info; + int i; + struct oid_array to_fetch = OID_ARRAY_INIT; + + /* + * TODO: The following loops mirror the code/logic from + * find_basename_matches(), though not quite exactly. Maybe + * abstract the iteration logic out somehow? + */ + for (i = 0; i < rename_src_nr; ++i) { + char *filename = rename_src[i].p->one->path; + const char *base = NULL; + intptr_t src_index; + intptr_t dst_index; + + /* Skip irrelevant sources */ + if (relevant_sources && + !strintmap_contains(relevant_sources, filename)) + continue; + + /* + * If the basename is unique among remaining sources, then + * src_index will equal 'i' and we can attempt to match it + * to a unique basename in the destinations. Otherwise, + * use directory rename heuristics, if possible. + */ + base = get_basename(filename); + src_index = strintmap_get(sources, base); + assert(src_index == -1 || src_index == i); + + if (strintmap_contains(dests, base)) { + struct diff_filespec *one, *two; + + /* Find a matching destination, if possible */ + dst_index = strintmap_get(dests, base); + if (src_index == -1 || dst_index == -1) { + src_index = i; + dst_index = idx_possible_rename(filename, info); + } + if (dst_index == -1) + continue; + + /* Ignore this dest if already used in a rename */ + if (rename_dst[dst_index].is_rename) + continue; /* already used previously */ + + one = rename_src[src_index].p->one; + two = rename_dst[dst_index].p->two; + + /* Add the pairs */ + diff_add_if_missing(options->repo, &to_fetch, two); + diff_add_if_missing(options->repo, &to_fetch, one); + } + } + + promisor_remote_get_direct(options->repo, to_fetch.oid, to_fetch.nr); + oid_array_clear(&to_fetch); +} + static int find_basename_matches(struct diff_options *options, int minimum_score, struct dir_rename_info *info, @@ -862,18 +927,18 @@ static int find_basename_matches(struct diff_options *options, int i, renames = 0; struct strintmap sources; struct strintmap dests; - - /* - * The prefeteching stuff wants to know if it can skip prefetching - * blobs that are unmodified...and will then do a little extra work - * to verify that the oids are indeed different before prefetching. - * Unmodified blobs are only relevant when doing copy detection; - * when limiting to rename detection, diffcore_rename[_extended]() - * will never be called with unmodified source paths fed to us, so - * the extra work necessary to check if rename_src entries are - * unmodified would be a small waste. - */ - int skip_unmodified = 0; + struct diff_populate_filespec_options dpf_options = { + .check_binary = 0, + .missing_object_cb = NULL, + .missing_object_data = NULL + }; + struct basename_prefetch_options prefetch_options = { + .repo = options->repo, + .relevant_sources = relevant_sources, + .sources = &sources, + .dests = &dests, + .info = info + }; /* * Create maps of basename -> fullname(s) for remaining sources and @@ -910,6 +975,11 @@ static int find_basename_matches(struct diff_options *options, strintmap_set(&dests, base, i); } + if (options->repo == the_repository && has_promisor_remote()) { + dpf_options.missing_object_cb = basename_prefetch; + dpf_options.missing_object_data = &prefetch_options; + } + /* Now look for basename matchups and do similarity estimation */ for (i = 0; i < rename_src_nr; ++i) { char *filename = rename_src[i].p->one->path; @@ -953,7 +1023,7 @@ static int find_basename_matches(struct diff_options *options, one = rename_src[src_index].p->one; two = rename_dst[dst_index].p->two; score = estimate_similarity(options->repo, one, two, - minimum_score, skip_unmodified); + minimum_score, &dpf_options); /* If sufficiently similar, record as rename pair */ if (score < minimum_score) @@ -1272,6 +1342,14 @@ void diffcore_rename_extended(struct diff_options *options, int num_sources, want_copies; struct progress *progress = NULL; struct dir_rename_info info; + struct diff_populate_filespec_options dpf_options = { + .check_binary = 0, + .missing_object_cb = NULL, + .missing_object_data = NULL + }; + struct inexact_prefetch_options prefetch_options = { + .repo = options->repo + }; trace2_region_enter("diff", "setup", options->repo); info.setup = 0; @@ -1433,6 +1511,13 @@ void diffcore_rename_extended(struct diff_options *options, (uint64_t)num_destinations * (uint64_t)num_sources); } + /* Finish setting up dpf_options */ + prefetch_options.skip_unmodified = skip_unmodified; + if (options->repo == the_repository && has_promisor_remote()) { + dpf_options.missing_object_cb = inexact_prefetch; + dpf_options.missing_object_data = &prefetch_options; + } + CALLOC_ARRAY(mx, st_mult(NUM_CANDIDATE_PER_DST, num_destinations)); for (dst_cnt = i = 0; i < rename_dst_nr; i++) { struct diff_filespec *two = rename_dst[i].p->two; @@ -1458,7 +1543,7 @@ void diffcore_rename_extended(struct diff_options *options, this_src.score = estimate_similarity(options->repo, one, two, minimum_score, - skip_unmodified); + &dpf_options); this_src.name_score = basename_same(one, two); this_src.dst = i; this_src.src = j; @@ -1543,7 +1628,7 @@ void diffcore_rename_extended(struct diff_options *options, /* all the usual ones need to be kept */ diff_q(&outq, p); else - /* no need to keep unmodified pairs; FIXME: remove earlier? */ + /* no need to keep unmodified pairs */ pair_to_free = p; if (pair_to_free) @@ -53,12 +53,6 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir, int check_only, int stop_at_first_file, const struct pathspec *pathspec); static int resolve_dtype(int dtype, struct index_state *istate, const char *path, int len); - -void dir_init(struct dir_struct *dir) -{ - memset(dir, 0, sizeof(*dir)); -} - struct dirent *readdir_skip_dot_and_dotdot(DIR *dirp) { struct dirent *e; @@ -84,11 +78,21 @@ int fspathcmp(const char *a, const char *b) return ignore_case ? strcasecmp(a, b) : strcmp(a, b); } +int fspatheq(const char *a, const char *b) +{ + return !fspathcmp(a, b); +} + int fspathncmp(const char *a, const char *b, size_t count) { return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count); } +unsigned int fspathhash(const char *str) +{ + return ignore_case ? strihash(str) : strhash(str); +} + int git_fnmatch(const struct pathspec_item *item, const char *pattern, const char *string, int prefix) @@ -1376,7 +1380,7 @@ enum pattern_match_result path_matches_pattern_list( struct path_pattern *pattern; struct strbuf parent_pathname = STRBUF_INIT; int result = NOT_MATCHED; - const char *slash_pos; + size_t slash_pos; if (!pl->use_cone_patterns) { pattern = last_matching_pattern_from_list(pathname, pathlen, basename, @@ -1397,21 +1401,35 @@ enum pattern_match_result path_matches_pattern_list( strbuf_addch(&parent_pathname, '/'); strbuf_add(&parent_pathname, pathname, pathlen); + /* + * Directory entries are matched if and only if a file + * contained immediately within them is matched. For the + * case of a directory entry, modify the path to create + * a fake filename within this directory, allowing us to + * use the file-base matching logic in an equivalent way. + */ + if (parent_pathname.len > 0 && + parent_pathname.buf[parent_pathname.len - 1] == '/') { + slash_pos = parent_pathname.len - 1; + strbuf_add(&parent_pathname, "-", 1); + } else { + const char *slash_ptr = strrchr(parent_pathname.buf, '/'); + slash_pos = slash_ptr ? slash_ptr - parent_pathname.buf : 0; + } + if (hashmap_contains_path(&pl->recursive_hashmap, &parent_pathname)) { result = MATCHED_RECURSIVE; goto done; } - slash_pos = strrchr(parent_pathname.buf, '/'); - - if (slash_pos == parent_pathname.buf) { + if (!slash_pos) { /* include every file in root */ result = MATCHED; goto done; } - strbuf_setlen(&parent_pathname, slash_pos - parent_pathname.buf); + strbuf_setlen(&parent_pathname, slash_pos); if (hashmap_contains_path(&pl->parent_hashmap, &parent_pathname)) { result = MATCHED; @@ -3105,6 +3123,7 @@ void dir_clear(struct dir_struct *dir) struct exclude_list_group *group; struct pattern_list *pl; struct exclude_stack *stk; + struct dir_struct new = DIR_INIT; for (i = EXC_CMDL; i <= EXC_FILE; i++) { group = &dir->exclude_list_group[i]; @@ -3132,7 +3151,7 @@ void dir_clear(struct dir_struct *dir) } strbuf_release(&dir->basebuf); - dir_init(dir); + memcpy(dir, &new, sizeof(*dir)); } struct ondisk_untracked_cache { @@ -342,6 +342,8 @@ struct dir_struct { unsigned visited_directories; }; +#define DIR_INIT { 0 } + struct dirent *readdir_skip_dot_and_dotdot(DIR *dirp); /*Count the number of slashes for string s*/ @@ -367,8 +369,6 @@ int match_pathspec(struct index_state *istate, int report_path_error(const char *ps_matched, const struct pathspec *pathspec); int within_depth(const char *name, int namelen, int depth, int max_depth); -void dir_init(struct dir_struct *dir); - int fill_directory(struct dir_struct *dir, struct index_state *istate, const struct pathspec *pathspec); @@ -489,7 +489,9 @@ int remove_dir_recursively(struct strbuf *path, int flag); int remove_path(const char *path); int fspathcmp(const char *a, const char *b); +int fspatheq(const char *a, const char *b); int fspathncmp(const char *a, const char *b, size_t count); +unsigned int fspathhash(const char *str); /* * The prefix part of pattern must not contains wildcards. @@ -143,8 +143,8 @@ void enable_delayed_checkout(struct checkout *state) if (!state->delayed_checkout) { state->delayed_checkout = xmalloc(sizeof(*state->delayed_checkout)); state->delayed_checkout->state = CE_CAN_DELAY; - string_list_init(&state->delayed_checkout->filters, 0); - string_list_init(&state->delayed_checkout->paths, 0); + string_list_init_nodup(&state->delayed_checkout->filters); + string_list_init_nodup(&state->delayed_checkout->paths); } } @@ -66,6 +66,7 @@ const char *get_preferred_languages(void) } #ifndef NO_GETTEXT +__attribute__((format (printf, 1, 2))) static int test_vsnprintf(const char *fmt, ...) { char buf[26]; diff --git a/git-compat-util.h b/git-compat-util.h index dca72cba29..b46605300a 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -876,6 +876,7 @@ char *xstrndup(const char *str, size_t len); void *xrealloc(void *ptr, size_t size); void *xcalloc(size_t nmemb, size_t size); void *xmmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); +const char *mmap_os_err(void); void *xmmap_gently(void *start, size_t length, int prot, int flags, int fd, off_t offset); int xopen(const char *path, int flags, ...); ssize_t xread(int fd, void *buf, size_t len); @@ -1977,8 +1977,11 @@ class P4Submit(Command, P4UserMap): newdiff += "+%s\n" % os.readlink(newFile) else: f = open(newFile, "r") - for line in f.readlines(): - newdiff += "+" + line + try: + for line in f.readlines(): + newdiff += "+" + line + except UnicodeDecodeError: + pass # Found non-text data and skip, since diff description should only include text f.close() return (diff + newdiff).replace('\r\n', '\n') diff --git a/git-send-email.perl b/git-send-email.perl index 7ba0b3433d..e65d969d0b 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -18,21 +18,11 @@ use 5.008; use strict; -use warnings; -use POSIX qw/strftime/; -use Term::ReadLine; +use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : (); use Getopt::Long; -use Text::ParseWords; -use Term::ANSIColor; -use File::Temp qw/ tempdir tempfile /; -use File::Spec::Functions qw(catdir catfile); use Git::LoadCPAN::Error qw(:try); -use Cwd qw(abs_path cwd); use Git; use Git::I18N; -use Net::Domain (); -use Net::SMTP (); -use Git::LoadCPAN::Mail::Address; Getopt::Long::Configure qw/ pass_through /; @@ -167,7 +157,6 @@ sub format_2822_time { ); } -my $have_email_valid = eval { require Email::Valid; 1 }; my $smtp; my $auth; my $num_sent = 0; @@ -193,14 +182,6 @@ my (@config_bcc, @getopt_bcc); my $repo = eval { Git->repository() }; my @repo = $repo ? ($repo) : (); -my $term = eval { - $ENV{"GIT_SEND_EMAIL_NOTTY"} - ? new Term::ReadLine 'git-send-email', \*STDIN, \*STDOUT - : new Term::ReadLine 'git-send-email'; -}; -if ($@) { - $term = new FakeTerm "$@: going non-interactive"; -} # Behavior modification variables my ($quiet, $dry_run) = (0, 0); @@ -289,6 +270,7 @@ my %config_bool_settings = ( ); my %config_settings = ( + "smtpencryption" => \$smtp_encryption, "smtpserver" => \$smtp_server, "smtpserverport" => \$smtp_server_port, "smtpserveroption" => \@smtp_server_options, @@ -321,9 +303,9 @@ my %config_path_settings = ( # Handle Uncouth Termination sub signal_handler { - # Make text normal - print color("reset"), "\n"; + require Term::ANSIColor; + print Term::ANSIColor::color("reset"), "\n"; # SMTP password masked system "stty echo"; @@ -349,11 +331,17 @@ $SIG{INT} = \&signal_handler; # Read our sendemail.* config sub read_config { - my ($configured, $prefix) = @_; + my ($known_keys, $configured, $prefix) = @_; foreach my $setting (keys %config_bool_settings) { my $target = $config_bool_settings{$setting}; - my $v = Git::config_bool(@repo, "$prefix.$setting"); + my $key = "$prefix.$setting"; + next unless exists $known_keys->{$key}; + my $v = (@{$known_keys->{$key}} == 1 && + (defined $known_keys->{$key}->[0] && + $known_keys->{$key}->[0] =~ /^(?:true|false)$/s)) + ? $known_keys->{$key}->[0] eq 'true' + : Git::config_bool(@repo, $key); next unless defined $v; next if $configured->{$setting}++; $$target = $v; @@ -361,8 +349,10 @@ sub read_config { foreach my $setting (keys %config_path_settings) { my $target = $config_path_settings{$setting}; + my $key = "$prefix.$setting"; + next unless exists $known_keys->{$key}; if (ref($target) eq "ARRAY") { - my @values = Git::config_path(@repo, "$prefix.$setting"); + my @values = Git::config_path(@repo, $key); next unless @values; next if $configured->{$setting}++; @$target = @values; @@ -377,36 +367,64 @@ sub read_config { foreach my $setting (keys %config_settings) { my $target = $config_settings{$setting}; + my $key = "$prefix.$setting"; + next unless exists $known_keys->{$key}; if (ref($target) eq "ARRAY") { - my @values = Git::config(@repo, "$prefix.$setting"); - next unless @values; + my @values = @{$known_keys->{$key}}; + @values = grep { defined } @values; next if $configured->{$setting}++; @$target = @values; } else { - my $v = Git::config(@repo, "$prefix.$setting"); + my $v = $known_keys->{$key}->[0]; next unless defined $v; next if $configured->{$setting}++; $$target = $v; } } +} - if (!defined $smtp_encryption) { - my $setting = "$prefix.smtpencryption"; - my $enc = Git::config(@repo, $setting); - return unless defined $enc; - return if $configured->{$setting}++; - if (defined $enc) { - $smtp_encryption = $enc; - } elsif (Git::config_bool(@repo, "$prefix.smtpssl")) { - $smtp_encryption = 'ssl'; - } +sub config_regexp { + my ($regex) = @_; + my @ret; + eval { + my $ret = Git::command( + 'config', + '--null', + '--get-regexp', + $regex, + ); + @ret = map { + # We must always return ($k, $v) here, since + # empty config values will be just "key\0", + # not "key\nvalue\0". + my ($k, $v) = split /\n/, $_, 2; + ($k, $v); + } split /\0/, $ret; + 1; + } or do { + # If we have no keys we're OK, otherwise re-throw + die $@ if $@->value != 1; + }; + return @ret; +} + +# Save ourselves a lot of work of shelling out to 'git config' (it +# parses 'bool' etc.) by only doing so for config keys that exist. +my %known_config_keys; +{ + my @kv = config_regexp("^sende?mail[.]"); + while (my ($k, $v) = splice @kv, 0, 2) { + push @{$known_config_keys{$k}} => $v; } } # sendemail.identity yields to --identity. We must parse this # special-case first before the rest of the config is read. -$identity = Git::config(@repo, "sendemail.identity"); +{ + my $key = "sendemail.identity"; + $identity = Git::config(@repo, $key) if exists $known_config_keys{$key}; +} my $rc = GetOptions( "identity=s" => \$identity, "no-identity" => \$no_identity, @@ -417,8 +435,8 @@ undef $identity if $no_identity; # Now we know enough to read the config { my %configured; - read_config(\%configured, "sendemail.$identity") if defined $identity; - read_config(\%configured, "sendemail"); + read_config(\%known_config_keys, \%configured, "sendemail.$identity") if defined $identity; + read_config(\%known_config_keys, \%configured, "sendemail"); } # Begin by accumulating all the variables (defined above), that we will end up @@ -503,7 +521,7 @@ unless ($rc) { usage(); } -if ($forbid_sendmail_variables && (scalar Git::config_regexp("^sendmail[.]")) != 0) { +if ($forbid_sendmail_variables && grep { /^sendmail/s } keys %known_config_keys) { die __("fatal: found configuration options for 'sendmail'\n" . "git-send-email is configured with the sendemail.* options - note the 'e'.\n" . "Set sendemail.forbidSendmailVariables to false to disable this check.\n"); @@ -568,15 +586,27 @@ if (0) { } my ($repoauthor, $repocommitter); -($repoauthor) = Git::ident_person(@repo, 'author'); -($repocommitter) = Git::ident_person(@repo, 'committer'); +{ + my %cache; + my ($author, $committer); + my $common = sub { + my ($what) = @_; + return $cache{$what} if exists $cache{$what}; + ($cache{$what}) = Git::ident_person(@repo, $what); + return $cache{$what}; + }; + $repoauthor = sub { $common->('author') }; + $repocommitter = sub { $common->('committer') }; +} sub parse_address_line { + require Git::LoadCPAN::Mail::Address; return map { $_->format } Mail::Address->parse($_[0]); } sub split_addrs { - return quotewords('\s*,\s*', 1, @_); + require Text::ParseWords; + return Text::ParseWords::quotewords('\s*,\s*', 1, @_); } my %aliases; @@ -625,10 +655,11 @@ my %parse_alias = ( s/\\"/"/g foreach @addr; $aliases{$alias} = \@addr }}}, - mailrc => sub { my $fh = shift; while (<$fh>) { + mailrc => sub { my $fh = shift; while (<$fh>) { if (/^alias\s+(\S+)\s+(.*?)\s*$/) { + require Text::ParseWords; # spaces delimit multiple addresses - $aliases{$1} = [ quotewords('\s+', 0, $2) ]; + $aliases{$1} = [ Text::ParseWords::quotewords('\s+', 0, $2) ]; }}}, pine => sub { my $fh = shift; my $f='\t[^\t]*'; for (my $x = ''; defined($x); $x = $_) { @@ -676,7 +707,7 @@ sub is_format_patch_arg { if (defined($format_patch)) { return $format_patch; } - die sprintf(__ <<EOF, $f, $f); + die sprintf(__(<<EOF), $f, $f); File '%s' exists but it could also be the range of commits to produce patches for. Please disambiguate by... @@ -700,7 +731,8 @@ while (defined(my $f = shift @ARGV)) { opendir my $dh, $f or die sprintf(__("Failed to opendir %s: %s"), $f, $!); - push @files, grep { -f $_ } map { catfile($f, $_) } + require File::Spec; + push @files, grep { -f $_ } map { File::Spec->catfile($f, $_) } sort readdir $dh; closedir $dh; } elsif ((-f $f or -p $f) and !is_format_patch_arg($f)) { @@ -713,7 +745,8 @@ while (defined(my $f = shift @ARGV)) { if (@rev_list_opts) { die __("Cannot run git format-patch from outside a repository\n") unless $repo; - push @files, $repo->command('format-patch', '-o', tempdir(CLEANUP => 1), @rev_list_opts); + require File::Temp; + push @files, $repo->command('format-patch', '-o', File::Temp::tempdir(CLEANUP => 1), @rev_list_opts); } @files = handle_backup_files(@files); @@ -750,19 +783,20 @@ sub get_patch_subject { if ($compose) { # Note that this does not need to be secure, but we will make a small # effort to have it be unique + require File::Temp; $compose_filename = ($repo ? - tempfile(".gitsendemail.msg.XXXXXX", DIR => $repo->repo_path()) : - tempfile(".gitsendemail.msg.XXXXXX", DIR => "."))[1]; + File::Temp::tempfile(".gitsendemail.msg.XXXXXX", DIR => $repo->repo_path()) : + File::Temp::tempfile(".gitsendemail.msg.XXXXXX", DIR => "."))[1]; open my $c, ">", $compose_filename or die sprintf(__("Failed to open for writing %s: %s"), $compose_filename, $!); - my $tpl_sender = $sender || $repoauthor || $repocommitter || ''; + my $tpl_sender = $sender || $repoauthor->() || $repocommitter->() || ''; my $tpl_subject = $initial_subject || ''; my $tpl_in_reply_to = $initial_in_reply_to || ''; my $tpl_reply_to = $reply_to || ''; - print $c <<EOT1, Git::prefix_lines("GIT: ", __ <<EOT2), <<EOT3; + print $c <<EOT1, Git::prefix_lines("GIT: ", __(<<EOT2)), <<EOT3; From $tpl_sender # This line is ignored. EOT1 Lines beginning in "GIT:" will be removed. @@ -859,6 +893,19 @@ EOT3 do_edit(@files); } +sub term { + my $term = eval { + require Term::ReadLine; + $ENV{"GIT_SEND_EMAIL_NOTTY"} + ? Term::ReadLine->new('git-send-email', \*STDIN, \*STDOUT) + : Term::ReadLine->new('git-send-email'); + }; + if ($@) { + $term = FakeTerm->new("$@: going non-interactive"); + } + return $term; +} + sub ask { my ($prompt, %arg) = @_; my $valid_re = $arg{valid_re}; @@ -866,6 +913,7 @@ sub ask { my $confirm_only = $arg{confirm_only}; my $resp; my $i = 0; + my $term = term(); return defined $default ? $default : undef unless defined $term->IN and defined fileno($term->IN) and defined $term->OUT and defined fileno($term->OUT); @@ -963,7 +1011,7 @@ if (defined $sender) { $sender =~ s/^\s+|\s+$//g; ($sender) = expand_aliases($sender); } else { - $sender = $repoauthor || $repocommitter || ''; + $sender = $repoauthor->() || $repocommitter->() || ''; } # $sender could be an already sanitized address @@ -1049,6 +1097,7 @@ sub extract_valid_address { return $address if ($address =~ /^($local_part_regexp)$/); $address =~ s/^\s*<(.*)>\s*$/$1/; + my $have_email_valid = eval { require Email::Valid; 1 }; if ($have_email_valid) { return scalar Email::Valid->address($address); } @@ -1108,14 +1157,15 @@ my ($message_id_stamp, $message_id_serial); sub make_message_id { my $uniq; if (!defined $message_id_stamp) { - $message_id_stamp = strftime("%Y%m%d%H%M%S.$$", gmtime(time)); + require POSIX; + $message_id_stamp = POSIX::strftime("%Y%m%d%H%M%S.$$", gmtime(time)); $message_id_serial = 0; } $message_id_serial++; $uniq = "$message_id_stamp-$message_id_serial"; my $du_part; - for ($sender, $repocommitter, $repoauthor) { + for ($sender, $repocommitter->(), $repoauthor->()) { $du_part = extract_valid_address(sanitize_address($_)); last if (defined $du_part and $du_part ne ''); } @@ -1278,6 +1328,7 @@ sub valid_fqdn { sub maildomain_net { my $maildomain; + require Net::Domain; my $domain = Net::Domain::domainname(); $maildomain = $domain if valid_fqdn($domain); @@ -1288,6 +1339,7 @@ sub maildomain_mta { my $maildomain; for my $host (qw(mailhost localhost)) { + require Net::SMTP; my $smtp = Net::SMTP->new($host); if (defined $smtp) { my $domain = $smtp->domain; @@ -1980,13 +2032,15 @@ sub validate_patch { if ($repo) { my $hooks_path = $repo->command_oneline('rev-parse', '--git-path', 'hooks'); - my $validate_hook = catfile($hooks_path, + require File::Spec; + my $validate_hook = File::Spec->catfile($hooks_path, 'sendemail-validate'); my $hook_error; if (-x $validate_hook) { - my $target = abs_path($fn); + require Cwd; + my $target = Cwd::abs_path($fn); # The hook needs a correct cwd and GIT_DIR. - my $cwd_save = cwd(); + my $cwd_save = Cwd::getcwd(); chdir($repo->wc_path() or $repo->repo_path()) or die("chdir: $!"); local $ENV{"GIT_DIR"} = $repo->repo_path(); diff --git a/git-submodule.sh b/git-submodule.sh index 4678378424..cb06aa02c8 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -335,7 +335,7 @@ cmd_foreach() shift done - git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper foreach ${GIT_QUIET:+--quiet} ${recursive:+--recursive} -- "$@" + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper foreach ${GIT_QUIET:+--quiet} ${recursive:+--recursive} -- "$@" } # @@ -402,7 +402,7 @@ cmd_deinit() shift done - git ${wt_prefix:+-C "$wt_prefix"} submodule--helper deinit ${GIT_QUIET:+--quiet} ${prefix:+--prefix "$prefix"} ${force:+--force} ${deinit_all:+--all} -- "$@" + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper deinit ${GIT_QUIET:+--quiet} ${force:+--force} ${deinit_all:+--all} -- "$@" } is_tip_reachable () ( @@ -726,7 +726,7 @@ cmd_set_branch() { shift done - git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper set-branch ${GIT_QUIET:+--quiet} ${branch:+--branch "$branch"} ${default:+--default} -- "$@" + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper set-branch ${GIT_QUIET:+--quiet} ${branch:+--branch "$branch"} ${default:+--default} -- "$@" } # @@ -755,7 +755,7 @@ cmd_set_url() { shift done - git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper set-url ${GIT_QUIET:+--quiet} -- "$@" + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper set-url ${GIT_QUIET:+--quiet} -- "$@" } # @@ -807,7 +807,7 @@ cmd_summary() { shift done - git ${wt_prefix:+-C "$wt_prefix"} submodule--helper summary ${prefix:+--prefix "$prefix"} ${files:+--files} ${cached:+--cached} ${for_status:+--for-status} ${summary_limit:+-n $summary_limit} -- "$@" + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper summary ${files:+--files} ${cached:+--cached} ${for_status:+--for-status} ${summary_limit:+-n $summary_limit} -- "$@" } # # List all submodules, prefixed with: @@ -848,7 +848,7 @@ cmd_status() shift done - git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper status ${GIT_QUIET:+--quiet} ${cached:+--cached} ${recursive:+--recursive} -- "$@" + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper status ${GIT_QUIET:+--quiet} ${cached:+--cached} ${recursive:+--recursive} -- "$@" } # # Sync remote urls for submodules @@ -881,7 +881,7 @@ cmd_sync() esac done - git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper sync ${GIT_QUIET:+--quiet} ${recursive:+--recursive} -- "$@" + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper sync ${GIT_QUIET:+--quiet} ${recursive:+--recursive} -- "$@" } cmd_absorbgitdirs() @@ -657,6 +657,8 @@ static struct grep_expr *compile_pattern_and(struct grep_pat **list) x = compile_pattern_not(list); p = *list; if (p && p->token == GREP_AND) { + if (!x) + die("--and not preceded by pattern expression"); if (!p->next) die("--and not followed by pattern expression"); *list = p->next; @@ -265,7 +265,7 @@ static inline void oidcpy(struct object_id *dst, const struct object_id *src) /* Like oidcpy() but zero-pads the unused bytes in dst's hash array. */ static inline void oidcpy_with_padding(struct object_id *dst, - struct object_id *src) + const struct object_id *src) { size_t hashsz; diff --git a/imap-send.c b/imap-send.c index bb085d66d1..a0540ba5cf 100644 --- a/imap-send.c +++ b/imap-send.c @@ -451,6 +451,7 @@ static int buffer_gets(struct imap_buffer *b, char **s) /* not reached */ } +__attribute__((format (printf, 1, 2))) static void imap_info(const char *msg, ...) { va_list va; @@ -463,6 +464,7 @@ static void imap_info(const char *msg, ...) } } +__attribute__((format (printf, 1, 2))) static void imap_warn(const char *msg, ...) { va_list va; @@ -504,6 +506,7 @@ static char *next_arg(char **s) return ret; } +__attribute__((format (printf, 3, 4))) static int nfsnprintf(char *buf, int blen, const char *fmt, ...) { int ret; @@ -1266,18 +1269,6 @@ static void wrap_in_html(struct strbuf *msg) *msg = buf; } -#define CHUNKSIZE 0x1000 - -static int read_message(FILE *f, struct strbuf *all_msgs) -{ - do { - if (strbuf_fread(all_msgs, CHUNKSIZE, f) <= 0) - break; - } while (!feof(f)); - - return ferror(f) ? -1 : 0; -} - static int count_messages(struct strbuf *all_msgs) { int count = 0; @@ -1582,8 +1573,8 @@ int cmd_main(int argc, const char **argv) } /* read the messages */ - if (read_message(stdin, &all_msgs)) { - fprintf(stderr, "error reading input\n"); + if (strbuf_read(&all_msgs, 0, 0) < 0) { + error_errno(_("could not read from stdin")); return 1; } diff --git a/json-writer.c b/json-writer.c index aadb9dbddc..f1cfd8fa8c 100644 --- a/json-writer.c +++ b/json-writer.c @@ -3,10 +3,8 @@ void jw_init(struct json_writer *jw) { - strbuf_init(&jw->json, 0); - strbuf_init(&jw->open_stack, 0); - jw->need_comma = 0; - jw->pretty = 0; + struct json_writer blank = JSON_WRITER_INIT; + memcpy(jw, &blank, sizeof(*jw));; } void jw_release(struct json_writer *jw) diff --git a/json-writer.h b/json-writer.h index 83906b09c1..209355e0f1 100644 --- a/json-writer.h +++ b/json-writer.h @@ -64,7 +64,10 @@ struct json_writer unsigned int pretty:1; }; -#define JSON_WRITER_INIT { STRBUF_INIT, STRBUF_INIT, 0, 0 } +#define JSON_WRITER_INIT { \ + .json = STRBUF_INIT, \ + .open_stack = STRBUF_INIT, \ +} void jw_init(struct json_writer *jw); void jw_release(struct json_writer *jw); @@ -74,7 +74,7 @@ static const double __ac_HASH_UPPER = 0.77; void kh_destroy_##name(kh_##name##_t *h); \ void kh_clear_##name(kh_##name##_t *h); \ khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \ - int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ + void kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ void kh_del_##name(kh_##name##_t *h, khint_t x); @@ -116,7 +116,7 @@ static const double __ac_HASH_UPPER = 0.77; return __ac_iseither(h->flags, i)? h->n_buckets : i; \ } else return 0; \ } \ - SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ + SCOPE void kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ khint32_t *new_flags = NULL; \ khint_t j = 1; \ @@ -126,7 +126,6 @@ static const double __ac_HASH_UPPER = 0.77; if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \ else { /* hash table size to be changed (shrink or expand); rehash */ \ ALLOC_ARRAY(new_flags, __ac_fsize(new_n_buckets)); \ - if (!new_flags) return -1; \ memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ if (h->n_buckets < new_n_buckets) { /* expand */ \ REALLOC_ARRAY(h->keys, new_n_buckets); \ @@ -173,18 +172,15 @@ static const double __ac_HASH_UPPER = 0.77; h->n_occupied = h->size; \ h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ } \ - return 0; \ } \ SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ { \ khint_t x; \ if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ if (h->n_buckets > (h->size<<1)) { \ - if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \ - *ret = -1; return h->n_buckets; \ - } \ - } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \ - *ret = -1; return h->n_buckets; \ + kh_resize_##name(h, h->n_buckets - 1); /* clear "deleted" elements */ \ + } else { \ + kh_resize_##name(h, h->n_buckets + 1); /* expand the hash table */ \ } \ } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ { \ diff --git a/ll-merge.c b/ll-merge.c index 9a8a2c365c..261657578c 100644 --- a/ll-merge.c +++ b/ll-merge.c @@ -91,7 +91,9 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused, * With -Xtheirs or -Xours, we have cleanly merged; * otherwise we got a conflict. */ - return (opts->variant ? 0 : 1); + return opts->variant == XDL_MERGE_FAVOR_OURS || + opts->variant == XDL_MERGE_FAVOR_THEIRS ? + 0 : 1; } static int ll_xdl_merge(const struct ll_merge_driver *drv_unused, @@ -136,7 +138,7 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused, static int ll_union_merge(const struct ll_merge_driver *drv_unused, mmbuffer_t *result, - const char *path_unused, + const char *path, mmfile_t *orig, const char *orig_name, mmfile_t *src1, const char *name1, mmfile_t *src2, const char *name2, @@ -148,8 +150,8 @@ static int ll_union_merge(const struct ll_merge_driver *drv_unused, assert(opts); o = *opts; o.variant = XDL_MERGE_FAVOR_UNION; - return ll_xdl_merge(drv_unused, result, path_unused, - orig, NULL, src1, NULL, src2, NULL, + return ll_xdl_merge(drv_unused, result, path, + orig, orig_name, src1, name1, src2, name2, &o, marker_size); } diff --git a/log-tree.c b/log-tree.c index 7b823786c2..6dc4412268 100644 --- a/log-tree.c +++ b/log-tree.c @@ -134,7 +134,8 @@ static int add_ref_decoration(const char *refname, const struct object_id *oid, int flags, void *cb_data) { struct object *obj; - enum decoration_type type = DECORATION_NONE; + enum object_type objtype; + enum decoration_type deco_type = DECORATION_NONE; struct decoration_filter *filter = (struct decoration_filter *)cb_data; if (filter && !ref_filter_match(refname, filter)) @@ -155,28 +156,29 @@ static int add_ref_decoration(const char *refname, const struct object_id *oid, return 0; } - obj = parse_object(the_repository, oid); - if (!obj) + objtype = oid_object_info(the_repository, oid, NULL); + if (objtype < 0) return 0; + obj = lookup_object_by_type(the_repository, oid, objtype); if (starts_with(refname, "refs/heads/")) - type = DECORATION_REF_LOCAL; + deco_type = DECORATION_REF_LOCAL; else if (starts_with(refname, "refs/remotes/")) - type = DECORATION_REF_REMOTE; + deco_type = DECORATION_REF_REMOTE; else if (starts_with(refname, "refs/tags/")) - type = DECORATION_REF_TAG; + deco_type = DECORATION_REF_TAG; else if (!strcmp(refname, "refs/stash")) - type = DECORATION_REF_STASH; + deco_type = DECORATION_REF_STASH; else if (!strcmp(refname, "HEAD")) - type = DECORATION_REF_HEAD; + deco_type = DECORATION_REF_HEAD; - add_name_decoration(type, refname, obj); + add_name_decoration(deco_type, refname, obj); while (obj->type == OBJ_TAG) { + if (!obj->parsed) + parse_object(the_repository, &obj->oid); obj = ((struct tag *)obj)->tagged; if (!obj) break; - if (!obj->parsed) - parse_object(the_repository, &obj->oid); add_name_decoration(DECORATION_REF_TAG, refname, obj); } return 0; @@ -8,6 +8,7 @@ #define debug_mm(...) fprintf(stderr, __VA_ARGS__) #define debug_str(X) ((X) ? (X) : "(none)") #else +__attribute__((format (printf, 1, 2))) static inline void debug_mm(const char *format, ...) {} static inline const char *debug_str(const char *s) { return s; } #endif diff --git a/merge-ort.c b/merge-ort.c index 8a84375e94..ec0c590421 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -29,6 +29,7 @@ #include "entry.h" #include "ll-merge.h" #include "object-store.h" +#include "promisor-remote.h" #include "revision.h" #include "strmap.h" #include "submodule.h" @@ -529,6 +530,7 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti, renames->callback_data_nr = renames->callback_data_alloc = 0; } +__attribute__((format (printf, 2, 3))) static int err(struct merge_options *opt, const char *err, ...) { va_list params; @@ -765,6 +767,7 @@ static void add_pair(struct merge_options *opt, int names_idx = is_add ? side : 0; if (is_add) { + assert(match_mask == 0 || match_mask == 6); if (strset_contains(&renames->cached_target_names[side], pathname)) return; @@ -772,6 +775,8 @@ static void add_pair(struct merge_options *opt, unsigned content_relevant = (match_mask == 0); unsigned location_relevant = (dir_rename_mask == 0x07); + assert(match_mask == 0 || match_mask == 3 || match_mask == 5); + /* * If pathname is found in cached_irrelevant[side] due to * previous pick but for this commit content is relevant, @@ -1836,7 +1841,7 @@ static void compute_collisions(struct strmap *collisions, free(new_path); } else { CALLOC_ARRAY(collision_info, 1); - string_list_init(&collision_info->source_files, 0); + string_list_init_nodup(&collision_info->source_files); strmap_put(collisions, new_path, collision_info); } string_list_insert(&collision_info->source_files, @@ -2533,7 +2538,7 @@ static int compare_pairs(const void *a_, const void *b_) return strcmp(a->one->path, b->one->path); } -/* Call diffcore_rename() to compute which files have changed on given side */ +/* Call diffcore_rename() to update deleted/added pairs into rename pairs */ static void detect_regular_renames(struct merge_options *opt, unsigned side_index) { @@ -2586,8 +2591,10 @@ static void detect_regular_renames(struct merge_options *opt, } /* - * Get information of all renames which occurred in 'side_pairs', discarding - * non-renames. + * Get information of all renames which occurred in 'side_pairs', making use + * of any implicit directory renames in side_dir_renames (also making use of + * implicit directory renames rename_exclusions as needed by + * check_for_directory_rename()). Add all (updated) renames into result. */ static int collect_renames(struct merge_options *opt, struct diff_queue_struct *result, @@ -2746,31 +2753,58 @@ simple_cleanup: /*** Function Grouping: functions related to process_entries() ***/ -static int string_list_df_name_compare(const char *one, const char *two) +static int sort_dirs_next_to_their_children(const char *one, const char *two) { - int onelen = strlen(one); - int twolen = strlen(two); + unsigned char c1, c2; + /* - * Here we only care that entries for D/F conflicts are - * adjacent, in particular with the file of the D/F conflict - * appearing before files below the corresponding directory. - * The order of the rest of the list is irrelevant for us. + * Here we only care that entries for directories appear adjacent + * to and before files underneath the directory. We can achieve + * that by pretending to add a trailing slash to every file and + * then sorting. In other words, we do not want the natural + * sorting of + * foo + * foo.txt + * foo/bar + * Instead, we want "foo" to sort as though it were "foo/", so that + * we instead get + * foo.txt + * foo + * foo/bar + * To achieve this, we basically implement our own strcmp, except that + * if we get to the end of either string instead of comparing NUL to + * another character, we compare '/' to it. * - * To achieve this, we sort with df_name_compare and provide - * the mode S_IFDIR so that D/F conflicts will sort correctly. - * We use the mode S_IFDIR for everything else for simplicity, - * since in other cases any changes in their order due to - * sorting cause no problems for us. + * If this unusual "sort as though '/' were appended" perplexes + * you, perhaps it will help to note that this is not the final + * sort. write_tree() will sort again without the trailing slash + * magic, but just on paths immediately under a given tree. + * + * The reason to not use df_name_compare directly was that it was + * just too expensive (we don't have the string lengths handy), so + * it was reimplemented. */ - int cmp = df_name_compare(one, onelen, S_IFDIR, - two, twolen, S_IFDIR); + /* - * Now that 'foo' and 'foo/bar' compare equal, we have to make sure - * that 'foo' comes before 'foo/bar'. + * NOTE: This function will never be called with two equal strings, + * because it is used to sort the keys of a strmap, and strmaps have + * unique keys by construction. That simplifies our c1==c2 handling + * below. */ - if (cmp) - return cmp; - return onelen - twolen; + + while (*one && (*one == *two)) { + one++; + two++; + } + + c1 = *one ? *one : '/'; + c2 = *two ? *two : '/'; + + if (c1 == c2) { + /* Getting here means one is a leading directory of the other */ + return (*one) ? 1 : -1; + } else + return c1 - c2; } static int read_oid_strbuf(struct merge_options *opt, @@ -3237,7 +3271,7 @@ static void process_entry(struct merge_options *opt, * above. */ if (ci->match_mask) { - ci->merged.clean = 1; + ci->merged.clean = !ci->df_conflict && !ci->path_conflict; if (ci->match_mask == 6) { /* stages[1] == stages[2] */ ci->merged.result.mode = ci->stages[1].mode; @@ -3249,6 +3283,8 @@ static void process_entry(struct merge_options *opt, ci->merged.result.mode = ci->stages[side].mode; ci->merged.is_null = !ci->merged.result.mode; + if (ci->merged.is_null) + ci->merged.clean = 1; oidcpy(&ci->merged.result.oid, &ci->stages[side].oid); assert(othermask == 2 || othermask == 4); @@ -3421,6 +3457,7 @@ static void process_entry(struct merge_options *opt, path)) { ci->merged.is_null = 1; ci->merged.clean = 1; + assert(!ci->df_conflict && !ci->path_conflict); } else if (ci->path_conflict && oideq(&ci->stages[0].oid, &ci->stages[side].oid)) { /* @@ -3447,6 +3484,7 @@ static void process_entry(struct merge_options *opt, ci->merged.is_null = 1; ci->merged.result.mode = 0; oidcpy(&ci->merged.result.oid, null_oid()); + assert(!ci->df_conflict); ci->merged.clean = !ci->path_conflict; } @@ -3457,9 +3495,59 @@ static void process_entry(struct merge_options *opt, */ if (!ci->merged.clean) strmap_put(&opt->priv->conflicted, path, ci); + + /* Record metadata for ci->merged in dir_metadata */ record_entry_for_tree(dir_metadata, path, &ci->merged); } +static void prefetch_for_content_merges(struct merge_options *opt, + struct string_list *plist) +{ + struct string_list_item *e; + struct oid_array to_fetch = OID_ARRAY_INIT; + + if (opt->repo != the_repository || !has_promisor_remote()) + return; + + for (e = &plist->items[plist->nr-1]; e >= plist->items; --e) { + /* char *path = e->string; */ + struct conflict_info *ci = e->util; + int i; + + /* Ignore clean entries */ + if (ci->merged.clean) + continue; + + /* Ignore entries that don't need a content merge */ + if (ci->match_mask || ci->filemask < 6 || + !S_ISREG(ci->stages[1].mode) || + !S_ISREG(ci->stages[2].mode) || + oideq(&ci->stages[1].oid, &ci->stages[2].oid)) + continue; + + /* Also don't need content merge if base matches either side */ + if (ci->filemask == 7 && + S_ISREG(ci->stages[0].mode) && + (oideq(&ci->stages[0].oid, &ci->stages[1].oid) || + oideq(&ci->stages[0].oid, &ci->stages[2].oid))) + continue; + + for (i = 0; i < 3; i++) { + unsigned side_mask = (1 << i); + struct version_info *vi = &ci->stages[i]; + + if ((ci->filemask & side_mask) && + S_ISREG(vi->mode) && + oid_object_info_extended(opt->repo, &vi->oid, NULL, + OBJECT_INFO_FOR_PREFETCH)) + oid_array_append(&to_fetch, &vi->oid); + } + } + + promisor_remote_get_direct(opt->repo, to_fetch.oid, to_fetch.nr); + oid_array_clear(&to_fetch); +} + static void process_entries(struct merge_options *opt, struct object_id *result_oid) { @@ -3490,7 +3578,7 @@ static void process_entries(struct merge_options *opt, trace2_region_leave("merge", "plist copy", opt->repo); trace2_region_enter("merge", "plist special sort", opt->repo); - plist.cmp = string_list_df_name_compare; + plist.cmp = sort_dirs_next_to_their_children; string_list_sort(&plist); trace2_region_leave("merge", "plist special sort", opt->repo); @@ -3506,6 +3594,7 @@ static void process_entries(struct merge_options *opt, * the way when it is time to process the file at the same path). */ trace2_region_enter("merge", "processing", opt->repo); + prefetch_for_content_merges(opt, &plist); for (entry = &plist.items[plist.nr-1]; entry >= plist.items; --entry) { char *path = entry->string; /* @@ -3942,7 +4031,7 @@ static void merge_start(struct merge_options *opt, struct merge_result *result) */ strmap_init_with_options(&opt->priv->paths, NULL, 0); strmap_init_with_options(&opt->priv->conflicted, NULL, 0); - string_list_init(&opt->priv->paths_to_free, 0); + string_list_init_nodup(&opt->priv->paths_to_free); /* * keys & strbufs in output will sometimes need to outlive "paths", diff --git a/merge-recursive.c b/merge-recursive.c index f19f8cc37b..7008a90df5 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -121,7 +121,7 @@ static void dir_rename_entry_init(struct dir_rename_entry *entry, entry->dir = directory; entry->non_unique_new_dir = 0; strbuf_init(&entry->new_dir, 0); - string_list_init(&entry->possible_new_dirs, 0); + string_list_init_nodup(&entry->possible_new_dirs); } struct collision_entry { @@ -167,6 +167,7 @@ static void flush_output(struct merge_options *opt) } } +__attribute__((format (printf, 2, 3))) static int err(struct merge_options *opt, const char *err, ...) { va_list params; @@ -2804,12 +2805,19 @@ static int process_renames(struct merge_options *opt, int renamed_stage = a_renames == renames1 ? 2 : 3; int other_stage = a_renames == renames1 ? 3 : 2; + /* + * Directory renames have a funny corner case... + */ + int renamed_to_self = !strcmp(ren1_src, ren1_dst); + /* BUG: We should only remove ren1_src in the base * stage and in other_stage (think of rename + * add-source case). */ - remove_file(opt, 1, ren1_src, - renamed_stage == 2 || !was_tracked(opt, ren1_src)); + if (!renamed_to_self) + remove_file(opt, 1, ren1_src, + renamed_stage == 2 || + !was_tracked(opt, ren1_src)); oidcpy(&src_other.oid, &ren1->src_entry->stages[other_stage].oid); @@ -2823,6 +2831,9 @@ static int process_renames(struct merge_options *opt, ren1->dir_rename_original_type == 'A') { setup_rename_conflict_info(RENAME_VIA_DIR, opt, ren1, NULL); + } else if (renamed_to_self) { + setup_rename_conflict_info(RENAME_NORMAL, + opt, ren1, NULL); } else if (oideq(&src_other.oid, null_oid())) { setup_rename_conflict_info(RENAME_DELETE, opt, ren1, NULL); @@ -3180,7 +3191,6 @@ static int handle_rename_normal(struct merge_options *opt, struct rename *ren = ci->ren1; struct merge_file_info mfi; int clean; - int side = (ren->branch == opt->branch1 ? 2 : 3); /* Merge the content and write it out */ clean = handle_content_merge(&mfi, opt, path, was_dirty(opt, path), @@ -3190,9 +3200,7 @@ static int handle_rename_normal(struct merge_options *opt, opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_CONFLICT && ren->dir_rename_original_dest) { if (update_stages(opt, path, - NULL, - side == 2 ? &mfi.blob : NULL, - side == 2 ? NULL : &mfi.blob)) + &mfi.blob, &mfi.blob, &mfi.blob)) return -1; clean = 0; /* not clean, but conflicted */ } @@ -3703,7 +3711,7 @@ static int merge_start(struct merge_options *opt, struct tree *head) } CALLOC_ARRAY(opt->priv, 1); - string_list_init(&opt->priv->df_conflict_file_set, 1); + string_list_init_dup(&opt->priv->df_conflict_file_set); return 0; } @@ -53,7 +53,7 @@ int checkout_fast_forward(struct repository *r, struct unpack_trees_options opts; struct tree_desc t[MAX_UNPACK_TREES]; int i, nr_trees = 0; - struct dir_struct dir; + struct dir_struct dir = DIR_INIT; struct lock_file lock_file = LOCK_INIT; refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL); @@ -80,7 +80,6 @@ int checkout_fast_forward(struct repository *r, } memset(&opts, 0, sizeof(opts)); - dir_init(&dir); if (overwrite_ignore) { dir.flags |= DIR_SHOW_IGNORED; setup_standard_excludes(&dir); @@ -885,6 +885,11 @@ static void write_midx_reverse_index(char *midx_name, unsigned char *midx_hash, static void clear_midx_files_ext(struct repository *r, const char *ext, unsigned char *keep_hash); +static int midx_checksum_valid(struct multi_pack_index *m) +{ + return hashfile_checksum_valid(m->data, m->data_len); +} + static int write_midx_internal(const char *object_dir, struct multi_pack_index *m, struct string_list *packs_to_drop, const char *preferred_pack_name, @@ -911,6 +916,11 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index * else ctx.m = load_multi_pack_index(object_dir, 1); + if (ctx.m && !midx_checksum_valid(ctx.m)) { + warning(_("ignoring existing multi-pack-index; checksum mismatch")); + ctx.m = NULL; + } + ctx.nr = 0; ctx.alloc = ctx.m ? ctx.m->num_packs : 16; ctx.info = NULL; @@ -1162,6 +1172,7 @@ void clear_midx_file(struct repository *r) static int verify_midx_error; +__attribute__((format (printf, 1, 2))) static void midx_report(const char *fmt, ...) { va_list ap; @@ -1218,6 +1229,9 @@ int verify_midx_file(struct repository *r, const char *object_dir, unsigned flag return result; } + if (!midx_checksum_valid(m)) + midx_report(_("incorrect checksum")); + if (flags & MIDX_PROGRESS) progress = start_delayed_progress(_("Looking for referenced packfiles"), m->num_packs); diff --git a/object-file.c b/object-file.c index f233b440b2..3d27dc8dea 100644 --- a/object-file.c +++ b/object-file.c @@ -517,9 +517,9 @@ const char *loose_object_path(struct repository *r, struct strbuf *buf, */ static int alt_odb_usable(struct raw_object_store *o, struct strbuf *path, - const char *normalized_objdir) + const char *normalized_objdir, khiter_t *pos) { - struct object_directory *odb; + int r; /* Detect cases where alternate disappeared */ if (!is_directory(path->buf)) { @@ -533,14 +533,20 @@ static int alt_odb_usable(struct raw_object_store *o, * Prevent the common mistake of listing the same * thing twice, or object directory itself. */ - for (odb = o->odb; odb; odb = odb->next) { - if (!fspathcmp(path->buf, odb->path)) - return 0; + if (!o->odb_by_path) { + khiter_t p; + + o->odb_by_path = kh_init_odb_path_map(); + assert(!o->odb->next); + p = kh_put_odb_path_map(o->odb_by_path, o->odb->path, &r); + assert(r == 1); /* never used */ + kh_value(o->odb_by_path, p) = o->odb; } - if (!fspathcmp(path->buf, normalized_objdir)) + if (fspatheq(path->buf, normalized_objdir)) return 0; - - return 1; + *pos = kh_put_odb_path_map(o->odb_by_path, path->buf, &r); + /* r: 0 = exists, 1 = never used, 2 = deleted */ + return r == 0 ? 0 : 1; } /* @@ -561,17 +567,18 @@ static int alt_odb_usable(struct raw_object_store *o, static void read_info_alternates(struct repository *r, const char *relative_base, int depth); -static int link_alt_odb_entry(struct repository *r, const char *entry, +static int link_alt_odb_entry(struct repository *r, const struct strbuf *entry, const char *relative_base, int depth, const char *normalized_objdir) { struct object_directory *ent; struct strbuf pathbuf = STRBUF_INIT; + khiter_t pos; - if (!is_absolute_path(entry) && relative_base) { + if (!is_absolute_path(entry->buf) && relative_base) { strbuf_realpath(&pathbuf, relative_base, 1); strbuf_addch(&pathbuf, '/'); } - strbuf_addstr(&pathbuf, entry); + strbuf_addbuf(&pathbuf, entry); if (strbuf_normalize_path(&pathbuf) < 0 && relative_base) { error(_("unable to normalize alternate object path: %s"), @@ -587,23 +594,25 @@ static int link_alt_odb_entry(struct repository *r, const char *entry, while (pathbuf.len && pathbuf.buf[pathbuf.len - 1] == '/') strbuf_setlen(&pathbuf, pathbuf.len - 1); - if (!alt_odb_usable(r->objects, &pathbuf, normalized_objdir)) { + if (!alt_odb_usable(r->objects, &pathbuf, normalized_objdir, &pos)) { strbuf_release(&pathbuf); return -1; } CALLOC_ARRAY(ent, 1); - ent->path = xstrdup(pathbuf.buf); + /* pathbuf.buf is already in r->objects->odb_by_path */ + ent->path = strbuf_detach(&pathbuf, NULL); /* add the alternate entry */ *r->objects->odb_tail = ent; r->objects->odb_tail = &(ent->next); ent->next = NULL; + assert(r->objects->odb_by_path); + kh_value(r->objects->odb_by_path, pos) = ent; /* recursively add alternates */ - read_info_alternates(r, pathbuf.buf, depth + 1); + read_info_alternates(r, ent->path, depth + 1); - strbuf_release(&pathbuf); return 0; } @@ -660,7 +669,7 @@ static void link_alt_odb_entries(struct repository *r, const char *alt, alt = parse_alt_odb_entry(alt, sep, &entry); if (!entry.len) continue; - link_alt_odb_entry(r, entry.buf, + link_alt_odb_entry(r, &entry, relative_base, depth, objdirbuf.buf); } strbuf_release(&entry); @@ -1023,12 +1032,26 @@ void *xmmap_gently(void *start, size_t length, return ret; } +const char *mmap_os_err(void) +{ + static const char blank[] = ""; +#if defined(__linux__) + if (errno == ENOMEM) { + /* this continues an existing error message: */ + static const char enomem[] = +", check sys.vm.max_map_count and/or RLIMIT_DATA"; + return enomem; + } +#endif /* OS-specific bits */ + return blank; +} + void *xmmap(void *start, size_t length, int prot, int flags, int fd, off_t offset) { void *ret = xmmap_gently(start, length, prot, flags, fd, offset); if (ret == MAP_FAILED) - die_errno(_("mmap failed")); + die_errno(_("mmap failed%s"), mmap_os_err()); return ret; } @@ -1164,7 +1187,7 @@ static int quick_has_loose(struct repository *r, prepare_alt_odb(r); for (odb = r->objects->odb; odb; odb = odb->next) { - if (oid_array_lookup(odb_loose_cache(odb, oid), oid) >= 0) + if (oidtree_contains(odb_loose_cache(odb, oid), oid)) return 1; } return 0; @@ -1570,15 +1593,12 @@ static int do_oid_object_info_extended(struct repository *r, } /* Check if it is a missing object */ - if (fetch_if_missing && has_promisor_remote() && - !already_retried && r == the_repository && + if (fetch_if_missing && repo_has_promisor_remote(r) && + !already_retried && !(flags & OBJECT_INFO_SKIP_FETCH_OBJECT)) { /* * TODO Investigate checking promisor_remote_get_direct() * TODO return value and stopping on error here. - * TODO Pass a repository struct through - * promisor_remote_get_direct(), such that arbitrary - * repositories work. */ promisor_remote_get_direct(r, real, 1); already_retried = 1; @@ -2443,39 +2463,45 @@ int for_each_loose_object(each_loose_object_fn cb, void *data, static int append_loose_object(const struct object_id *oid, const char *path, void *data) { - oid_array_append(data, oid); + oidtree_insert(data, oid); return 0; } -struct oid_array *odb_loose_cache(struct object_directory *odb, +struct oidtree *odb_loose_cache(struct object_directory *odb, const struct object_id *oid) { int subdir_nr = oid->hash[0]; struct strbuf buf = STRBUF_INIT; + size_t word_bits = bitsizeof(odb->loose_objects_subdir_seen[0]); + size_t word_index = subdir_nr / word_bits; + size_t mask = 1 << (subdir_nr % word_bits); + uint32_t *bitmap; if (subdir_nr < 0 || - subdir_nr >= ARRAY_SIZE(odb->loose_objects_subdir_seen)) + subdir_nr >= bitsizeof(odb->loose_objects_subdir_seen)) BUG("subdir_nr out of range"); - if (odb->loose_objects_subdir_seen[subdir_nr]) - return &odb->loose_objects_cache[subdir_nr]; - + bitmap = &odb->loose_objects_subdir_seen[word_index]; + if (*bitmap & mask) + return odb->loose_objects_cache; + if (!odb->loose_objects_cache) { + ALLOC_ARRAY(odb->loose_objects_cache, 1); + oidtree_init(odb->loose_objects_cache); + } strbuf_addstr(&buf, odb->path); for_each_file_in_obj_subdir(subdir_nr, &buf, append_loose_object, NULL, NULL, - &odb->loose_objects_cache[subdir_nr]); - odb->loose_objects_subdir_seen[subdir_nr] = 1; + odb->loose_objects_cache); + *bitmap |= mask; strbuf_release(&buf); - return &odb->loose_objects_cache[subdir_nr]; + return odb->loose_objects_cache; } void odb_clear_loose_cache(struct object_directory *odb) { - int i; - - for (i = 0; i < ARRAY_SIZE(odb->loose_objects_cache); i++) - oid_array_clear(&odb->loose_objects_cache[i]); + oidtree_clear(odb->loose_objects_cache); + FREE_AND_NULL(odb->loose_objects_cache); memset(&odb->loose_objects_subdir_seen, 0, sizeof(odb->loose_objects_subdir_seen)); } diff --git a/object-name.c b/object-name.c index 64202de60b..3263c19457 100644 --- a/object-name.c +++ b/object-name.c @@ -87,27 +87,21 @@ static void update_candidates(struct disambiguate_state *ds, const struct object static int match_hash(unsigned, const unsigned char *, const unsigned char *); +static enum cb_next match_prefix(const struct object_id *oid, void *arg) +{ + struct disambiguate_state *ds = arg; + /* no need to call match_hash, oidtree_each did prefix match */ + update_candidates(ds, oid); + return ds->ambiguous ? CB_BREAK : CB_CONTINUE; +} + static void find_short_object_filename(struct disambiguate_state *ds) { struct object_directory *odb; - for (odb = ds->repo->objects->odb; odb && !ds->ambiguous; odb = odb->next) { - int pos; - struct oid_array *loose_objects; - - loose_objects = odb_loose_cache(odb, &ds->bin_pfx); - pos = oid_array_lookup(loose_objects, &ds->bin_pfx); - if (pos < 0) - pos = -1 - pos; - while (!ds->ambiguous && pos < loose_objects->nr) { - const struct object_id *oid; - oid = loose_objects->oid + pos; - if (!match_hash(ds->len, ds->bin_pfx.hash, oid->hash)) - break; - update_candidates(ds, oid); - pos++; - } - } + for (odb = ds->repo->objects->odb; odb && !ds->ambiguous; odb = odb->next) + oidtree_each(odb_loose_cache(odb, &ds->bin_pfx), + &ds->bin_pfx, ds->len, match_prefix, ds); } static int match_hash(unsigned len, const unsigned char *a, const unsigned char *b) diff --git a/object-store.h b/object-store.h index ec32c23dcb..e679acc4c3 100644 --- a/object-store.h +++ b/object-store.h @@ -7,6 +7,9 @@ #include "oid-array.h" #include "strbuf.h" #include "thread-utils.h" +#include "khash.h" +#include "dir.h" +#include "oidtree.h" struct object_directory { struct object_directory *next; @@ -20,8 +23,8 @@ struct object_directory { * * Be sure to call odb_load_loose_cache() before using. */ - char loose_objects_subdir_seen[256]; - struct oid_array loose_objects_cache[256]; + uint32_t loose_objects_subdir_seen[8]; /* 256 bits */ + struct oidtree *loose_objects_cache; /* * Path to the alternative object store. If this is a relative path, @@ -30,6 +33,9 @@ struct object_directory { char *path; }; +KHASH_INIT(odb_path_map, const char * /* key: odb_path */, + struct object_directory *, 1, fspathhash, fspatheq); + void prepare_alt_odb(struct repository *r); char *compute_alternate_path(const char *path, struct strbuf *err); typedef int alt_odb_fn(struct object_directory *, void *); @@ -54,7 +60,7 @@ void add_to_alternates_memory(const char *dir); * Populate and return the loose object cache array corresponding to the * given object ID. */ -struct oid_array *odb_loose_cache(struct object_directory *odb, +struct oidtree *odb_loose_cache(struct object_directory *odb, const struct object_id *oid); /* Empty the loose object cache for the specified object directory. */ @@ -116,6 +122,8 @@ struct raw_object_store { */ struct object_directory *odb; struct object_directory **odb_tail; + kh_odb_path_map_t *odb_by_path; + int loaded_alternates; /* @@ -185,6 +185,24 @@ struct object *lookup_unknown_object(struct repository *r, const struct object_i return obj; } +struct object *lookup_object_by_type(struct repository *r, + const struct object_id *oid, + enum object_type type) +{ + switch (type) { + case OBJ_COMMIT: + return (struct object *)lookup_commit(r, oid); + case OBJ_TREE: + return (struct object *)lookup_tree(r, oid); + case OBJ_TAG: + return (struct object *)lookup_tag(r, oid); + case OBJ_BLOB: + return (struct object *)lookup_blob(r, oid); + default: + die("BUG: unknown object type %d", type); + } +} + struct object *parse_object_buffer(struct repository *r, const struct object_id *oid, enum object_type type, unsigned long size, void *buffer, int *eaten_p) { struct object *obj; @@ -511,6 +529,8 @@ static void free_object_directories(struct raw_object_store *o) free_object_directory(o->odb); o->odb = next; } + kh_destroy_odb_path_map(o->odb_by_path); + o->odb_by_path = NULL; } void raw_object_store_clear(struct raw_object_store *o) @@ -144,9 +144,27 @@ struct object *parse_object_or_die(const struct object_id *oid, const char *name */ struct object *parse_object_buffer(struct repository *r, const struct object_id *oid, enum object_type type, unsigned long size, void *buffer, int *eaten_p); -/** Returns the object, with potentially excess memory allocated. **/ +/* + * Allocate and return an object struct, even if you do not know the type of + * the object. The returned object may have its "type" field set to a real type + * (if somebody previously called lookup_blob(), etc), or it may be set to + * OBJ_NONE. In the latter case, subsequent calls to lookup_blob(), etc, will + * set the type field as appropriate. + * + * Use this when you do not know the expected type of an object and want to + * avoid parsing it for efficiency reasons. Try to avoid it otherwise; it + * may allocate excess memory, since the returned object must be as large as + * the maximum struct of any type. + */ struct object *lookup_unknown_object(struct repository *r, const struct object_id *oid); +/* + * Dispatch to the appropriate lookup_blob(), lookup_commit(), etc, based on + * "type". + */ +struct object *lookup_object_by_type(struct repository *r, const struct object_id *oid, + enum object_type type); + struct object_list *object_list_insert(struct object *item, struct object_list **list_p); diff --git a/oidtree.c b/oidtree.c new file mode 100644 index 0000000000..7eb0e9ba05 --- /dev/null +++ b/oidtree.c @@ -0,0 +1,104 @@ +/* + * A wrapper around cbtree which stores oids + * May be used to replace oid-array for prefix (abbreviation) matches + */ +#include "oidtree.h" +#include "alloc.h" +#include "hash.h" + +struct oidtree_node { + /* n.k[] is used to store "struct object_id" */ + struct cb_node n; +}; + +struct oidtree_iter_data { + oidtree_iter fn; + void *arg; + size_t *last_nibble_at; + int algo; + uint8_t last_byte; +}; + +void oidtree_init(struct oidtree *ot) +{ + cb_init(&ot->tree); + mem_pool_init(&ot->mem_pool, 0); +} + +void oidtree_clear(struct oidtree *ot) +{ + if (ot) { + mem_pool_discard(&ot->mem_pool, 0); + oidtree_init(ot); + } +} + +void oidtree_insert(struct oidtree *ot, const struct object_id *oid) +{ + struct oidtree_node *on; + + if (!oid->algo) + BUG("oidtree_insert requires oid->algo"); + + on = mem_pool_alloc(&ot->mem_pool, sizeof(*on) + sizeof(*oid)); + oidcpy_with_padding((struct object_id *)on->n.k, oid); + + /* + * n.b. Current callers won't get us duplicates, here. If a + * future caller causes duplicates, there'll be a a small leak + * that won't be freed until oidtree_clear. Currently it's not + * worth maintaining a free list + */ + cb_insert(&ot->tree, &on->n, sizeof(*oid)); +} + + +int oidtree_contains(struct oidtree *ot, const struct object_id *oid) +{ + struct object_id k; + size_t klen = sizeof(k); + + oidcpy_with_padding(&k, oid); + + if (oid->algo == GIT_HASH_UNKNOWN) + klen -= sizeof(oid->algo); + + /* cb_lookup relies on memcmp on the struct, so order matters: */ + klen += BUILD_ASSERT_OR_ZERO(offsetof(struct object_id, hash) < + offsetof(struct object_id, algo)); + + return cb_lookup(&ot->tree, (const uint8_t *)&k, klen) ? 1 : 0; +} + +static enum cb_next iter(struct cb_node *n, void *arg) +{ + struct oidtree_iter_data *x = arg; + const struct object_id *oid = (const struct object_id *)n->k; + + if (x->algo != GIT_HASH_UNKNOWN && x->algo != oid->algo) + return CB_CONTINUE; + + if (x->last_nibble_at) { + if ((oid->hash[*x->last_nibble_at] ^ x->last_byte) & 0xf0) + return CB_CONTINUE; + } + + return x->fn(oid, x->arg); +} + +void oidtree_each(struct oidtree *ot, const struct object_id *oid, + size_t oidhexsz, oidtree_iter fn, void *arg) +{ + size_t klen = oidhexsz / 2; + struct oidtree_iter_data x = { 0 }; + assert(oidhexsz <= GIT_MAX_HEXSZ); + + x.fn = fn; + x.arg = arg; + x.algo = oid->algo; + if (oidhexsz & 1) { + x.last_byte = oid->hash[klen]; + x.last_nibble_at = &klen; + } + cb_each(&ot->tree, (const uint8_t *)oid, klen, iter, &x); +} diff --git a/oidtree.h b/oidtree.h new file mode 100644 index 0000000000..77898f510a --- /dev/null +++ b/oidtree.h @@ -0,0 +1,22 @@ +#ifndef OIDTREE_H +#define OIDTREE_H + +#include "cbtree.h" +#include "hash.h" +#include "mem-pool.h" + +struct oidtree { + struct cb_tree tree; + struct mem_pool mem_pool; +}; + +void oidtree_init(struct oidtree *); +void oidtree_clear(struct oidtree *); +void oidtree_insert(struct oidtree *, const struct object_id *); +int oidtree_contains(struct oidtree *, const struct object_id *); + +typedef enum cb_next (*oidtree_iter)(const struct object_id *, void *data); +void oidtree_each(struct oidtree *, const struct object_id *, + size_t oidhexsz, oidtree_iter, void *data); + +#endif /* OIDTREE_H */ diff --git a/pack-check.c b/pack-check.c index 4b089fe8ec..c8e560d71a 100644 --- a/pack-check.c +++ b/pack-check.c @@ -164,22 +164,13 @@ static int verify_packfile(struct repository *r, int verify_pack_index(struct packed_git *p) { - size_t len; - const unsigned char *index_base; - git_hash_ctx ctx; - unsigned char hash[GIT_MAX_RAWSZ]; int err = 0; if (open_pack_index(p)) return error("packfile %s index not opened", p->pack_name); - index_base = p->index_data; - len = p->index_size - the_hash_algo->rawsz; /* Verify SHA1 sum of the index file */ - the_hash_algo->init_fn(&ctx); - the_hash_algo->update_fn(&ctx, index_base, len); - the_hash_algo->final_fn(hash, &ctx); - if (!hasheq(hash, index_base + len)) + if (!hashfile_checksum_valid(p->index_data, p->index_size)) err = error("Packfile index for %s hash mismatch", p->pack_name); return err; diff --git a/packfile.c b/packfile.c index 755aa7aec5..9ef6d98292 100644 --- a/packfile.c +++ b/packfile.c @@ -652,8 +652,8 @@ unsigned char *use_pack(struct packed_git *p, PROT_READ, MAP_PRIVATE, p->pack_fd, win->offset); if (win->base == MAP_FAILED) - die_errno("packfile %s cannot be mapped", - p->pack_name); + die_errno(_("packfile %s cannot be mapped%s"), + p->pack_name, mmap_os_err()); if (!win->offset && win->len == p->pack_size && !p->do_not_close) close_pack_fd(p); diff --git a/pathspec.h b/pathspec.h index fceebb876f..2341dc9901 100644 --- a/pathspec.h +++ b/pathspec.h @@ -108,7 +108,7 @@ struct pathspec { * * A similar process is applied when a new pathspec magic is added. The designer * lifts the GUARD_PATHSPEC restriction in the functions that support the new - * magic. At the same time (s)he has to make sure this new feature will be + * magic while at the same time making sure this new feature will be * caught at parse_pathspec() in commands that cannot handle the new magic in * some cases. grepping parse_pathspec() should help. */ diff --git a/perl/Git.pm b/perl/Git.pm index 02eacef0c2..090a7df63f 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -11,9 +11,6 @@ use 5.008; use strict; use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : (); -use File::Temp (); -use File::Spec (); - BEGIN { our ($VERSION, @ISA, @EXPORT, @EXPORT_OK); @@ -103,12 +100,9 @@ increase notwithstanding). =cut -use Carp qw(carp croak); # but croak is bad - throw instead +sub carp { require Carp; goto &Carp::carp } +sub croak { require Carp; goto &Carp::croak } use Git::LoadCPAN::Error qw(:try); -use Cwd qw(abs_path cwd); -use IPC::Open2 qw(open2); -use Fcntl qw(SEEK_SET SEEK_CUR); -use Time::Local qw(timegm); } @@ -191,13 +185,15 @@ sub repository { $dir = undef; }; + require Cwd; if ($dir) { + require File::Spec; File::Spec->file_name_is_absolute($dir) or $dir = $opts{Directory} . '/' . $dir; - $opts{Repository} = abs_path($dir); + $opts{Repository} = Cwd::abs_path($dir); # If --git-dir went ok, this shouldn't die either. my $prefix = $search->command_oneline('rev-parse', '--show-prefix'); - $dir = abs_path($opts{Directory}) . '/'; + $dir = Cwd::abs_path($opts{Directory}) . '/'; if ($prefix) { if (substr($dir, -length($prefix)) ne $prefix) { throw Error::Simple("rev-parse confused me - $dir does not have trailing $prefix"); @@ -223,7 +219,7 @@ sub repository { throw Error::Simple("fatal: Not a git repository: $dir"); } - $opts{Repository} = abs_path($dir); + $opts{Repository} = Cwd::abs_path($dir); } delete $opts{Directory}; @@ -408,10 +404,12 @@ sub command_bidi_pipe { my $cwd_save = undef; if ($self) { shift; - $cwd_save = cwd(); + require Cwd; + $cwd_save = Cwd::getcwd(); _setup_git_cmd_env($self); } - $pid = open2($in, $out, 'git', @_); + require IPC::Open2; + $pid = IPC::Open2::open2($in, $out, 'git', @_); chdir($cwd_save) if $cwd_save; return ($pid, $in, $out, join(' ', @_)); } @@ -538,7 +536,8 @@ sub get_tz_offset { my $t = shift || time; my @t = localtime($t); $t[5] += 1900; - my $gm = timegm(@t); + require Time::Local; + my $gm = Time::Local::timegm(@t); my $sign = qw( + + - )[ $gm <=> $t ]; return sprintf("%s%02d%02d", $sign, (gmtime(abs($t - $gm)))[2,1]); } @@ -1340,6 +1339,7 @@ sub _temp_cache { my $n = $name; $n =~ s/\W/_/g; # no strange chars + require File::Temp; ($$temp_fd, $fname) = File::Temp::tempfile( "Git_${n}_XXXXXX", UNLINK => 1, DIR => $tmpdir, ) or throw Error::Simple("couldn't open new temp file"); @@ -1362,9 +1362,9 @@ sub temp_reset { truncate $temp_fd, 0 or throw Error::Simple("couldn't truncate file"); - sysseek($temp_fd, 0, SEEK_SET) and seek($temp_fd, 0, SEEK_SET) + sysseek($temp_fd, 0, Fcntl::SEEK_SET()) and seek($temp_fd, 0, Fcntl::SEEK_SET()) or throw Error::Simple("couldn't seek to beginning of file"); - sysseek($temp_fd, 0, SEEK_CUR) == 0 and tell($temp_fd) == 0 + sysseek($temp_fd, 0, Fcntl::SEEK_CUR()) == 0 and tell($temp_fd) == 0 or throw Error::Simple("expected file position to be reset"); } diff --git a/pkt-line.c b/pkt-line.c index 98304ce374..9f63eae2e6 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -103,7 +103,7 @@ void packet_response_end(int fd) { packet_trace("0002", 4, 1); if (write_in_full(fd, "0002", 4) < 0) - die_errno(_("unable to write stateless separator packet")); + die_errno(_("unable to write response end packet")); } int packet_flush_gently(int fd) @@ -1735,6 +1735,10 @@ static size_t userformat_want_item(struct strbuf *sb, const char *placeholder, case 'S': w->source = 1; break; + case 'd': + case 'D': + w->decorate = 1; + break; } return 0; } @@ -65,12 +65,16 @@ static inline int cmit_fmt_is_mail(enum cmit_fmt fmt) return (fmt == CMIT_FMT_EMAIL || fmt == CMIT_FMT_MBOXRD); } +/* + * Examine the user-specified format given by "fmt" (or if NULL, the global one + * previously saved by get_commit_format()), and set flags based on which items + * the format will need when it is expanded. + */ struct userformat_want { unsigned notes:1; unsigned source:1; + unsigned decorate:1; }; - -/* Set the flag "w->notes" if there is placeholder %N in "fmt". */ void userformat_find_requirements(const char *fmt, struct userformat_want *w); /* diff --git a/promisor-remote.c b/promisor-remote.c index da3f2ca261..db2ebdc66e 100644 --- a/promisor-remote.c +++ b/promisor-remote.c @@ -5,14 +5,13 @@ #include "transport.h" #include "strvec.h" -static char *repository_format_partial_clone; +struct promisor_remote_config { + struct promisor_remote *promisors; + struct promisor_remote **promisors_tail; +}; -void set_repository_format_partial_clone(char *partial_clone) -{ - repository_format_partial_clone = xstrdup_or_null(partial_clone); -} - -static int fetch_objects(const char *remote_name, +static int fetch_objects(struct repository *repo, + const char *remote_name, const struct object_id *oids, int oid_nr) { @@ -22,6 +21,8 @@ static int fetch_objects(const char *remote_name, child.git_cmd = 1; child.in = -1; + if (repo != the_repository) + prepare_other_repo_env(&child.env_array, repo->gitdir); strvec_pushl(&child.args, "-c", "fetch.negotiationAlgorithm=noop", "fetch", remote_name, "--no-tags", "--no-write-fetch-head", "--recurse-submodules=no", @@ -30,6 +31,8 @@ static int fetch_objects(const char *remote_name, die(_("promisor-remote: unable to fork off fetch subprocess")); child_in = xfdopen(child.in, "w"); + trace2_data_intmax("promisor", repo, "fetch_count", oid_nr); + for (i = 0; i < oid_nr; i++) { if (fputs(oid_to_hex(&oids[i]), child_in) < 0) die_errno(_("promisor-remote: could not write to fetch subprocess")); @@ -42,10 +45,8 @@ static int fetch_objects(const char *remote_name, return finish_command(&child) ? -1 : 0; } -static struct promisor_remote *promisors; -static struct promisor_remote **promisors_tail = &promisors; - -static struct promisor_remote *promisor_remote_new(const char *remote_name) +static struct promisor_remote *promisor_remote_new(struct promisor_remote_config *config, + const char *remote_name) { struct promisor_remote *r; @@ -57,18 +58,19 @@ static struct promisor_remote *promisor_remote_new(const char *remote_name) FLEX_ALLOC_STR(r, name, remote_name); - *promisors_tail = r; - promisors_tail = &r->next; + *config->promisors_tail = r; + config->promisors_tail = &r->next; return r; } -static struct promisor_remote *promisor_remote_lookup(const char *remote_name, +static struct promisor_remote *promisor_remote_lookup(struct promisor_remote_config *config, + const char *remote_name, struct promisor_remote **previous) { struct promisor_remote *r, *p; - for (p = NULL, r = promisors; r; p = r, r = r->next) + for (p = NULL, r = config->promisors; r; p = r, r = r->next) if (!strcmp(r->name, remote_name)) { if (previous) *previous = p; @@ -78,7 +80,8 @@ static struct promisor_remote *promisor_remote_lookup(const char *remote_name, return NULL; } -static void promisor_remote_move_to_tail(struct promisor_remote *r, +static void promisor_remote_move_to_tail(struct promisor_remote_config *config, + struct promisor_remote *r, struct promisor_remote *previous) { if (r->next == NULL) @@ -87,14 +90,15 @@ static void promisor_remote_move_to_tail(struct promisor_remote *r, if (previous) previous->next = r->next; else - promisors = r->next ? r->next : r; + config->promisors = r->next ? r->next : r; r->next = NULL; - *promisors_tail = r; - promisors_tail = &r->next; + *config->promisors_tail = r; + config->promisors_tail = &r->next; } static int promisor_remote_config(const char *var, const char *value, void *data) { + struct promisor_remote_config *config = data; const char *name; size_t namelen; const char *subkey; @@ -110,8 +114,8 @@ static int promisor_remote_config(const char *var, const char *value, void *data remote_name = xmemdupz(name, namelen); - if (!promisor_remote_lookup(remote_name, NULL)) - promisor_remote_new(remote_name); + if (!promisor_remote_lookup(config, remote_name, NULL)) + promisor_remote_new(config, remote_name); free(remote_name); return 0; @@ -120,9 +124,9 @@ static int promisor_remote_config(const char *var, const char *value, void *data struct promisor_remote *r; char *remote_name = xmemdupz(name, namelen); - r = promisor_remote_lookup(remote_name, NULL); + r = promisor_remote_lookup(config, remote_name, NULL); if (!r) - r = promisor_remote_new(remote_name); + r = promisor_remote_new(config, remote_name); free(remote_name); @@ -135,59 +139,63 @@ static int promisor_remote_config(const char *var, const char *value, void *data return 0; } -static int initialized; - -static void promisor_remote_init(void) +static void promisor_remote_init(struct repository *r) { - if (initialized) + struct promisor_remote_config *config; + + if (r->promisor_remote_config) return; - initialized = 1; + config = r->promisor_remote_config = + xcalloc(sizeof(*r->promisor_remote_config), 1); + config->promisors_tail = &config->promisors; - git_config(promisor_remote_config, NULL); + repo_config(r, promisor_remote_config, config); - if (repository_format_partial_clone) { + if (r->repository_format_partial_clone) { struct promisor_remote *o, *previous; - o = promisor_remote_lookup(repository_format_partial_clone, + o = promisor_remote_lookup(config, + r->repository_format_partial_clone, &previous); if (o) - promisor_remote_move_to_tail(o, previous); + promisor_remote_move_to_tail(config, o, previous); else - promisor_remote_new(repository_format_partial_clone); + promisor_remote_new(config, r->repository_format_partial_clone); } } -static void promisor_remote_clear(void) +void promisor_remote_clear(struct promisor_remote_config *config) { - while (promisors) { - struct promisor_remote *r = promisors; - promisors = promisors->next; + while (config->promisors) { + struct promisor_remote *r = config->promisors; + config->promisors = config->promisors->next; free(r); } - promisors_tail = &promisors; + config->promisors_tail = &config->promisors; } -void promisor_remote_reinit(void) +void repo_promisor_remote_reinit(struct repository *r) { - initialized = 0; - promisor_remote_clear(); - promisor_remote_init(); + promisor_remote_clear(r->promisor_remote_config); + FREE_AND_NULL(r->promisor_remote_config); + promisor_remote_init(r); } -struct promisor_remote *promisor_remote_find(const char *remote_name) +struct promisor_remote *repo_promisor_remote_find(struct repository *r, + const char *remote_name) { - promisor_remote_init(); + promisor_remote_init(r); if (!remote_name) - return promisors; + return r->promisor_remote_config->promisors; - return promisor_remote_lookup(remote_name, NULL); + return promisor_remote_lookup(r->promisor_remote_config, remote_name, NULL); } -int has_promisor_remote(void) +int repo_has_promisor_remote(struct repository *r) { - return !!promisor_remote_find(NULL); + return !!repo_promisor_remote_find(r, NULL); } static int remove_fetched_oids(struct repository *repo, @@ -235,10 +243,10 @@ int promisor_remote_get_direct(struct repository *repo, if (oid_nr == 0) return 0; - promisor_remote_init(); + promisor_remote_init(repo); - for (r = promisors; r; r = r->next) { - if (fetch_objects(r->name, remaining_oids, remaining_nr) < 0) { + for (r = repo->promisor_remote_config->promisors; r; r = r->next) { + if (fetch_objects(repo, r->name, remaining_oids, remaining_nr) < 0) { if (remaining_nr == 1) continue; remaining_nr = remove_fetched_oids(repo, &remaining_oids, diff --git a/promisor-remote.h b/promisor-remote.h index c7a14063c5..edc45ab0f5 100644 --- a/promisor-remote.h +++ b/promisor-remote.h @@ -17,9 +17,25 @@ struct promisor_remote { const char name[FLEX_ARRAY]; }; -void promisor_remote_reinit(void); -struct promisor_remote *promisor_remote_find(const char *remote_name); -int has_promisor_remote(void); +void repo_promisor_remote_reinit(struct repository *r); +static inline void promisor_remote_reinit(void) +{ + repo_promisor_remote_reinit(the_repository); +} + +void promisor_remote_clear(struct promisor_remote_config *config); + +struct promisor_remote *repo_promisor_remote_find(struct repository *r, const char *remote_name); +static inline struct promisor_remote *promisor_remote_find(const char *remote_name) +{ + return repo_promisor_remote_find(the_repository, remote_name); +} + +int repo_has_promisor_remote(struct repository *r); +static inline int has_promisor_remote(void) +{ + return repo_has_promisor_remote(the_repository); +} /* * Fetches all requested objects from all promisor remotes, trying them one at @@ -32,10 +48,4 @@ int promisor_remote_get_direct(struct repository *repo, const struct object_id *oids, int oid_nr); -/* - * This should be used only once from setup.c to set the value we got - * from the extensions.partialclone config option. - */ -void set_repository_format_partial_clone(char *partial_clone); - #endif /* PROMISOR_REMOTE_H */ @@ -31,6 +31,7 @@ struct strbuf; void sq_quote_buf(struct strbuf *, const char *src); void sq_quote_argv(struct strbuf *, const char **argv); +__attribute__((format (printf, 2, 3))) void sq_quotef(struct strbuf *, const char *fmt, ...); /* diff --git a/range-diff.c b/range-diff.c index 1a4471fe4c..e9479794b4 100644 --- a/range-diff.c +++ b/range-diff.c @@ -274,9 +274,10 @@ static void find_exact_matches(struct string_list *a, struct string_list *b) hashmap_clear(&map); } -static void diffsize_consume(void *data, char *line, unsigned long len) +static int diffsize_consume(void *data, char *line, unsigned long len) { (*(int *)data)++; + return 0; } static void diffsize_hunk(void *data, long ob, long on, long nb, long nn, diff --git a/reachable.c b/reachable.c index c59847257a..84e3d0d75e 100644 --- a/reachable.c +++ b/reachable.c @@ -159,24 +159,6 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs, FOR_EACH_OBJECT_LOCAL_ONLY); } -static void *lookup_object_by_type(struct repository *r, - const struct object_id *oid, - enum object_type type) -{ - switch (type) { - case OBJ_COMMIT: - return lookup_commit(r, oid); - case OBJ_TREE: - return lookup_tree(r, oid); - case OBJ_TAG: - return lookup_tag(r, oid); - case OBJ_BLOB: - return lookup_blob(r, oid); - default: - die("BUG: unknown object type %d", type); - } -} - static int mark_object_seen(const struct object_id *oid, enum object_type type, int exclude, diff --git a/read-cache.c b/read-cache.c index 0c3ac3cefc..46ccd66f34 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1585,8 +1585,7 @@ int refresh_index(struct index_state *istate, unsigned int flags, */ preload_index(istate, pathspec, 0); trace2_region_enter("index", "refresh", NULL); - /* TODO: audit for interaction with sparse-index. */ - ensure_full_index(istate); + for (i = 0; i < istate->cache_nr; i++) { struct cache_entry *ce, *new_entry; int cache_errno = 0; @@ -1601,6 +1600,13 @@ int refresh_index(struct index_state *istate, unsigned int flags, if (ignore_skip_worktree && ce_skip_worktree(ce)) continue; + /* + * If this entry is a sparse directory, then there isn't + * any stat() information to update. Ignore the entry. + */ + if (S_ISSPARSEDIR(ce->ce_mode)) + continue; + if (pathspec && !ce_path_match(istate, ce, pathspec, seen)) filtered = 1; @@ -2233,7 +2239,8 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist) mmap = xmmap_gently(NULL, mmap_size, PROT_READ, MAP_PRIVATE, fd, 0); if (mmap == MAP_FAILED) - die_errno(_("%s: unable to map index file"), path); + die_errno(_("%s: unable to map index file%s"), path, + mmap_os_err()); close(fd); hdr = (const struct cache_header *)mmap; diff --git a/ref-filter.c b/ref-filter.c index 4db0e40ff4..f45d3a1b26 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -213,6 +213,7 @@ static int used_atom_cnt, need_tagged, need_symref; * Expand string, append it to strbuf *sb, then return error code ret. * Allow to save few lines of code. */ +__attribute__((format (printf, 3, 4))) static int strbuf_addf_ret(struct strbuf *sb, int ret, const char *fmt, ...) { va_list ap; @@ -2010,7 +2010,7 @@ int peel_iterated_oid(const struct object_id *base, struct object_id *peeled) oideq(current_ref_iter->oid, base))) return ref_iterator_peel(current_ref_iter, peeled); - return peel_object(base, peeled); + return peel_object(base, peeled) ? -1 : 0; } int refs_create_symref(struct ref_store *refs, diff --git a/refs/packed-backend.c b/refs/packed-backend.c index dfecdbc1db..f8aa97d799 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -889,7 +889,7 @@ static int packed_ref_iterator_peel(struct ref_iterator *ref_iterator, } else if ((iter->base.flags & (REF_ISBROKEN | REF_ISSYMREF))) { return -1; } else { - return !!peel_object(&iter->oid, peeled); + return peel_object(&iter->oid, peeled) ? -1 : 0; } } @@ -1425,7 +1425,7 @@ static int packed_transaction_prepare(struct ref_store *ref_store, */ CALLOC_ARRAY(data, 1); - string_list_init(&data->updates, 0); + string_list_init_nodup(&data->updates); transaction->backend_data = data; diff --git a/refs/ref-cache.c b/refs/ref-cache.c index 46f1e54284..49d732f6db 100644 --- a/refs/ref-cache.c +++ b/refs/ref-cache.c @@ -491,7 +491,7 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator) static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator, struct object_id *peeled) { - return peel_object(ref_iterator->oid, peeled); + return peel_object(ref_iterator->oid, peeled) ? -1 : 0; } static int cache_ref_iterator_abort(struct ref_iterator *ref_iterator) diff --git a/refs/refs-internal.h b/refs/refs-internal.h index 467f4b3c93..3155708345 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -453,6 +453,9 @@ void base_ref_iterator_free(struct ref_iterator *iter); */ typedef int ref_iterator_advance_fn(struct ref_iterator *ref_iterator); +/* + * Peels the current ref, returning 0 for success or -1 for failure. + */ typedef int ref_iterator_peel_fn(struct ref_iterator *ref_iterator, struct object_id *peeled); diff --git a/remote-curl.c b/remote-curl.c index 9d432c299a..6c320d5704 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -653,7 +653,7 @@ static int rpc_read_from_out(struct rpc_state *rpc, int options, memcpy(buf - 4, "0000", 4); break; case PACKET_READ_RESPONSE_END: - die(_("remote server sent stateless separator")); + die(_("remote server sent unexpected response end packet")); } } diff --git a/repository.c b/repository.c index 448cd557d4..b2bf44c6fa 100644 --- a/repository.c +++ b/repository.c @@ -11,6 +11,7 @@ #include "lockfile.h" #include "submodule-config.h" #include "sparse-index.h" +#include "promisor-remote.h" /* The main repository */ static struct repository the_repo; @@ -172,6 +173,10 @@ int repo_init(struct repository *repo, repo_set_hash_algo(repo, format.hash_algo); + /* take ownership of format.partial_clone */ + repo->repository_format_partial_clone = format.partial_clone; + format.partial_clone = NULL; + if (worktree) repo_set_worktree(repo, worktree); @@ -258,6 +263,11 @@ void repo_clear(struct repository *repo) if (repo->index != &the_index) FREE_AND_NULL(repo->index); } + + if (repo->promisor_remote_config) { + promisor_remote_clear(repo->promisor_remote_config); + FREE_AND_NULL(repo->promisor_remote_config); + } } int repo_read_index(struct repository *repo) diff --git a/repository.h b/repository.h index a45f7520fd..3740c93bc0 100644 --- a/repository.h +++ b/repository.h @@ -10,6 +10,7 @@ struct lock_file; struct pathspec; struct raw_object_store; struct submodule_cache; +struct promisor_remote_config; enum untracked_cache_setting { UNTRACKED_CACHE_UNSET = -1, @@ -139,6 +140,10 @@ struct repository { /* True if commit-graph has been disabled within this process. */ int commit_graph_disabled; + /* Configurations related to promisor remotes. */ + char *repository_format_partial_clone; + struct promisor_remote_config *promisor_remote_config; + /* Configurations */ /* Indicate if a repository has a different 'commondir' from 'gitdir' */ diff --git a/revision.h b/revision.h index 5c5510d422..fbb068da9f 100644 --- a/revision.h +++ b/revision.h @@ -215,7 +215,8 @@ struct rev_info { missing_newline:1, date_mode_explicit:1, preserve_subject:1, - encode_email_headers:1; + encode_email_headers:1, + include_header:1; unsigned int disable_stdin:1; /* --show-linear-break */ unsigned int track_linear:1, diff --git a/run-command.c b/run-command.c index be6bc128cd..f72e72cce7 100644 --- a/run-command.c +++ b/run-command.c @@ -11,9 +11,8 @@ void child_process_init(struct child_process *child) { - memset(child, 0, sizeof(*child)); - strvec_init(&child->args); - strvec_init(&child->env_array); + struct child_process blank = CHILD_PROCESS_INIT; + memcpy(child, &blank, sizeof(*child)); } void child_process_clear(struct child_process *child) @@ -1892,3 +1891,15 @@ int run_auto_maintenance(int quiet) return run_command(&maint); } + +void prepare_other_repo_env(struct strvec *env_array, const char *new_git_dir) +{ + const char * const *var; + + for (var = local_repo_env; *var; var++) { + if (strcmp(*var, CONFIG_DATA_ENVIRONMENT) && + strcmp(*var, CONFIG_COUNT_ENVIRONMENT)) + strvec_push(env_array, *var); + } + strvec_pushf(env_array, "%s=%s", GIT_DIR_ENVIRONMENT, new_git_dir); +} diff --git a/run-command.h b/run-command.h index d08414a92e..af1296769f 100644 --- a/run-command.h +++ b/run-command.h @@ -141,7 +141,10 @@ struct child_process { void *clean_on_exit_handler_cbdata; }; -#define CHILD_PROCESS_INIT { NULL, STRVEC_INIT, STRVEC_INIT } +#define CHILD_PROCESS_INIT { \ + .args = STRVEC_INIT, \ + .env_array = STRVEC_INIT, \ +} /** * The functions: child_process_init, start_command, finish_command, @@ -483,4 +486,14 @@ int run_processes_parallel_tr2(int n, get_next_task_fn, start_failure_fn, task_finished_fn, void *pp_cb, const char *tr2_category, const char *tr2_label); +/** + * Convenience function which prepares env_array for a command to be run in a + * new repo. This adds all GIT_* environment variables to env_array with the + * exception of GIT_CONFIG_PARAMETERS and GIT_CONFIG_COUNT (which cause the + * corresponding environment variables to be unset in the subprocess) and adds + * an environment variable pointing to new_git_dir. See local_repo_env in + * cache.h for more information. + */ +void prepare_other_repo_env(struct strvec *env_array, const char *new_git_dir); + #endif diff --git a/send-pack.c b/send-pack.c index 9cb9f71650..5a79e0e711 100644 --- a/send-pack.c +++ b/send-pack.c @@ -486,6 +486,12 @@ int send_pack(struct send_pack_args *args, const char *push_cert_nonce = NULL; struct packet_reader reader; + if (!remote_refs) { + fprintf(stderr, "No refs in common and none specified; doing nothing.\n" + "Perhaps you should specify a branch.\n"); + return 0; + } + git_config_get_bool("push.negotiate", &push_negotiate); if (push_negotiate) get_commons_through_negotiation(args->url, remote_refs, &commons); @@ -534,11 +540,6 @@ int send_pack(struct send_pack_args *args, } } - if (!remote_refs) { - fprintf(stderr, "No refs in common and none specified; doing nothing.\n" - "Perhaps you should specify a branch.\n"); - return 0; - } if (args->atomic && !atomic_supported) die(_("the receiving end does not support --atomic push")); diff --git a/sequencer.c b/sequencer.c index 0bec01cf38..7f07cd00f3 100644 --- a/sequencer.c +++ b/sequencer.c @@ -3521,6 +3521,7 @@ static int do_exec(struct repository *r, const char *command_line) return status; } +__attribute__((format (printf, 2, 3))) static int safe_append(const char *filename, const char *fmt, ...) { va_list ap; @@ -3598,8 +3599,27 @@ static int do_label(struct repository *r, const char *name, int len) return ret; } +__attribute__((format (printf, 3, 4))) static const char *reflog_message(struct replay_opts *opts, - const char *sub_action, const char *fmt, ...); + const char *sub_action, const char *fmt, ...) +{ + va_list ap; + static struct strbuf buf = STRBUF_INIT; + char *reflog_action = getenv(GIT_REFLOG_ACTION); + + va_start(ap, fmt); + strbuf_reset(&buf); + strbuf_addstr(&buf, reflog_action ? reflog_action : action_name(opts)); + if (sub_action) + strbuf_addf(&buf, " (%s)", sub_action); + if (fmt) { + strbuf_addstr(&buf, ": "); + strbuf_vaddf(&buf, fmt, ap); + } + va_end(ap); + + return buf.buf; +} static int do_reset(struct repository *r, const char *name, int len, @@ -4178,27 +4198,6 @@ int apply_autostash_oid(const char *stash_oid) return apply_save_autostash_oid(stash_oid, 1); } -static const char *reflog_message(struct replay_opts *opts, - const char *sub_action, const char *fmt, ...) -{ - va_list ap; - static struct strbuf buf = STRBUF_INIT; - char *reflog_action = getenv(GIT_REFLOG_ACTION); - - va_start(ap, fmt); - strbuf_reset(&buf); - strbuf_addstr(&buf, reflog_action ? reflog_action : action_name(opts)); - if (sub_action) - strbuf_addf(&buf, " (%s)", sub_action); - if (fmt) { - strbuf_addstr(&buf, ": "); - strbuf_vaddf(&buf, fmt, ap); - } - va_end(ap); - - return buf.buf; -} - static int run_git_checkout(struct repository *r, struct replay_opts *opts, const char *commit, const char *action) { @@ -258,7 +258,7 @@ static int process_request(void) state = PROCESS_REQUEST_DONE; break; case PACKET_READ_RESPONSE_END: - BUG("unexpected stateless separator packet"); + BUG("unexpected response end packet"); } } diff --git a/server-info.c b/server-info.c index de0aa4498c..7701d7c20a 100644 --- a/server-info.c +++ b/server-info.c @@ -27,6 +27,7 @@ static int uic_is_stale(const struct update_info_ctx *uic) return uic->old_fp == NULL; } +__attribute__((format (printf, 2, 3))) static int uic_printf(struct update_info_ctx *uic, const char *fmt, ...) { va_list ap; @@ -468,8 +468,6 @@ static enum extension_result handle_extension_v0(const char *var, data->precious_objects = git_config_bool(var, value); return EXTENSION_OK; } else if (!strcmp(ext, "partialclone")) { - if (!value) - return config_error_nonbool(var); data->partial_clone = xstrdup(value); return EXTENSION_OK; } else if (!strcmp(ext, "worktreeconfig")) { @@ -566,7 +564,6 @@ static int check_repository_format_gently(const char *gitdir, struct repository_ } repository_format_precious_objects = candidate->precious_objects; - set_repository_format_partial_clone(candidate->partial_clone); repository_format_worktree_config = candidate->worktree_config; string_list_clear(&candidate->unknown_extensions, 0); string_list_clear(&candidate->v1_only_extensions, 0); @@ -1197,6 +1194,11 @@ int discover_git_directory(struct strbuf *commondir, return -1; } + /* take ownership of candidate.partial_clone */ + the_repository->repository_format_partial_clone = + candidate.partial_clone; + candidate.partial_clone = NULL; + clear_repository_format(&candidate); return 0; } @@ -1304,8 +1306,13 @@ const char *setup_git_directory_gently(int *nongit_ok) gitdir = DEFAULT_GIT_DIR_ENVIRONMENT; setup_git_env(gitdir); } - if (startup_info->have_repository) + if (startup_info->have_repository) { repo_set_hash_algo(the_repository, repo_fmt.hash_algo); + /* take ownership of repo_fmt.partial_clone */ + the_repository->repository_format_partial_clone = + repo_fmt.partial_clone; + repo_fmt.partial_clone = NULL; + } } /* * Since precompose_string_if_needed() needs to look at @@ -1390,6 +1397,8 @@ void check_repository_format(struct repository_format *fmt) check_repository_format_gently(get_git_dir(), fmt, NULL); startup_info->have_repository = 1; repo_set_hash_algo(the_repository, fmt->hash_algo); + the_repository->repository_format_partial_clone = + xstrdup_or_null(fmt->partial_clone); clear_repository_format(&repo_fmt); } diff --git a/sparse-index.c b/sparse-index.c index affc4048f2..53c8f711cc 100644 --- a/sparse-index.c +++ b/sparse-index.c @@ -116,6 +116,17 @@ int set_sparse_index_config(struct repository *repo, int enable) return res; } +static int index_has_unmerged_entries(struct index_state *istate) +{ + int i; + for (i = 0; i < istate->cache_nr; i++) { + if (ce_stage(istate->cache[i])) + return 1; + } + + return 0; +} + int convert_to_sparse(struct index_state *istate) { int test_env; @@ -152,6 +163,13 @@ int convert_to_sparse(struct index_state *istate) return -1; } + /* + * NEEDSWORK: If we have unmerged entries, then stay full. + * Unmerged entries prevent the cache-tree extension from working. + */ + if (index_has_unmerged_entries(istate)) + return 0; + if (cache_tree_update(istate, 0)) { warning(_("unable to update cache-tree, staying full")); return -1; @@ -168,6 +186,10 @@ int convert_to_sparse(struct index_state *istate) cache_tree_free(&istate->cache_tree); cache_tree_update(istate, 0); + istate->fsmonitor_has_run_once = 0; + FREE_AND_NULL(istate->fsmonitor_dirty); + FREE_AND_NULL(istate->fsmonitor_last_update); + istate->sparse_index = 1; trace2_region_leave("index", "convert_to_sparse", istate->repo); return 0; @@ -195,7 +217,7 @@ static int add_path_to_index(const struct object_id *oid, strbuf_addstr(base, path); ce = make_cache_entry(istate, mode, oid, base->buf, 0, 0); - ce->ce_flags |= CE_SKIP_WORKTREE; + ce->ce_flags |= CE_SKIP_WORKTREE | CE_EXTENDED; set_index_entry(istate, istate->cache_nr++, ce); strbuf_setlen(base, len); @@ -264,6 +286,9 @@ void ensure_full_index(struct index_state *istate) istate->cache = full->cache; istate->cache_nr = full->cache_nr; istate->cache_alloc = full->cache_alloc; + istate->fsmonitor_has_run_once = 0; + FREE_AND_NULL(istate->fsmonitor_dirty); + FREE_AND_NULL(istate->fsmonitor_last_update); strbuf_release(&base); free(full); @@ -52,8 +52,8 @@ char strbuf_slopbuf[1]; void strbuf_init(struct strbuf *sb, size_t hint) { - sb->alloc = sb->len = 0; - sb->buf = strbuf_slopbuf; + struct strbuf blank = STRBUF_INIT; + memcpy(sb, &blank, sizeof(*sb)); if (hint) strbuf_grow(sb, hint); } @@ -263,6 +263,7 @@ static inline void strbuf_insertstr(struct strbuf *sb, size_t pos, void strbuf_vinsertf(struct strbuf *sb, size_t pos, const char *fmt, va_list ap); +__attribute__((format (printf, 3, 4))) void strbuf_insertf(struct strbuf *sb, size_t pos, const char *fmt, ...); /** @@ -337,8 +338,8 @@ const char *strbuf_join_argv(struct strbuf *buf, int argc, * placeholder is unknown, then the percent sign is copied, too. * * In order to facilitate caching and to make it possible to give - * parameters to the callback, `strbuf_expand()` passes a context pointer, - * which can be used by the programmer of the callback as she sees fit. + * parameters to the callback, `strbuf_expand()` passes a context + * pointer with any kind of data. */ typedef size_t (*expand_fn_t) (struct strbuf *sb, const char *placeholder, diff --git a/string-list.c b/string-list.c index a917955fbd..43576ad126 100644 --- a/string-list.c +++ b/string-list.c @@ -1,10 +1,24 @@ #include "cache.h" #include "string-list.h" +void string_list_init_nodup(struct string_list *list) +{ + struct string_list blank = STRING_LIST_INIT_NODUP; + memcpy(list, &blank, sizeof(*list)); +} + +void string_list_init_dup(struct string_list *list) +{ + struct string_list blank = STRING_LIST_INIT_DUP; + memcpy(list, &blank, sizeof(*list)); +} + void string_list_init(struct string_list *list, int strdup_strings) { - memset(list, 0, sizeof(*list)); - list->strdup_strings = strdup_strings; + if (strdup_strings) + string_list_init_dup(list); + else + string_list_init_nodup(list); } /* if there is no exact match, point to the index where the entry could be diff --git a/string-list.h b/string-list.h index 6c5d274126..0d6b469239 100644 --- a/string-list.h +++ b/string-list.h @@ -91,14 +91,21 @@ struct string_list { compare_strings_fn cmp; /* NULL uses strcmp() */ }; -#define STRING_LIST_INIT_NODUP { NULL, 0, 0, 0, NULL } -#define STRING_LIST_INIT_DUP { NULL, 0, 0, 1, NULL } +#define STRING_LIST_INIT_NODUP { 0 } +#define STRING_LIST_INIT_DUP { .strdup_strings = 1 } /* General functions which work with both sorted and unsorted lists. */ /** - * Initialize the members of the string_list, set `strdup_strings` - * member according to the value of the second parameter. + * Initialize the members of a string_list pointer in the same way as + * the corresponding `STRING_LIST_INIT_NODUP` and + * `STRING_LIST_INIT_DUP` macros. + */ +void string_list_init_nodup(struct string_list *list); +void string_list_init_dup(struct string_list *list); + +/** + * TODO remove: For compatibility with any in-flight older API users */ void string_list_init(struct string_list *list, int strdup_strings); @@ -25,7 +25,8 @@ static struct strmap_entry *find_strmap_entry(struct strmap *map, void strmap_init(struct strmap *map) { - strmap_init_with_options(map, NULL, 1); + struct strmap blank = STRMAP_INIT; + memcpy(map, &blank, sizeof(*map)); } void strmap_init_with_options(struct strmap *map, @@ -6,9 +6,8 @@ const char *empty_strvec[] = { NULL }; void strvec_init(struct strvec *array) { - array->v = empty_strvec; - array->nr = 0; - array->alloc = 0; + struct strvec blank = STRVEC_INIT; + memcpy(array, &blank, sizeof(*array)); } static void strvec_push_nodup(struct strvec *array, const char *value) diff --git a/submodule.c b/submodule.c index 0b1d9c1dde..8e611fe1db 100644 --- a/submodule.c +++ b/submodule.c @@ -484,27 +484,14 @@ static void print_submodule_diff_summary(struct repository *r, struct rev_info * strbuf_release(&sb); } -static void prepare_submodule_repo_env_no_git_dir(struct strvec *out) -{ - const char * const *var; - - for (var = local_repo_env; *var; var++) { - if (strcmp(*var, CONFIG_DATA_ENVIRONMENT)) - strvec_push(out, *var); - } -} - void prepare_submodule_repo_env(struct strvec *out) { - prepare_submodule_repo_env_no_git_dir(out); - strvec_pushf(out, "%s=%s", GIT_DIR_ENVIRONMENT, - DEFAULT_GIT_DIR_ENVIRONMENT); + prepare_other_repo_env(out, DEFAULT_GIT_DIR_ENVIRONMENT); } static void prepare_submodule_repo_env_in_gitdir(struct strvec *out) { - prepare_submodule_repo_env_no_git_dir(out); - strvec_pushf(out, "%s=.", GIT_DIR_ENVIRONMENT); + prepare_other_repo_env(out, "."); } /* @@ -1126,6 +1126,12 @@ use these, and "test_set_prereq" for how to define your own. Git wasn't compiled with NO_PTHREADS=YesPlease. + - REFFILES + + Test is specific to packed/loose ref storage, and should be + disabled for other ref storage backends + + Tips for Writing Tests ---------------------- diff --git a/t/helper/test-advise.c b/t/helper/test-advise.c index a7043df1d3..cb881139f7 100644 --- a/t/helper/test-advise.c +++ b/t/helper/test-advise.c @@ -16,7 +16,7 @@ int cmd__advise_if_enabled(int argc, const char **argv) * selected here and in t0018 where this command is being * executed. */ - advise_if_enabled(ADVICE_NESTED_TAG, argv[1]); + advise_if_enabled(ADVICE_NESTED_TAG, "%s", argv[1]); return 0; } diff --git a/t/helper/test-oidtree.c b/t/helper/test-oidtree.c new file mode 100644 index 0000000000..180ee28dd9 --- /dev/null +++ b/t/helper/test-oidtree.c @@ -0,0 +1,49 @@ +#include "test-tool.h" +#include "cache.h" +#include "oidtree.h" + +static enum cb_next print_oid(const struct object_id *oid, void *data) +{ + puts(oid_to_hex(oid)); + return CB_CONTINUE; +} + +int cmd__oidtree(int argc, const char **argv) +{ + struct oidtree ot; + struct strbuf line = STRBUF_INIT; + int nongit_ok; + int algo = GIT_HASH_UNKNOWN; + + oidtree_init(&ot); + setup_git_directory_gently(&nongit_ok); + + while (strbuf_getline(&line, stdin) != EOF) { + const char *arg; + struct object_id oid; + + if (skip_prefix(line.buf, "insert ", &arg)) { + if (get_oid_hex_any(arg, &oid) == GIT_HASH_UNKNOWN) + die("insert not a hexadecimal oid: %s", arg); + algo = oid.algo; + oidtree_insert(&ot, &oid); + } else if (skip_prefix(line.buf, "contains ", &arg)) { + if (get_oid_hex(arg, &oid)) + die("contains not a hexadecimal oid: %s", arg); + printf("%d\n", oidtree_contains(&ot, &oid)); + } else if (skip_prefix(line.buf, "each ", &arg)) { + char buf[GIT_MAX_HEXSZ + 1] = { '0' }; + memset(&oid, 0, sizeof(oid)); + memcpy(buf, arg, strlen(arg)); + buf[hash_algos[algo].hexsz] = '\0'; + get_oid_hex_any(buf, &oid); + oid.algo = algo; + oidtree_each(&ot, &oid, strlen(arg), print_oid, NULL); + } else if (!strcmp(line.buf, "clear")) { + oidtree_clear(&ot); + } else { + die("unknown command: %s", line.buf); + } + } + return 0; +} diff --git a/t/helper/test-partial-clone.c b/t/helper/test-partial-clone.c new file mode 100644 index 0000000000..3f102cfddd --- /dev/null +++ b/t/helper/test-partial-clone.c @@ -0,0 +1,43 @@ +#include "cache.h" +#include "test-tool.h" +#include "repository.h" +#include "object-store.h" + +/* + * Prints the size of the object corresponding to the given hash in a specific + * gitdir. This is similar to "git -C gitdir cat-file -s", except that this + * exercises the code that accesses the object of an arbitrary repository that + * is not the_repository. ("git -C gitdir" makes it so that the_repository is + * the one in gitdir.) + */ +static void object_info(const char *gitdir, const char *oid_hex) +{ + struct repository r; + struct object_id oid; + unsigned long size; + struct object_info oi = {.sizep = &size}; + const char *p; + + if (repo_init(&r, gitdir, NULL)) + die("could not init repo"); + if (parse_oid_hex(oid_hex, &oid, &p)) + die("could not parse oid"); + if (oid_object_info_extended(&r, &oid, &oi, 0)) + die("could not obtain object info"); + printf("%d\n", (int) size); +} + +int cmd__partial_clone(int argc, const char **argv) +{ + setup_git_directory(); + + if (argc < 4) + die("too few arguments"); + + if (!strcmp(argv[1], "object-info")) + object_info(argv[2], argv[3]); + else + die("invalid argument '%s'", argv[1]); + + return 0; +} diff --git a/t/helper/test-pkt-line.c b/t/helper/test-pkt-line.c index 5e638f0b97..c5e052e537 100644 --- a/t/helper/test-pkt-line.c +++ b/t/helper/test-pkt-line.c @@ -26,6 +26,16 @@ static void pack(int argc, const char **argv) } } +static void pack_raw_stdin(void) +{ + struct strbuf sb = STRBUF_INIT; + + if (strbuf_read(&sb, 0, 0) < 0) + die_errno("failed to read from stdin"); + packet_write(1, sb.buf, sb.len); + strbuf_release(&sb); +} + static void unpack(void) { struct packet_reader reader; @@ -110,6 +120,8 @@ int cmd__pkt_line(int argc, const char **argv) if (!strcmp(argv[1], "pack")) pack(argc - 2, argv + 2); + else if (!strcmp(argv[1], "pack-raw-stdin")) + pack_raw_stdin(); else if (!strcmp(argv[1], "unpack")) unpack(); else if (!strcmp(argv[1], "unpack-sideband")) diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c index bba5f841c6..b314b81a45 100644 --- a/t/helper/test-ref-store.c +++ b/t/helper/test-ref-store.c @@ -118,7 +118,7 @@ static int cmd_for_each_ref(struct ref_store *refs, const char **argv) static int cmd_resolve_ref(struct ref_store *refs, const char **argv) { - struct object_id oid; + struct object_id oid = *null_oid(); const char *refname = notnull(*argv++, "refname"); int resolve_flags = arg_flags(*argv++, "resolve-flags"); int flags; diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c index c5bd0c6d4c..490ac026c5 100644 --- a/t/helper/test-tool.c +++ b/t/helper/test-tool.c @@ -43,9 +43,11 @@ static struct test_cmd cmds[] = { { "mktemp", cmd__mktemp }, { "oid-array", cmd__oid_array }, { "oidmap", cmd__oidmap }, + { "oidtree", cmd__oidtree }, { "online-cpus", cmd__online_cpus }, { "parse-options", cmd__parse_options }, { "parse-pathspec-file", cmd__parse_pathspec_file }, + { "partial-clone", cmd__partial_clone }, { "path-utils", cmd__path_utils }, { "pcre2-config", cmd__pcre2_config }, { "pkt-line", cmd__pkt_line }, diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h index e8069a3b22..f8dc266721 100644 --- a/t/helper/test-tool.h +++ b/t/helper/test-tool.h @@ -32,9 +32,11 @@ int cmd__match_trees(int argc, const char **argv); int cmd__mergesort(int argc, const char **argv); int cmd__mktemp(int argc, const char **argv); int cmd__oidmap(int argc, const char **argv); +int cmd__oidtree(int argc, const char **argv); int cmd__online_cpus(int argc, const char **argv); int cmd__parse_options(int argc, const char **argv); int cmd__parse_pathspec_file(int argc, const char** argv); +int cmd__partial_clone(int argc, const char **argv); int cmd__path_utils(int argc, const char **argv); int cmd__pcre2_config(int argc, const char **argv); int cmd__pkt_line(int argc, const char **argv); diff --git a/t/perf/p4209-pickaxe.sh b/t/perf/p4209-pickaxe.sh new file mode 100755 index 0000000000..f585a4465a --- /dev/null +++ b/t/perf/p4209-pickaxe.sh @@ -0,0 +1,70 @@ +#!/bin/sh + +test_description="Test pickaxe performance" + +. ./perf-lib.sh + +test_perf_default_repo + +# Not --max-count, as that's the number of matching commit, so it's +# unbounded. We want to limit our revision walk here. +from_rev_desc= +from_rev= +max_count=1000 +if test_have_prereq EXPENSIVE +then + max_count=10000 +fi +from_rev=" $(git rev-list HEAD | head -n $max_count | tail -n 1).." +from_rev_desc=" <limit-rev>.." + +for icase in \ + '' \ + '-i ' +do + # -S (no regex) + for pattern in \ + 'int main' \ + 'æ' + do + for opts in \ + '-S' + do + test_perf "git log $icase$opts'$pattern'$from_rev_desc" " + git log --pretty=format:%H $icase$opts'$pattern'$from_rev + " + done + done + + # -S (regex) + for pattern in \ + '(int|void|null)' \ + 'if *\([^ ]+ & ' \ + '[àáâãäåæñøùúûüýþ]' + do + for opts in \ + '--pickaxe-regex -S' + do + test_perf "git log $icase$opts'$pattern'$from_rev_desc" " + git log --pretty=format:%H $icase$opts'$pattern'$from_rev + " + done + done + + # -G + for pattern in \ + '(int|void|null)' \ + 'if *\([^ ]+ & ' \ + '[àáâãäåæñøùúûüýþ]' + do + for opts in \ + '-G' + do + test_perf "git log $icase$opts'$pattern'$from_rev_desc" " + git log --pretty=format:%H $icase$opts'$pattern'$from_rev + " + done + done +done + +test_done diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 2c6e34b947..f68c27d864 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -69,6 +69,23 @@ test_expect_success 'success is reported like this' ' _run_sub_test_lib_test_common () { neg="$1" name="$2" descr="$3" # stdin is the body of the test code shift 3 + + # intercept pseudo-options at the front of the argument list that we + # will not pass to child script + skip= + while test $# -gt 0 + do + case "$1" in + --skip=*) + skip=${1#--*=} + shift + ;; + *) + break + ;; + esac + done + mkdir "$name" && ( # Pretend we're not running under a test harness, whether we @@ -91,6 +108,8 @@ _run_sub_test_lib_test_common () { export TEST_DIRECTORY && TEST_OUTPUT_DIRECTORY=$(pwd) && export TEST_OUTPUT_DIRECTORY && + GIT_SKIP_TESTS=$skip && + export GIT_SKIP_TESTS && sane_unset GIT_TEST_FAIL_PREREQS && if test -z "$neg" then @@ -319,9 +338,9 @@ test_expect_success 'test --verbose-only' ' test_expect_success 'GIT_SKIP_TESTS' ' ( - GIT_SKIP_TESTS="git.2" && export GIT_SKIP_TESTS && run_sub_test_lib_test git-skip-tests-basic \ - "GIT_SKIP_TESTS" <<-\EOF && + "GIT_SKIP_TESTS" \ + --skip="git.2" <<-\EOF && for i in 1 2 3 do test_expect_success "passing test #$i" "true" @@ -340,9 +359,9 @@ test_expect_success 'GIT_SKIP_TESTS' ' test_expect_success 'GIT_SKIP_TESTS several tests' ' ( - GIT_SKIP_TESTS="git.2 git.5" && export GIT_SKIP_TESTS && run_sub_test_lib_test git-skip-tests-several \ - "GIT_SKIP_TESTS several tests" <<-\EOF && + "GIT_SKIP_TESTS several tests" \ + --skip="git.2 git.5" <<-\EOF && for i in 1 2 3 4 5 6 do test_expect_success "passing test #$i" "true" @@ -364,9 +383,9 @@ test_expect_success 'GIT_SKIP_TESTS several tests' ' test_expect_success 'GIT_SKIP_TESTS sh pattern' ' ( - GIT_SKIP_TESTS="git.[2-5]" && export GIT_SKIP_TESTS && run_sub_test_lib_test git-skip-tests-sh-pattern \ - "GIT_SKIP_TESTS sh pattern" <<-\EOF && + "GIT_SKIP_TESTS sh pattern" \ + --skip="git.[2-5]" <<-\EOF && for i in 1 2 3 4 5 6 do test_expect_success "passing test #$i" "true" @@ -388,9 +407,9 @@ test_expect_success 'GIT_SKIP_TESTS sh pattern' ' test_expect_success 'GIT_SKIP_TESTS entire suite' ' ( - GIT_SKIP_TESTS="git" && export GIT_SKIP_TESTS && run_sub_test_lib_test git-skip-tests-entire-suite \ - "GIT_SKIP_TESTS entire suite" <<-\EOF && + "GIT_SKIP_TESTS entire suite" \ + --skip="git" <<-\EOF && for i in 1 2 3 do test_expect_success "passing test #$i" "true" @@ -405,9 +424,9 @@ test_expect_success 'GIT_SKIP_TESTS entire suite' ' test_expect_success 'GIT_SKIP_TESTS does not skip unmatched suite' ' ( - GIT_SKIP_TESTS="notgit" && export GIT_SKIP_TESTS && run_sub_test_lib_test git-skip-tests-unmatched-suite \ - "GIT_SKIP_TESTS does not skip unmatched suite" <<-\EOF && + "GIT_SKIP_TESTS does not skip unmatched suite" \ + --skip="notgit" <<-\EOF && for i in 1 2 3 do test_expect_success "passing test #$i" "true" diff --git a/t/t0069-oidtree.sh b/t/t0069-oidtree.sh new file mode 100755 index 0000000000..bfb1397d7b --- /dev/null +++ b/t/t0069-oidtree.sh @@ -0,0 +1,49 @@ +#!/bin/sh + +test_description='basic tests for the oidtree implementation' +. ./test-lib.sh + +maxhexsz=$(test_oid hexsz) +echoid () { + prefix="${1:+$1 }" + shift + while test $# -gt 0 + do + shortoid="$1" + shift + difference=$(($maxhexsz - ${#shortoid})) + printf "%s%s%0${difference}d\\n" "$prefix" "$shortoid" "0" + done +} + +test_expect_success 'oidtree insert and contains' ' + cat >expect <<-\EOF && + 0 + 0 + 0 + 1 + 1 + 0 + EOF + { + echoid insert 444 1 2 3 4 5 a b c d e && + echoid contains 44 441 440 444 4440 4444 + echo clear + } | test-tool oidtree >actual && + test_cmp expect actual +' + +test_expect_success 'oidtree each' ' + echoid "" 123 321 321 >expect && + { + echoid insert f 9 8 123 321 a b c d e + echo each 12300 + echo each 3211 + echo each 3210 + echo each 32100 + echo clear + } | test-tool oidtree >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh index 584a039b85..a211a66c67 100755 --- a/t/t0410-partial-clone.sh +++ b/t/t0410-partial-clone.sh @@ -604,6 +604,29 @@ test_expect_success 'do not fetch when checking existence of tree we construct o git -C repo cherry-pick side1 ' +test_expect_success 'lazy-fetch when accessing object not in the_repository' ' + rm -rf full partial.git && + test_create_repo full && + test_commit -C full create-a-file file.txt && + + test_config -C full uploadpack.allowfilter 1 && + test_config -C full uploadpack.allowanysha1inwant 1 && + git clone --filter=blob:none --bare "file://$(pwd)/full" partial.git && + FILE_HASH=$(git -C full rev-parse HEAD:file.txt) && + + # Sanity check that the file is missing + git -C partial.git rev-list --objects --missing=print HEAD >out && + grep "[?]$FILE_HASH" out && + + git -C full cat-file -s "$FILE_HASH" >expect && + test-tool partial-clone object-info partial.git "$FILE_HASH" >actual && + test_cmp expect actual && + + # Sanity check that the file is now present + git -C partial.git rev-list --objects --missing=print HEAD >out && + ! grep "[?]$FILE_HASH" out +' + . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh index 5d2dc99b74..18b3779ccb 100755 --- a/t/t1006-cat-file.sh +++ b/t/t1006-cat-file.sh @@ -586,4 +586,26 @@ test_expect_success 'cat-file --unordered works' ' test_cmp expect actual ' +test_expect_success 'set up object list for --batch-all-objects tests' ' + git -C all-two cat-file --batch-all-objects --batch-check="%(objectname)" >objects +' + +test_expect_success 'cat-file --batch="%(objectname)" with --batch-all-objects will work' ' + git -C all-two cat-file --batch="%(objectname)" <objects >expect && + git -C all-two cat-file --batch-all-objects --batch="%(objectname)" >actual && + cmp expect actual +' + +test_expect_success 'cat-file --batch="%(rest)" with --batch-all-objects will work' ' + git -C all-two cat-file --batch="%(rest)" <objects >expect && + git -C all-two cat-file --batch-all-objects --batch="%(rest)" >actual && + cmp expect actual +' + +test_expect_success 'cat-file --batch="batman" with --batch-all-objects will work' ' + git -C all-two cat-file --batch="batman" <objects >expect && + git -C all-two cat-file --batch-all-objects --batch="batman" >actual && + cmp expect actual +' + test_done diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh index d028b73eba..cabbd42e33 100755 --- a/t/t1092-sparse-checkout-compatibility.sh +++ b/t/t1092-sparse-checkout-compatibility.sh @@ -17,7 +17,7 @@ test_expect_success 'setup' ' echo "after folder1" >g && echo "after x" >z && mkdir folder1 folder2 deep x && - mkdir deep/deeper1 deep/deeper2 && + mkdir deep/deeper1 deep/deeper2 deep/before deep/later && mkdir deep/deeper1/deepest && echo "after deeper1" >deep/e && echo "after deepest" >deep/deeper1/e && @@ -25,10 +25,23 @@ test_expect_success 'setup' ' cp a folder2 && cp a x && cp a deep && + cp a deep/before && cp a deep/deeper1 && cp a deep/deeper2 && + cp a deep/later && cp a deep/deeper1/deepest && cp -r deep/deeper1/deepest deep/deeper2 && + mkdir deep/deeper1/0 && + mkdir deep/deeper1/0/0 && + touch deep/deeper1/0/1 && + touch deep/deeper1/0/0/0 && + >folder1- && + >folder1.x && + >folder10 && + cp -r deep/deeper1/0 folder1 && + cp -r deep/deeper1/0 folder2 && + echo >>folder1/0/0/0 && + echo >>folder2/0/1 && git add . && git commit -m "initial commit" && git checkout -b base && @@ -40,7 +53,7 @@ test_expect_success 'setup' ' done && git checkout -b rename-base base && - echo >folder1/larger-content <<-\EOF && + cat >folder1/larger-content <<-\EOF && matching lines help @@ -56,11 +69,17 @@ test_expect_success 'setup' ' mv folder1/a folder2/b && mv folder1/larger-content folder2/edited-content && echo >>folder2/edited-content && + echo >>folder2/0/1 && + echo stuff >>deep/deeper1/a && git add . && git commit -m "rename folder1/... to folder2/..." && git checkout -b rename-out-to-in rename-base && mv folder1/a deep/deeper1/b && + echo more stuff >>deep/deeper1/a && + rm folder2/0/1 && + mkdir folder2/0/1 && + echo >>folder2/0/1/1 && mv folder1/larger-content deep/deeper1/edited-content && echo >>deep/deeper1/edited-content && git add . && @@ -68,6 +87,9 @@ test_expect_success 'setup' ' git checkout -b rename-in-to-out rename-base && mv deep/deeper1/a folder1/b && + echo >>folder2/0/1 && + rm -rf folder1/0/0 && + echo >>folder1/0/0 && mv deep/deeper1/larger-content folder1/edited-content && echo >>folder1/edited-content && git add . && @@ -196,6 +218,14 @@ test_expect_success 'status with options' ' test_all_match git status --porcelain=v2 -uno ' +test_expect_success 'status reports sparse-checkout' ' + init_repos && + git -C sparse-checkout status >full && + git -C sparse-index status >sparse && + test_i18ngrep "You are in a sparse checkout with " full && + test_i18ngrep "You are in a sparse checkout." sparse +' + test_expect_success 'add, commit, checkout' ' init_repos && @@ -232,6 +262,44 @@ test_expect_success 'add, commit, checkout' ' test_all_match git checkout - ' +test_expect_success 'status/add: outside sparse cone' ' + init_repos && + + # adding a "missing" file outside the cone should fail + test_sparse_match test_must_fail git add folder1/a && + + # folder1 is at HEAD, but outside the sparse cone + run_on_sparse mkdir folder1 && + cp initial-repo/folder1/a sparse-checkout/folder1/a && + cp initial-repo/folder1/a sparse-index/folder1/a && + + test_sparse_match git status && + + write_script edit-contents <<-\EOF && + echo text >>$1 + EOF + run_on_sparse ../edit-contents folder1/a && + run_on_all ../edit-contents folder1/new && + + test_sparse_match git status --porcelain=v2 && + + # This "git add folder1/a" fails with a warning + # in the sparse repos, differing from the full + # repo. This is intentional. + test_sparse_match test_must_fail git add folder1/a && + test_sparse_match test_must_fail git add --refresh folder1/a && + test_all_match git status --porcelain=v2 && + + test_all_match git add . && + test_all_match git status --porcelain=v2 && + test_all_match git commit -m folder1/new && + + run_on_all ../edit-contents folder1/newer && + test_all_match git add folder1/ && + test_all_match git status --porcelain=v2 && + test_all_match git commit -m folder1/newer +' + test_expect_success 'checkout and reset --hard' ' init_repos && @@ -262,13 +330,29 @@ test_expect_success 'diff --staged' ' test_all_match git diff --staged ' -test_expect_success 'diff with renames' ' +test_expect_success 'diff with renames and conflicts' ' init_repos && for branch in rename-out-to-out rename-out-to-in rename-in-to-out do test_all_match git checkout rename-base && test_all_match git checkout $branch -- . && + test_all_match git status --porcelain=v2 && + test_all_match git diff --staged --no-renames && + test_all_match git diff --staged --find-renames || return 1 + done +' + +test_expect_success 'diff with directory/file conflicts' ' + init_repos && + + for branch in rename-out-to-out rename-out-to-in rename-in-to-out + do + git -C full-checkout reset --hard && + test_sparse_match git reset --hard && + test_all_match git checkout $branch && + test_all_match git checkout rename-base -- . && + test_all_match git status --porcelain=v2 && test_all_match git diff --staged --no-renames && test_all_match git diff --staged --find-renames || return 1 done @@ -308,8 +392,8 @@ test_expect_failure 'blame with pathspec outside sparse definition' ' test_all_match git blame deep/deeper2/deepest/a ' -# TODO: reset currently does not behave as expected when in a -# sparse-checkout. +# NEEDSWORK: a sparse-checkout behaves differently from a full checkout +# in this scenario, but it shouldn't. test_expect_failure 'checkout and reset (mixed)' ' init_repos && @@ -319,8 +403,8 @@ test_expect_failure 'checkout and reset (mixed)' ' test_all_match git reset update-folder2 ' -# Ensure that sparse-index behaves identically to -# sparse-checkout with a full index. +# NEEDSWORK: a sparse-checkout behaves differently from a full checkout +# in this scenario, but it shouldn't. test_expect_success 'checkout and reset (mixed) [sparse]' ' init_repos && @@ -352,6 +436,28 @@ test_expect_success 'merge with outside renames' ' done ' +# Sparse-index fails to convert the index in the +# final 'git cherry-pick' command. +test_expect_success 'cherry-pick with conflicts' ' + init_repos && + + write_script edit-conflict <<-\EOF && + echo $1 >conflict + EOF + + test_all_match git checkout -b to-cherry-pick && + run_on_all ../edit-conflict ABC && + test_all_match git add conflict && + test_all_match git commit -m "conflict to pick" && + + test_all_match git checkout -B base HEAD~1 && + run_on_all ../edit-conflict DEF && + test_all_match git add conflict && + test_all_match git commit -m "conflict in base" && + + test_all_match test_must_fail git cherry-pick to-cherry-pick +' + test_expect_success 'clean' ' init_repos && @@ -405,12 +511,52 @@ test_expect_success 'sparse-index is expanded and converted back' ' GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \ git -C sparse-index -c core.fsmonitor="" reset --hard && test_region index convert_to_sparse trace2.txt && - test_region index ensure_full_index trace2.txt && + test_region index ensure_full_index trace2.txt +' - rm trace2.txt && +test_expect_success 'sparse-index is not expanded' ' + init_repos && + + rm -f trace2.txt && + echo >>sparse-index/untracked.txt && GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \ - git -C sparse-index -c core.fsmonitor="" status -uno && - test_region index ensure_full_index trace2.txt + git -C sparse-index status && + test_region ! index ensure_full_index trace2.txt +' + +# NEEDSWORK: a sparse-checkout behaves differently from a full checkout +# in this scenario, but it shouldn't. +test_expect_success 'reset mixed and checkout orphan' ' + init_repos && + + test_all_match git checkout rename-out-to-in && + + # Sparse checkouts do not agree with full checkouts about + # how to report a directory/file conflict during a reset. + # This command would fail with test_all_match because the + # full checkout reports "T folder1/0/1" while a sparse + # checkout reports "D folder1/0/1". This matches because + # the sparse checkouts skip "adding" the other side of + # the conflict. + test_sparse_match git reset --mixed HEAD~1 && + test_sparse_match test-tool read-cache --table --expand && + test_sparse_match git status --porcelain=v2 && + + # At this point, sparse-checkouts behave differently + # from the full-checkout. + test_sparse_match git checkout --orphan new-branch && + test_sparse_match test-tool read-cache --table --expand && + test_sparse_match git status --porcelain=v2 +' + +test_expect_success 'add everything with deep new file' ' + init_repos && + + run_on_sparse git sparse-checkout set deep/deeper1/deepest && + + run_on_all touch deep/deeper1/x && + test_all_match git add . && + test_all_match git status --porcelain=v2 ' test_done diff --git a/t/t1301-shared-repo.sh b/t/t1301-shared-repo.sh index ac947bff9f..84bf1970d8 100755 --- a/t/t1301-shared-repo.sh +++ b/t/t1301-shared-repo.sh @@ -124,7 +124,7 @@ test_expect_success POSIXPERM 'git reflog expire honors core.sharedRepository' ' : happy ;; *) - echo Ooops, .git/logs/refs/heads/main is not 0662 [$actual] + echo Ooops, .git/logs/refs/heads/main is not 066x [$actual] false ;; esac diff --git a/t/t1401-symbolic-ref.sh b/t/t1401-symbolic-ref.sh index a4ebb0b65f..132a1b885a 100755 --- a/t/t1401-symbolic-ref.sh +++ b/t/t1401-symbolic-ref.sh @@ -7,17 +7,19 @@ test_description='basic symbolic-ref tests' # the git repo, meaning that further tests will operate on # the surrounding git repo instead of the trash directory. reset_to_sane() { - echo ref: refs/heads/foo >.git/HEAD + rm -rf .git && + "$TAR" xf .git.tar } -test_expect_success 'symbolic-ref writes HEAD' ' +test_expect_success 'setup' ' git symbolic-ref HEAD refs/heads/foo && - echo ref: refs/heads/foo >expect && - test_cmp expect .git/HEAD + test_commit file && + "$TAR" cf .git.tar .git/ ' -test_expect_success 'symbolic-ref reads HEAD' ' - echo refs/heads/foo >expect && +test_expect_success 'symbolic-ref read/write roundtrip' ' + git symbolic-ref HEAD refs/heads/read-write-roundtrip && + echo refs/heads/read-write-roundtrip >expect && git symbolic-ref HEAD >actual && test_cmp expect actual ' @@ -25,12 +27,13 @@ test_expect_success 'symbolic-ref reads HEAD' ' test_expect_success 'symbolic-ref refuses non-ref for HEAD' ' test_must_fail git symbolic-ref HEAD foo ' + reset_to_sane test_expect_success 'symbolic-ref refuses bare sha1' ' - echo content >file && git add file && git commit -m one && test_must_fail git symbolic-ref HEAD $(git rev-parse HEAD) ' + reset_to_sane test_expect_success 'HEAD cannot be removed' ' @@ -42,16 +45,16 @@ reset_to_sane test_expect_success 'symbolic-ref can be deleted' ' git symbolic-ref NOTHEAD refs/heads/foo && git symbolic-ref -d NOTHEAD && - test_path_is_file .git/refs/heads/foo && - test_path_is_missing .git/NOTHEAD + git rev-parse refs/heads/foo && + test_must_fail git symbolic-ref NOTHEAD ' reset_to_sane test_expect_success 'symbolic-ref can delete dangling symref' ' git symbolic-ref NOTHEAD refs/heads/missing && git symbolic-ref -d NOTHEAD && - test_path_is_missing .git/refs/heads/missing && - test_path_is_missing .git/NOTHEAD + test_must_fail git rev-parse refs/heads/missing && + test_must_fail git symbolic-ref NOTHEAD ' reset_to_sane diff --git a/t/t1404-update-ref-errors.sh b/t/t1404-update-ref-errors.sh index 8b51c4efc1..b729c1f480 100755 --- a/t/t1404-update-ref-errors.sh +++ b/t/t1404-update-ref-errors.sh @@ -189,7 +189,7 @@ test_expect_success 'one new ref is a simple prefix of another' ' ' -test_expect_success 'empty directory should not fool rev-parse' ' +test_expect_success REFFILES 'empty directory should not fool rev-parse' ' prefix=refs/e-rev-parse && git update-ref $prefix/foo $C && git pack-refs --all && @@ -199,7 +199,7 @@ test_expect_success 'empty directory should not fool rev-parse' ' test_cmp expected actual ' -test_expect_success 'empty directory should not fool for-each-ref' ' +test_expect_success REFFILES 'empty directory should not fool for-each-ref' ' prefix=refs/e-for-each-ref && git update-ref $prefix/foo $C && git for-each-ref $prefix >expected && @@ -209,14 +209,14 @@ test_expect_success 'empty directory should not fool for-each-ref' ' test_cmp expected actual ' -test_expect_success 'empty directory should not fool create' ' +test_expect_success REFFILES 'empty directory should not fool create' ' prefix=refs/e-create && mkdir -p .git/$prefix/foo/bar/baz && printf "create %s $C\n" $prefix/foo | git update-ref --stdin ' -test_expect_success 'empty directory should not fool verify' ' +test_expect_success REFFILES 'empty directory should not fool verify' ' prefix=refs/e-verify && git update-ref $prefix/foo $C && git pack-refs --all && @@ -225,7 +225,7 @@ test_expect_success 'empty directory should not fool verify' ' git update-ref --stdin ' -test_expect_success 'empty directory should not fool 1-arg update' ' +test_expect_success REFFILES 'empty directory should not fool 1-arg update' ' prefix=refs/e-update-1 && git update-ref $prefix/foo $C && git pack-refs --all && @@ -234,7 +234,7 @@ test_expect_success 'empty directory should not fool 1-arg update' ' git update-ref --stdin ' -test_expect_success 'empty directory should not fool 2-arg update' ' +test_expect_success REFFILES 'empty directory should not fool 2-arg update' ' prefix=refs/e-update-2 && git update-ref $prefix/foo $C && git pack-refs --all && @@ -243,7 +243,7 @@ test_expect_success 'empty directory should not fool 2-arg update' ' git update-ref --stdin ' -test_expect_success 'empty directory should not fool 0-arg delete' ' +test_expect_success REFFILES 'empty directory should not fool 0-arg delete' ' prefix=refs/e-delete-0 && git update-ref $prefix/foo $C && git pack-refs --all && @@ -252,7 +252,7 @@ test_expect_success 'empty directory should not fool 0-arg delete' ' git update-ref --stdin ' -test_expect_success 'empty directory should not fool 1-arg delete' ' +test_expect_success REFFILES 'empty directory should not fool 1-arg delete' ' prefix=refs/e-delete-1 && git update-ref $prefix/foo $C && git pack-refs --all && @@ -466,7 +466,7 @@ test_expect_success 'incorrect old value blocks indirect no-deref delete' ' test_cmp expected output.err ' -test_expect_success 'non-empty directory blocks create' ' +test_expect_success REFFILES 'non-empty directory blocks create' ' prefix=refs/ne-create && mkdir -p .git/$prefix/foo/bar && : >.git/$prefix/foo/bar/baz.lock && @@ -485,7 +485,7 @@ test_expect_success 'non-empty directory blocks create' ' test_cmp expected output.err ' -test_expect_success 'broken reference blocks create' ' +test_expect_success REFFILES 'broken reference blocks create' ' prefix=refs/broken-create && mkdir -p .git/$prefix && echo "gobbledigook" >.git/$prefix/foo && @@ -504,7 +504,7 @@ test_expect_success 'broken reference blocks create' ' test_cmp expected output.err ' -test_expect_success 'non-empty directory blocks indirect create' ' +test_expect_success REFFILES 'non-empty directory blocks indirect create' ' prefix=refs/ne-indirect-create && git symbolic-ref $prefix/symref $prefix/foo && mkdir -p .git/$prefix/foo/bar && @@ -524,7 +524,7 @@ test_expect_success 'non-empty directory blocks indirect create' ' test_cmp expected output.err ' -test_expect_success 'broken reference blocks indirect create' ' +test_expect_success REFFILES 'broken reference blocks indirect create' ' prefix=refs/broken-indirect-create && git symbolic-ref $prefix/symref $prefix/foo && echo "gobbledigook" >.git/$prefix/foo && @@ -543,7 +543,7 @@ test_expect_success 'broken reference blocks indirect create' ' test_cmp expected output.err ' -test_expect_success 'no bogus intermediate values during delete' ' +test_expect_success REFFILES 'no bogus intermediate values during delete' ' prefix=refs/slow-transaction && # Set up a reference with differing loose and packed versions: git update-ref $prefix/foo $C && @@ -600,7 +600,7 @@ test_expect_success 'no bogus intermediate values during delete' ' test_must_fail git rev-parse --verify --quiet $prefix/foo ' -test_expect_success 'delete fails cleanly if packed-refs file is locked' ' +test_expect_success REFFILES 'delete fails cleanly if packed-refs file is locked' ' prefix=refs/locked-packed-refs && # Set up a reference with differing loose and packed versions: git update-ref $prefix/foo $C && @@ -616,7 +616,7 @@ test_expect_success 'delete fails cleanly if packed-refs file is locked' ' test_cmp unchanged actual ' -test_expect_success 'delete fails cleanly if packed-refs.new write fails' ' +test_expect_success REFFILES 'delete fails cleanly if packed-refs.new write fails' ' # Setup and expectations are similar to the test above. prefix=refs/failed-packed-refs && git update-ref $prefix/foo $C && diff --git a/t/t1407-worktree-ref-store.sh b/t/t1407-worktree-ref-store.sh index d3fe777511..ad8006c813 100755 --- a/t/t1407-worktree-ref-store.sh +++ b/t/t1407-worktree-ref-store.sh @@ -52,7 +52,14 @@ test_expect_success 'create_symref(FOO, refs/heads/main)' ' test_cmp expected actual ' -test_expect_success 'for_each_reflog()' ' +# Some refs (refs/bisect/*, pseudorefs) are kept per worktree, so they should +# only appear in the for-each-reflog output if it is called from the correct +# worktree, which is exercised in this test. This test is poorly written (and +# therefore marked REFFILES) for mulitple reasons: 1) it creates invalidly +# formatted log entres. 2) it uses direct FS access for creating the reflogs. 3) +# PSEUDO-WT and refs/bisect/random do not create reflogs by default, so it is +# not testing a realistic scenario. +test_expect_success REFFILES 'for_each_reflog()' ' echo $ZERO_OID > .git/logs/PSEUDO-MAIN && mkdir -p .git/logs/refs/bisect && echo $ZERO_OID > .git/logs/refs/bisect/random && diff --git a/t/t1413-reflog-detach.sh b/t/t1413-reflog-detach.sh index bde05208ae..934688a1ee 100755 --- a/t/t1413-reflog-detach.sh +++ b/t/t1413-reflog-detach.sh @@ -7,8 +7,7 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh reset_state () { - git checkout main && - cp saved_reflog .git/logs/HEAD + rm -rf .git && "$TAR" xf .git-saved.tar } test_expect_success setup ' @@ -17,7 +16,7 @@ test_expect_success setup ' git branch side && test_tick && git commit --allow-empty -m second && - cat .git/logs/HEAD >saved_reflog + "$TAR" cf .git-saved.tar .git ' test_expect_success baseline ' diff --git a/t/t1414-reflog-walk.sh b/t/t1414-reflog-walk.sh index 80d94704d0..ea64cecf47 100755 --- a/t/t1414-reflog-walk.sh +++ b/t/t1414-reflog-walk.sh @@ -119,7 +119,9 @@ test_expect_success 'min/max age uses entry date to limit' ' test_cmp expect actual ' -test_expect_success 'walk prefers reflog to ref tip' ' +# Create a situation where the reflog and ref database disagree about the latest +# state of HEAD. +test_expect_success REFFILES 'walk prefers reflog to ref tip' ' head=$(git rev-parse HEAD) && one=$(git rev-parse one) && ident="$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE" && diff --git a/t/t1415-worktree-refs.sh b/t/t1415-worktree-refs.sh index 7ab91241ab..a3e6ea0808 100755 --- a/t/t1415-worktree-refs.sh +++ b/t/t1415-worktree-refs.sh @@ -16,7 +16,10 @@ test_expect_success 'setup' ' git -C wt2 update-ref refs/worktree/foo HEAD ' -test_expect_success 'refs/worktree must not be packed' ' +# The 'packed-refs' file is stored directly in .git/. This means it is global +# to the repository, and can only contain refs that are shared across all +# worktrees. +test_expect_success REFFILES 'refs/worktree must not be packed' ' git pack-refs --all && test_path_is_missing .git/refs/tags/wt1 && test_path_is_file .git/refs/worktree/foo && @@ -37,9 +40,8 @@ test_expect_success 'resolve main-worktree/HEAD' ' ' test_expect_success 'ambiguous main-worktree/HEAD' ' - mkdir -p .git/refs/heads/main-worktree && - test_when_finished rm -f .git/refs/heads/main-worktree/HEAD && - cp .git/HEAD .git/refs/heads/main-worktree/HEAD && + test_when_finished git update-ref -d refs/heads/main-worktree/HEAD && + git update-ref refs/heads/main-worktree/HEAD $(git rev-parse HEAD) && git rev-parse main-worktree/HEAD 2>warn && grep "main-worktree/HEAD.*ambiguous" warn ' @@ -51,9 +53,8 @@ test_expect_success 'resolve worktrees/xx/HEAD' ' ' test_expect_success 'ambiguous worktrees/xx/HEAD' ' - mkdir -p .git/refs/heads/worktrees/wt1 && - test_when_finished rm -f .git/refs/heads/worktrees/wt1/HEAD && - cp .git/HEAD .git/refs/heads/worktrees/wt1/HEAD && + git update-ref refs/heads/worktrees/wt1/HEAD $(git rev-parse HEAD) && + test_when_finished git update-ref -d refs/heads/worktrees/wt1/HEAD && git rev-parse worktrees/wt1/HEAD 2>warn && grep "worktrees/wt1/HEAD.*ambiguous" warn ' diff --git a/t/t2017-checkout-orphan.sh b/t/t2017-checkout-orphan.sh index c7adbdd39a..88d6992a5e 100755 --- a/t/t2017-checkout-orphan.sh +++ b/t/t2017-checkout-orphan.sh @@ -76,7 +76,7 @@ test_expect_success '--orphan makes reflog by default' ' git rev-parse --verify delta@{0} ' -test_expect_success '--orphan does not make reflog when core.logAllRefUpdates = false' ' +test_expect_success REFFILES '--orphan does not make reflog when core.logAllRefUpdates = false' ' git checkout main && git config core.logAllRefUpdates false && git checkout --orphan epsilon && diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index 96dfca1554..37ad79470f 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh @@ -67,11 +67,25 @@ test_expect_success '"add" worktree' ' ' test_expect_success '"add" worktree with lock' ' - git rev-parse HEAD >expect && git worktree add --detach --lock here-with-lock main && + test_when_finished "git worktree unlock here-with-lock || :" && test -f .git/worktrees/here-with-lock/locked ' +test_expect_success '"add" worktree with lock and reason' ' + lock_reason="why not" && + git worktree add --detach --lock --reason "$lock_reason" here-with-lock-reason main && + test_when_finished "git worktree unlock here-with-lock-reason || :" && + test -f .git/worktrees/here-with-lock-reason/locked && + echo "$lock_reason" >expect && + test_cmp expect .git/worktrees/here-with-lock-reason/locked +' + +test_expect_success '"add" worktree with reason but no lock' ' + test_must_fail git worktree add --detach --reason "why not" here-with-reason-only main && + test_path_is_missing .git/worktrees/here-with-reason-only/locked +' + test_expect_success '"add" worktree from a subdir' ' ( mkdir sub && diff --git a/t/t3202-show-branch-octopus.sh b/t/t3202-show-branch-octopus.sh deleted file mode 100755 index 5cb0126cfe..0000000000 --- a/t/t3202-show-branch-octopus.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/sh - -test_description='test show-branch with more than 8 heads' - -GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main -export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME - -. ./test-lib.sh - -numbers="1 2 3 4 5 6 7 8 9 10" - -test_expect_success 'setup' ' - - > file && - git add file && - test_tick && - git commit -m initial && - - for i in $numbers - do - git checkout -b branch$i main && - > file$i && - git add file$i && - test_tick && - git commit -m branch$i || return 1 - done - -' - -cat > expect << EOF -! [branch1] branch1 - ! [branch2] branch2 - ! [branch3] branch3 - ! [branch4] branch4 - ! [branch5] branch5 - ! [branch6] branch6 - ! [branch7] branch7 - ! [branch8] branch8 - ! [branch9] branch9 - * [branch10] branch10 ----------- - * [branch10] branch10 - + [branch9] branch9 - + [branch8] branch8 - + [branch7] branch7 - + [branch6] branch6 - + [branch5] branch5 - + [branch4] branch4 - + [branch3] branch3 - + [branch2] branch2 -+ [branch1] branch1 -+++++++++* [branch10^] initial -EOF - -test_expect_success 'show-branch with more than 8 branches' ' - - git show-branch $(for i in $numbers; do echo branch$i; done) > out && - test_cmp expect out - -' - -test_expect_success 'show-branch with showbranch.default' ' - for i in $numbers; do - git config --add showbranch.default branch$i - done && - git show-branch >out && - test_cmp expect out -' - -test_done diff --git a/t/t3202-show-branch.sh b/t/t3202-show-branch.sh new file mode 100755 index 0000000000..ad9902a06b --- /dev/null +++ b/t/t3202-show-branch.sh @@ -0,0 +1,149 @@ +#!/bin/sh + +test_description='test show-branch' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit initial && + for i in $(test_seq 1 10) + do + git checkout -b branch$i initial && + test_commit --no-tag branch$i + done && + git for-each-ref \ + --sort=version:refname \ + --format="%(refname:strip=2)" \ + "refs/heads/branch*" >branches.sorted && + sed "s/^> //" >expect <<-\EOF + > ! [branch1] branch1 + > ! [branch2] branch2 + > ! [branch3] branch3 + > ! [branch4] branch4 + > ! [branch5] branch5 + > ! [branch6] branch6 + > ! [branch7] branch7 + > ! [branch8] branch8 + > ! [branch9] branch9 + > * [branch10] branch10 + > ---------- + > * [branch10] branch10 + > + [branch9] branch9 + > + [branch8] branch8 + > + [branch7] branch7 + > + [branch6] branch6 + > + [branch5] branch5 + > + [branch4] branch4 + > + [branch3] branch3 + > + [branch2] branch2 + > + [branch1] branch1 + > +++++++++* [branch10^] initial + EOF +' + +test_expect_success 'show-branch with more than 8 branches' ' + git show-branch $(cat branches.sorted) >actual && + test_cmp expect actual +' + +test_expect_success 'show-branch with showbranch.default' ' + for branch in $(cat branches.sorted) + do + test_config showbranch.default $branch --add + done && + git show-branch >actual && + test_cmp expect actual +' + +test_expect_success 'show-branch --color output' ' + sed "s/^> //" >expect <<-\EOF && + > <RED>!<RESET> [branch1] branch1 + > <GREEN>!<RESET> [branch2] branch2 + > <YELLOW>!<RESET> [branch3] branch3 + > <BLUE>!<RESET> [branch4] branch4 + > <MAGENTA>!<RESET> [branch5] branch5 + > <CYAN>!<RESET> [branch6] branch6 + > <BOLD;RED>!<RESET> [branch7] branch7 + > <BOLD;GREEN>!<RESET> [branch8] branch8 + > <BOLD;YELLOW>!<RESET> [branch9] branch9 + > <BOLD;BLUE>*<RESET> [branch10] branch10 + > ---------- + > <BOLD;BLUE>*<RESET> [branch10] branch10 + > <BOLD;YELLOW>+<RESET> [branch9] branch9 + > <BOLD;GREEN>+<RESET> [branch8] branch8 + > <BOLD;RED>+<RESET> [branch7] branch7 + > <CYAN>+<RESET> [branch6] branch6 + > <MAGENTA>+<RESET> [branch5] branch5 + > <BLUE>+<RESET> [branch4] branch4 + > <YELLOW>+<RESET> [branch3] branch3 + > <GREEN>+<RESET> [branch2] branch2 + > <RED>+<RESET> [branch1] branch1 + > <RED>+<RESET><GREEN>+<RESET><YELLOW>+<RESET><BLUE>+<RESET><MAGENTA>+<RESET><CYAN>+<RESET><BOLD;RED>+<RESET><BOLD;GREEN>+<RESET><BOLD;YELLOW>+<RESET><BOLD;BLUE>*<RESET> [branch10^] initial + EOF + git show-branch --color=always $(cat branches.sorted) >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expect actual +' + +test_expect_success 'show branch --remotes' ' + cat >expect.err <<-\EOF && + No revs to be shown. + EOF + git show-branch -r 2>actual.err >actual.out && + test_cmp expect.err actual.err && + test_must_be_empty actual.out +' + +test_expect_success 'setup show branch --list' ' + sed "s/^> //" >expect <<-\EOF + > [branch1] branch1 + > [branch2] branch2 + > [branch3] branch3 + > [branch4] branch4 + > [branch5] branch5 + > [branch6] branch6 + > [branch7] branch7 + > [branch8] branch8 + > [branch9] branch9 + > * [branch10] branch10 + EOF +' + +test_expect_success 'show branch --list' ' + git show-branch --list $(cat branches.sorted) >actual && + test_cmp expect actual +' + +test_expect_success 'show branch --list has no --color output' ' + git show-branch --color=always --list $(cat branches.sorted) >actual && + test_cmp expect actual +' + +test_expect_success 'show branch --merge-base with one argument' ' + for branch in $(cat branches.sorted) + do + git rev-parse $branch >expect && + git show-branch --merge-base $branch >actual && + test_cmp expect actual + done +' + +test_expect_success 'show branch --merge-base with two arguments' ' + for branch in $(cat branches.sorted) + do + git rev-parse initial >expect && + git show-branch --merge-base initial $branch >actual && + test_cmp expect actual + done +' + +test_expect_success 'show branch --merge-base with N arguments' ' + git rev-parse initial >expect && + git show-branch --merge-base $(cat branches.sorted) >actual && + test_cmp expect actual && + + git merge-base $(cat branches.sorted) >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t3800-mktag.sh b/t/t3800-mktag.sh index 6275c98523..0544d58a6e 100755 --- a/t/t3800-mktag.sh +++ b/t/t3800-mktag.sh @@ -12,15 +12,93 @@ test_description='git mktag: tag object verify test' # given in the expect.pat file. check_verify_failure () { - test_expect_success "$1" " - test_must_fail git mktag <tag.sig 2>message && - grep '$2' message && - if test '$3' != '--no-strict' + subject=$1 && + message=$2 && + shift 2 && + + no_strict= && + fsck_obj_ok= && + no_strict= && + while test $# != 0 + do + case "$1" in + --no-strict) + no_strict=yes + ;; + --fsck-obj-ok) + fsck_obj_ok=yes + ;; + esac && + shift + done && + + test_expect_success "fail with [--[no-]strict]: $subject" ' + test_must_fail git mktag <tag.sig 2>err && + if test -z "$no_strict" then - test_must_fail git mktag --no-strict <tag.sig 2>message.no-strict && - grep '$2' message.no-strict + test_must_fail git mktag <tag.sig 2>err2 && + test_cmp err err2 + else + git mktag --no-strict <tag.sig fi - " + ' + + test_expect_success "setup: $subject" ' + tag_ref=refs/tags/bad_tag && + + # Reset any leftover state from the last $subject + rm -rf bad-tag && + + git init --bare bad-tag && + bad_tag=$(git -C bad-tag hash-object -t tag -w --stdin --literally <tag.sig) + ' + + test_expect_success "hash-object & fsck unreachable: $subject" ' + if test -n "$fsck_obj_ok" + then + git -C bad-tag fsck + else + test_must_fail git -C bad-tag fsck + fi + ' + + test_expect_success "update-ref & fsck reachable: $subject" ' + # Make sure the earlier test created it for us + git rev-parse "$bad_tag" && + + # The update-ref of the bad content will fail, do it + # anyway to see if it segfaults + test_might_fail git -C bad-tag update-ref "$tag_ref" "$bad_tag" && + + # Manually create the broken, we cannot do it with + # update-ref + echo "$bad_tag" >"bad-tag/$tag_ref" && + + # Unlike fsck-ing unreachable content above, this + # will always fail. + test_must_fail git -C bad-tag fsck + ' + + test_expect_success "for-each-ref: $subject" ' + # Make sure the earlier test created it for us + git rev-parse "$bad_tag" && + + echo "$bad_tag" >"bad-tag/$tag_ref" && + + printf "%s tag\t%s\n" "$bad_tag" "$tag_ref" >expected && + git -C bad-tag for-each-ref "$tag_ref" >actual && + test_cmp expected actual && + + test_must_fail git -C bad-tag for-each-ref --format="%(*objectname)" + ' + + test_expect_success "fast-export & fast-import: $subject" ' + # Make sure the earlier test created it for us + git rev-parse "$bad_tag" && + + test_must_fail git -C bad-tag fast-export --all && + test_must_fail git -C bad-tag fast-export "$bad_tag" + ' } test_expect_mktag_success() { @@ -167,7 +245,8 @@ tagger . <> 0 +0000 EOF check_verify_failure 'verify object (hash/type) check -- correct type, nonexisting object' \ - '^fatal: could not read tagged object' + '^fatal: could not read tagged object' \ + --fsck-obj-ok cat >tag.sig <<EOF object $head @@ -200,7 +279,8 @@ tagger . <> 0 +0000 EOF check_verify_failure 'verify object (hash/type) check -- mismatched type, valid object' \ - '^fatal: object.*tagged as.*tree.*but is.*commit' + '^fatal: object.*tagged as.*tree.*but is.*commit' \ + --fsck-obj-ok ############################################################ # 9.5. verify object (hash/type) check -- replacement @@ -229,7 +309,8 @@ tagger . <> 0 +0000 EOF check_verify_failure 'verify object (hash/type) check -- mismatched type, valid object' \ - '^fatal: object.*tagged as.*tree.*but is.*blob' + '^fatal: object.*tagged as.*tree.*but is.*blob' \ + --fsck-obj-ok ############################################################ # 10. verify tag-name check @@ -243,7 +324,9 @@ tagger . <> 0 +0000 EOF check_verify_failure 'verify tag-name check' \ - '^error:.* badTagName:' '--no-strict' + '^error:.* badTagName:' \ + --no-strict \ + --fsck-obj-ok ############################################################ # 11. tagger line label check #1 @@ -257,7 +340,9 @@ This is filler EOF check_verify_failure '"tagger" line label check #1' \ - '^error:.* missingTaggerEntry:' '--no-strict' + '^error:.* missingTaggerEntry:' \ + --no-strict \ + --fsck-obj-ok ############################################################ # 12. tagger line label check #2 @@ -272,7 +357,9 @@ This is filler EOF check_verify_failure '"tagger" line label check #2' \ - '^error:.* missingTaggerEntry:' '--no-strict' + '^error:.* missingTaggerEntry:' \ + --no-strict \ + --fsck-obj-ok ############################################################ # 13. allow missing tag author name like fsck @@ -301,7 +388,9 @@ tagger T A Gger < EOF check_verify_failure 'disallow malformed tagger' \ - '^error:.* badEmail:' '--no-strict' + '^error:.* badEmail:' \ + --no-strict \ + --fsck-obj-ok ############################################################ # 15. allow empty tag email @@ -425,7 +514,9 @@ this line should not be here EOF check_verify_failure 'detect invalid header entry' \ - '^error:.* extraHeaderEntry:' '--no-strict' + '^error:.* extraHeaderEntry:' \ + --no-strict \ + --fsck-obj-ok test_expect_success 'invalid header entry config & fsck' ' test_must_fail git mktag <tag.sig && diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 350cfa3593..9dfead936b 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -1834,14 +1834,24 @@ test_expect_success 'log --graph --no-walk is forbidden' ' test_must_fail git log --graph --no-walk ' -test_expect_success 'log diagnoses bogus HEAD' ' +test_expect_success 'log on empty repo fails' ' git init empty && + test_when_finished "rm -rf empty" && test_must_fail git -C empty log 2>stderr && - test_i18ngrep does.not.have.any.commits stderr && + test_i18ngrep does.not.have.any.commits stderr +' + +test_expect_success REFFILES 'log diagnoses bogus HEAD hash' ' + git init empty && + test_when_finished "rm -rf empty" && echo 1234abcd >empty/.git/refs/heads/main && test_must_fail git -C empty log 2>stderr && - test_i18ngrep broken stderr && - echo "ref: refs/heads/invalid.lock" >empty/.git/HEAD && + test_i18ngrep broken stderr +' + +test_expect_success 'log diagnoses bogus HEAD symref' ' + git init empty && + git --git-dir empty/.git symbolic-ref HEAD refs/heads/invalid.lock && test_must_fail git -C empty log 2>stderr && test_i18ngrep broken stderr && test_must_fail git -C empty log --default totally-bogus 2>stderr && @@ -1905,6 +1915,20 @@ test_expect_success '--exclude-promisor-objects does not BUG-crash' ' test_must_fail git log --exclude-promisor-objects source-a ' +test_expect_success 'log --decorate includes all levels of tag annotated tags' ' + git checkout -b branch && + git commit --allow-empty -m "new commit" && + git tag lightweight HEAD && + git tag -m annotated annotated HEAD && + git tag -m double-0 double-0 HEAD && + git tag -m double-1 double-1 double-0 && + cat >expect <<-\EOF && + HEAD -> branch, tag: lightweight, tag: double-1, tag: double-0, tag: annotated + EOF + git log -1 --format="%D" >actual && + test_cmp expect actual +' + test_expect_success 'log --end-of-options' ' git update-ref refs/heads/--source HEAD && git log --end-of-options --source >actual && diff --git a/t/t4209-log-pickaxe.sh b/t/t4209-log-pickaxe.sh index 5d06f5f45e..75795d0b49 100755 --- a/t/t4209-log-pickaxe.sh +++ b/t/t4209-log-pickaxe.sh @@ -55,6 +55,43 @@ test_expect_success setup ' git rev-parse --verify HEAD >expect_second ' +test_expect_success 'usage' ' + test_expect_code 129 git log -S 2>err && + test_i18ngrep "switch.*requires a value" err && + + test_expect_code 129 git log -G 2>err && + test_i18ngrep "switch.*requires a value" err && + + test_expect_code 128 git log -Gregex -Sstring 2>err && + grep "mutually exclusive" err && + + test_expect_code 128 git log -Gregex --find-object=HEAD 2>err && + grep "mutually exclusive" err && + + test_expect_code 128 git log -Sstring --find-object=HEAD 2>err && + grep "mutually exclusive" err && + + test_expect_code 128 git log --pickaxe-all --find-object=HEAD 2>err && + grep "mutually exclusive" err +' + +test_expect_success 'usage: --pickaxe-regex' ' + test_expect_code 128 git log -Gregex --pickaxe-regex 2>err && + grep "mutually exclusive" err +' + +test_expect_success 'usage: --no-pickaxe-regex' ' + cat >expect <<-\EOF && + fatal: unrecognized argument: --no-pickaxe-regex + EOF + + test_expect_code 128 git log -Sstring --no-pickaxe-regex 2>actual && + test_cmp expect actual && + + test_expect_code 128 git log -Gstring --no-pickaxe-regex 2>err && + test_cmp expect actual +' + test_log expect_initial --grep initial test_log expect_nomatch --grep InItial test_log_icase expect_initial --grep InItial @@ -106,38 +143,83 @@ test_expect_success 'log -S --no-textconv (missing textconv tool)' ' rm .gitattributes ' +test_expect_success 'setup log -[GS] plain & regex' ' + test_create_repo GS-plain && + test_commit -C GS-plain --append A data.txt "a" && + test_commit -C GS-plain --append B data.txt "a a" && + test_commit -C GS-plain --append C data.txt "b" && + test_commit -C GS-plain --append D data.txt "[b]" && + test_commit -C GS-plain E data.txt "" && + + # We also include E, the deletion commit + git -C GS-plain log --grep="[ABE]" >A-to-B-then-E-log && + git -C GS-plain log --grep="[CDE]" >C-to-D-then-E-log && + git -C GS-plain log --grep="[DE]" >D-then-E-log && + git -C GS-plain log >full-log +' + +test_expect_success 'log -G trims diff new/old [-+]' ' + git -C GS-plain log -G"[+-]a" >log && + test_must_be_empty log && + git -C GS-plain log -G"^a" >log && + test_cmp log A-to-B-then-E-log +' + +test_expect_success 'log -S<pat> is not a regex, but -S<pat> --pickaxe-regex is' ' + git -C GS-plain log -S"a" >log && + test_cmp log A-to-B-then-E-log && + + git -C GS-plain log -S"[a]" >log && + test_must_be_empty log && + + git -C GS-plain log -S"[a]" --pickaxe-regex >log && + test_cmp log A-to-B-then-E-log && + + git -C GS-plain log -S"[b]" >log && + test_cmp log D-then-E-log && + + git -C GS-plain log -S"[b]" --pickaxe-regex >log && + test_cmp log C-to-D-then-E-log +' + test_expect_success 'setup log -[GS] binary & --text' ' - git checkout --orphan GS-binary-and-text && - git read-tree --empty && - printf "a\na\0a\n" >data.bin && - git add data.bin && - git commit -m "create binary file" data.bin && - printf "a\na\0a\n" >>data.bin && - git commit -m "modify binary file" data.bin && - git rm data.bin && - git commit -m "delete binary file" data.bin && - git log >full-log + test_create_repo GS-bin-txt && + test_commit -C GS-bin-txt --printf A data.bin "a\na\0a\n" && + test_commit -C GS-bin-txt --append --printf B data.bin "a\na\0a\n" && + test_commit -C GS-bin-txt C data.bin "" && + git -C GS-bin-txt log >full-log ' test_expect_success 'log -G ignores binary files' ' - git log -Ga >log && + git -C GS-bin-txt log -Ga >log && test_must_be_empty log ' test_expect_success 'log -G looks into binary files with -a' ' - git log -a -Ga >log && + git -C GS-bin-txt log -a -Ga >log && test_cmp log full-log ' test_expect_success 'log -G looks into binary files with textconv filter' ' - test_when_finished "rm .gitattributes" && - echo "* diff=bin" >.gitattributes && - git -c diff.bin.textconv=cat log -Ga >log && + test_when_finished "rm GS-bin-txt/.gitattributes" && + ( + cd GS-bin-txt && + echo "* diff=bin" >.gitattributes && + git -c diff.bin.textconv=cat log -Ga >../log + ) && test_cmp log full-log ' test_expect_success 'log -S looks into binary files' ' - git log -Sa >log && + git -C GS-bin-txt log -Sa >log && + test_cmp log full-log +' + +test_expect_success 'log -S --pickaxe-regex looks into binary files' ' + git -C GS-bin-txt log --pickaxe-regex -Sa >log && + test_cmp log full-log && + + git -C GS-bin-txt log --pickaxe-regex -S"[a]" >log && test_cmp log full-log ' diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index 7204799a0b..2c88d1c159 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -111,25 +111,34 @@ test_expect_success 'setup' ' EOF ' -test_expect_success \ - 'populate workdir' \ - 'mkdir a && - echo simple textfile >a/a && - ten=0123456789 && hundred=$ten$ten$ten$ten$ten$ten$ten$ten$ten$ten && - echo long filename >a/four$hundred && - mkdir a/bin && - test-tool genrandom "frotz" 500000 >a/bin/sh && - printf "A\$Format:%s\$O" "$SUBSTFORMAT" >a/substfile1 && - printf "A not substituted O" >a/substfile2 && - if test_have_prereq SYMLINKS; then - ln -s a a/l1 - else - printf %s a > a/l1 - fi && - (p=long_path_to_a_file && cd a && - for depth in 1 2 3 4 5; do mkdir $p && cd $p; done && - echo text >file_with_long_path) && - (cd a && find .) | sort >a.lst' +test_expect_success 'populate workdir' ' + mkdir a && + echo simple textfile >a/a && + ten=0123456789 && + hundred="$ten$ten$ten$ten$ten$ten$ten$ten$ten$ten" && + echo long filename >"a/four$hundred" && + mkdir a/bin && + test-tool genrandom "frotz" 500000 >a/bin/sh && + printf "A\$Format:%s\$O" "$SUBSTFORMAT" >a/substfile1 && + printf "A not substituted O" >a/substfile2 && + if test_have_prereq SYMLINKS + then + ln -s a a/l1 + else + printf %s a >a/l1 + fi && + ( + p=long_path_to_a_file && + cd a && + for depth in 1 2 3 4 5 + do + mkdir $p && + cd $p + done && + echo text >file_with_long_path + ) && + (cd a && find .) | sort >a.lst +' test_expect_success \ 'add ignored file' \ @@ -147,18 +156,18 @@ test_expect_success 'setup export-subst' ' >a/substfile1 ' -test_expect_success \ - 'create bare clone' \ - 'git clone --bare . bare.git && - cp .git/info/attributes bare.git/info/attributes' +test_expect_success 'create bare clone' ' + git clone --bare . bare.git && + cp .git/info/attributes bare.git/info/attributes +' -test_expect_success \ - 'remove ignored file' \ - 'rm a/ignored' +test_expect_success 'remove ignored file' ' + rm a/ignored +' -test_expect_success \ - 'git archive' \ - 'git archive HEAD >b.tar' +test_expect_success 'git archive' ' + git archive HEAD >b.tar +' check_tar b @@ -194,26 +203,28 @@ check_added with_untracked2 untracked one/untracked check_added with_untracked2 untracked two/untracked test_expect_success 'git archive on large files' ' - test_config core.bigfilethreshold 1 && - git archive HEAD >b3.tar && - test_cmp_bin b.tar b3.tar + test_config core.bigfilethreshold 1 && + git archive HEAD >b3.tar && + test_cmp_bin b.tar b3.tar ' -test_expect_success \ - 'git archive in a bare repo' \ - '(cd bare.git && git archive HEAD) >b3.tar' +test_expect_success 'git archive in a bare repo' ' + git --git-dir bare.git archive HEAD >b3.tar +' -test_expect_success \ - 'git archive vs. the same in a bare repo' \ - 'test_cmp_bin b.tar b3.tar' +test_expect_success 'git archive vs. the same in a bare repo' ' + test_cmp_bin b.tar b3.tar +' -test_expect_success 'git archive with --output' \ - 'git archive --output=b4.tar HEAD && - test_cmp_bin b.tar b4.tar' +test_expect_success 'git archive with --output' ' + git archive --output=b4.tar HEAD && + test_cmp_bin b.tar b4.tar +' -test_expect_success 'git archive --remote' \ - 'git archive --remote=. HEAD >b5.tar && - test_cmp_bin b.tar b5.tar' +test_expect_success 'git archive --remote' ' + git archive --remote=. HEAD >b5.tar && + test_cmp_bin b.tar b5.tar +' test_expect_success 'git archive --remote with configured remote' ' git config remote.foo.url . && @@ -224,18 +235,19 @@ test_expect_success 'git archive --remote with configured remote' ' test_cmp_bin b.tar b5-nick.tar ' -test_expect_success \ - 'validate file modification time' \ - 'mkdir extract && - "$TAR" xf b.tar -C extract a/a && - test-tool chmtime --get extract/a/a >b.mtime && - echo "1117231200" >expected.mtime && - test_cmp expected.mtime b.mtime' +test_expect_success 'validate file modification time' ' + mkdir extract && + "$TAR" xf b.tar -C extract a/a && + test-tool chmtime --get extract/a/a >b.mtime && + echo "1117231200" >expected.mtime && + test_cmp expected.mtime b.mtime +' -test_expect_success \ - 'git get-tar-commit-id' \ - 'git get-tar-commit-id <b.tar >b.commitid && - test_cmp .git/$(git symbolic-ref HEAD) b.commitid' +test_expect_success 'git get-tar-commit-id' ' + git get-tar-commit-id <b.tar >actual && + git rev-parse HEAD >expect && + test_cmp expect actual +' test_expect_success 'git archive with --output, override inferred format' ' git archive --format=tar --output=d4.zip HEAD && diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh index 3475b06aeb..7cabb85ca6 100755 --- a/t/t5304-prune.sh +++ b/t/t5304-prune.sh @@ -22,30 +22,25 @@ add_blob() { } test_expect_success setup ' - - : > file && + >file && git add file && test_tick && git commit -m initial && git gc - ' test_expect_success 'prune stale packs' ' - orig_pack=$(echo .git/objects/pack/*.pack) && - : > .git/objects/tmp_1.pack && - : > .git/objects/tmp_2.pack && + >.git/objects/tmp_1.pack && + >.git/objects/tmp_2.pack && test-tool chmtime =-86501 .git/objects/tmp_1.pack && git prune --expire 1.day && test_path_is_file $orig_pack && test_path_is_file .git/objects/tmp_2.pack && test_path_is_missing .git/objects/tmp_1.pack - ' test_expect_success 'prune --expire' ' - add_blob && git prune --expire=1.hour.ago && verbose test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && @@ -54,11 +49,9 @@ test_expect_success 'prune --expire' ' git prune --expire 1.day && verbose test $before = $(git count-objects | sed "s/ .*//") && test_path_is_missing $BLOB_FILE - ' test_expect_success 'gc: implicit prune --expire' ' - add_blob && test-tool chmtime =-$((2*$week-30)) $BLOB_FILE && git gc && @@ -68,123 +61,98 @@ test_expect_success 'gc: implicit prune --expire' ' git gc && verbose test $before = $(git count-objects | sed "s/ .*//") && test_path_is_missing $BLOB_FILE - ' test_expect_success 'gc: refuse to start with invalid gc.pruneExpire' ' - git config gc.pruneExpire invalid && test_must_fail git gc - ' test_expect_success 'gc: start with ok gc.pruneExpire' ' - git config gc.pruneExpire 2.days.ago && git gc - ' test_expect_success 'prune: prune nonsense parameters' ' - test_must_fail git prune garbage && test_must_fail git prune --- && test_must_fail git prune --no-such-option - ' test_expect_success 'prune: prune unreachable heads' ' - git config core.logAllRefUpdates false && - mv .git/logs .git/logs.old && - : > file2 && + >file2 && git add file2 && git commit -m temporary && tmp_head=$(git rev-list -1 HEAD) && git reset HEAD^ && + git reflog expire --all && git prune && test_must_fail git reset $tmp_head -- - ' test_expect_success 'prune: do not prune detached HEAD with no reflog' ' - git checkout --detach --quiet && git commit --allow-empty -m "detached commit" && - # verify that there is no reflogs - # (should be removed and disabled by previous test) - test_path_is_missing .git/logs && + git reflog expire --all && git prune -n >prune_actual && test_must_be_empty prune_actual - ' test_expect_success 'prune: prune former HEAD after checking out branch' ' - head_oid=$(git rev-parse HEAD) && git checkout --quiet main && + git reflog expire --all && git prune -v >prune_actual && grep "$head_oid" prune_actual - ' test_expect_success 'prune: do not prune heads listed as an argument' ' - - : > file2 && + >file2 && git add file2 && git commit -m temporary && tmp_head=$(git rev-list -1 HEAD) && git reset HEAD^ && git prune -- $tmp_head && git reset $tmp_head -- - ' test_expect_success 'gc --no-prune' ' - add_blob && test-tool chmtime =-$((5001*$day)) $BLOB_FILE && git config gc.pruneExpire 2.days.ago && git gc --no-prune && verbose test 1 = $(git count-objects | sed "s/ .*//") && test_path_is_file $BLOB_FILE - ' test_expect_success 'gc respects gc.pruneExpire' ' - git config gc.pruneExpire 5002.days.ago && git gc && test_path_is_file $BLOB_FILE && git config gc.pruneExpire 5000.days.ago && git gc && test_path_is_missing $BLOB_FILE - ' test_expect_success 'gc --prune=<date>' ' - add_blob && test-tool chmtime =-$((5001*$day)) $BLOB_FILE && git gc --prune=5002.days.ago && test_path_is_file $BLOB_FILE && git gc --prune=5000.days.ago && test_path_is_missing $BLOB_FILE - ' test_expect_success 'gc --prune=never' ' - add_blob && git gc --prune=never && test_path_is_file $BLOB_FILE && git gc --prune=now && test_path_is_missing $BLOB_FILE - ' test_expect_success 'gc respects gc.pruneExpire=never' ' - git config gc.pruneExpire never && add_blob && git gc && @@ -192,17 +160,14 @@ test_expect_success 'gc respects gc.pruneExpire=never' ' git config gc.pruneExpire now && git gc && test_path_is_missing $BLOB_FILE - ' test_expect_success 'prune --expire=never' ' - add_blob && git prune --expire=never && test_path_is_file $BLOB_FILE && git prune && test_path_is_missing $BLOB_FILE - ' test_expect_success 'gc: prune old objects after local clone' ' @@ -222,16 +187,16 @@ test_expect_success 'gc: prune old objects after local clone' ' test_expect_success 'garbage report in count-objects -v' ' test_when_finished "rm -f .git/objects/pack/fake*" && test_when_finished "rm -f .git/objects/pack/foo*" && - : >.git/objects/pack/foo && - : >.git/objects/pack/foo.bar && - : >.git/objects/pack/foo.keep && - : >.git/objects/pack/foo.pack && - : >.git/objects/pack/fake.bar && - : >.git/objects/pack/fake.keep && - : >.git/objects/pack/fake.pack && - : >.git/objects/pack/fake.idx && - : >.git/objects/pack/fake2.keep && - : >.git/objects/pack/fake3.idx && + >.git/objects/pack/foo && + >.git/objects/pack/foo.bar && + >.git/objects/pack/foo.keep && + >.git/objects/pack/foo.pack && + >.git/objects/pack/fake.bar && + >.git/objects/pack/fake.keep && + >.git/objects/pack/fake.pack && + >.git/objects/pack/fake.idx && + >.git/objects/pack/fake2.keep && + >.git/objects/pack/fake3.idx && git count-objects -v 2>stderr && grep "index file .git/objects/pack/fake.idx is too small" stderr && grep "^warning:" stderr | sort >actual && @@ -250,12 +215,12 @@ EOF test_expect_success 'clean pack garbage with gc' ' test_when_finished "rm -f .git/objects/pack/fake*" && test_when_finished "rm -f .git/objects/pack/foo*" && - : >.git/objects/pack/foo.keep && - : >.git/objects/pack/foo.pack && - : >.git/objects/pack/fake.idx && - : >.git/objects/pack/fake2.keep && - : >.git/objects/pack/fake2.idx && - : >.git/objects/pack/fake3.keep && + >.git/objects/pack/foo.keep && + >.git/objects/pack/foo.pack && + >.git/objects/pack/fake.idx && + >.git/objects/pack/fake2.keep && + >.git/objects/pack/fake2.idx && + >.git/objects/pack/fake3.keep && git gc && git count-objects -v 2>stderr && grep "^warning:" stderr | sort >actual && diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh index 5641d158df..7609f1ea64 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh @@ -410,6 +410,19 @@ test_expect_success 'git-fsck incorrect offset' ' "git -c core.multipackindex=true fsck" ' +test_expect_success 'corrupt MIDX is not reused' ' + corrupt_midx_and_verify $MIDX_BYTE_OFFSET "\377" $objdir \ + "incorrect object offset" && + git multi-pack-index write 2>err && + test_i18ngrep checksum.mismatch err && + git multi-pack-index verify +' + +test_expect_success 'verify incorrect checksum' ' + pos=$(($(wc -c <$objdir/pack/multi-pack-index) - 1)) && + corrupt_midx_and_verify $pos "\377" $objdir "incorrect checksum" +' + test_expect_success 'repack progress off for redirected stderr' ' GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir repack 2>err && test_line_count = 0 err diff --git a/t/t5411/once-0010-report-status-v1.sh b/t/t5411/once-0010-report-status-v1.sh index 1233a46eac..297b10925d 100644 --- a/t/t5411/once-0010-report-status-v1.sh +++ b/t/t5411/once-0010-report-status-v1.sh @@ -28,10 +28,10 @@ test_expect_success "proc-receive: report status v1" ' if test -z "$GIT_DEFAULT_HASH" || test "$GIT_DEFAULT_HASH" = "sha1" then printf "%s %s refs/heads/main\0report-status\n" \ - $A $B | packetize + $A $B | packetize_raw else printf "%s %s refs/heads/main\0report-status object-format=$GIT_DEFAULT_HASH\n" \ - $A $B | packetize + $A $B | packetize_raw fi && printf "%s %s refs/for/main/topic1\n" \ $ZERO_OID $A | packetize && diff --git a/t/t5562-http-backend-content-length.sh b/t/t5562-http-backend-content-length.sh index e5d3d15ba8..05a58069b0 100755 --- a/t/t5562-http-backend-content-length.sh +++ b/t/t5562-http-backend-content-length.sh @@ -63,7 +63,7 @@ test_expect_success 'setup' ' hash_next=$(git commit-tree -p HEAD -m next HEAD^{tree}) && { printf "%s %s refs/heads/newbranch\\0report-status object-format=%s\\n" \ - "$ZERO_OID" "$hash_next" "$(test_oid algo)" | packetize && + "$ZERO_OID" "$hash_next" "$(test_oid algo)" | packetize_raw printf 0000 && echo "$hash_next" | git pack-objects --stdout } >push_body && diff --git a/t/t5570-git-daemon.sh b/t/t5570-git-daemon.sh index 82c31ab6cd..b87ca06a58 100755 --- a/t/t5570-git-daemon.sh +++ b/t/t5570-git-daemon.sh @@ -194,7 +194,7 @@ test_expect_success 'hostname cannot break out of directory' ' test_expect_success FAKENC 'hostname interpolation works after LF-stripping' ' { - printf "git-upload-pack /interp.git\n\0host=localhost" | packetize + printf "git-upload-pack /interp.git\n\0host=localhost" | packetize_raw printf "0000" } >input && fake_nc "$GIT_DAEMON_HOST_PORT" <input >output && diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index c0688467e7..83c24fc97a 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -305,7 +305,8 @@ test_expect_success 'clone from original with relative alternate' ' test_expect_success 'clone checking out a tag' ' git clone --branch=some-tag src dst.tag && GIT_DIR=src/.git git rev-parse some-tag >expected && - test_cmp expected dst.tag/.git/HEAD && + GIT_DIR=dst.tag/.git git rev-parse HEAD >actual && + test_cmp expected actual && GIT_DIR=dst.tag/.git git config remote.origin.fetch >fetch.actual && echo "+refs/heads/*:refs/remotes/origin/*" >fetch.expected && test_cmp fetch.expected fetch.actual diff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh index 66af411057..78de1ff2ad 100755 --- a/t/t5702-protocol-v2.sh +++ b/t/t5702-protocol-v2.sh @@ -599,6 +599,22 @@ setup_negotiate_only () { test_commit -C client three } +test_expect_success 'usage: --negotiate-only without --negotiation-tip' ' + SERVER="server" && + URI="file://$(pwd)/server" && + + setup_negotiate_only "$SERVER" "$URI" && + + cat >err.expect <<-\EOF && + fatal: --negotiate-only needs one or more --negotiate-tip=* + EOF + + test_must_fail git -c protocol.version=2 -C client fetch \ + --negotiate-only \ + origin 2>err.actual && + test_cmp err.expect err.actual +' + test_expect_success 'file:// --negotiate-only' ' SERVER="server" && URI="file://$(pwd)/server" && diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index 35a2f62392..41d0ca00b1 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -41,22 +41,59 @@ test_expect_success 'setup' ' echo "$added_iso88591" | git commit -F - && head1=$(git rev-parse --verify HEAD) && head1_short=$(git rev-parse --verify --short $head1) && + head1_short4=$(git rev-parse --verify --short=4 $head1) && tree1=$(git rev-parse --verify HEAD:) && tree1_short=$(git rev-parse --verify --short $tree1) && echo "$changed" > foo && echo "$changed_iso88591" | git commit -a -F - && head2=$(git rev-parse --verify HEAD) && head2_short=$(git rev-parse --verify --short $head2) && + head2_short4=$(git rev-parse --verify --short=4 $head2) && tree2=$(git rev-parse --verify HEAD:) && tree2_short=$(git rev-parse --verify --short $tree2) && git config --unset i18n.commitEncoding ' -# usage: test_format name format_string [failure] <expected_output +# usage: test_format [argument...] name format_string [failure] <expected_output test_format () { + local args= + while true + do + case "$1" in + --*) + args="$args $1" + shift;; + *) + break;; + esac + done cat >expect.$1 test_expect_${3:-success} "format $1" " - git rev-list --pretty=format:'$2' main >output.$1 && + git rev-list $args --pretty=format:'$2' main >output.$1 && + test_cmp expect.$1 output.$1 + " +} + +# usage: test_pretty [argument...] name format_name [failure] <expected_output +test_pretty () { + local args= + while true + do + case "$1" in + --*) + args="$args $1" + shift;; + *) + break;; + esac + done + cat >expect.$1 + test_expect_${3:-success} "pretty $1 (without --no-commit-header)" " + git rev-list $args --pretty='$2' main >output.$1 && + test_cmp expect.$1 output.$1 + " + test_expect_${3:-success} "pretty $1 (with --no-commit-header)" " + git rev-list $args --no-commit-header --pretty='$2' main >output.$1 && test_cmp expect.$1 output.$1 " } @@ -93,6 +130,20 @@ $head1 $head1_short EOF +test_format --no-commit-header hash-no-header %H%n%h <<EOF +$head2 +$head2_short +$head1 +$head1_short +EOF + +test_format --abbrev-commit --abbrev=0 --no-commit-header hash-no-header-abbrev %H%n%h <<EOF +$head2 +$head2_short4 +$head1 +$head1_short4 +EOF + test_format tree %T%n%t <<EOF commit $head2 $tree2 @@ -181,6 +232,31 @@ $added EOF +test_format --no-commit-header raw-body-no-header %B <<EOF +$changed + +$added + +EOF + +test_pretty oneline oneline <<EOF +$head2 $changed +$head1 $added +EOF + +test_pretty short short <<EOF +commit $head2 +Author: $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> + + $changed + +commit $head1 +Author: $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> + + $added + +EOF + test_expect_success 'basic colors' ' cat >expect <<-EOF && commit $head2 diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index 88fddc9142..1a501ee09e 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -17,16 +17,29 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh check_describe () { + indir= && + while test $# != 0 + do + case "$1" in + -C) + indir="$2" + shift + ;; + *) + break + ;; + esac + shift + done && + indir=${indir:+"$indir"/} && expect="$1" shift describe_opts="$@" test_expect_success "describe $describe_opts" ' - R=$(git describe $describe_opts 2>err.actual) && - case "$R" in - $expect) echo happy ;; - *) echo "Oops - $R is not $expect" && - false ;; - esac + git ${indir:+ -C "$indir"} describe $describe_opts >raw && + sed -e "s/-g[0-9a-f]*\$/-gHASH/" <raw >actual && + echo "$expect" >expect && + test_cmp expect actual ' } @@ -59,29 +72,29 @@ test_expect_success setup ' test_commit --no-tag x file ' -check_describe A-* HEAD -check_describe A-* HEAD^ -check_describe R-* HEAD^^ -check_describe A-* HEAD^^2 +check_describe A-8-gHASH HEAD +check_describe A-7-gHASH HEAD^ +check_describe R-2-gHASH HEAD^^ +check_describe A-3-gHASH HEAD^^2 check_describe B HEAD^^2^ -check_describe R-* HEAD^^^ +check_describe R-1-gHASH HEAD^^^ -check_describe c-* --tags HEAD -check_describe c-* --tags HEAD^ -check_describe e-* --tags HEAD^^ -check_describe c-* --tags HEAD^^2 +check_describe c-7-gHASH --tags HEAD +check_describe c-6-gHASH --tags HEAD^ +check_describe e-1-gHASH --tags HEAD^^ +check_describe c-2-gHASH --tags HEAD^^2 check_describe B --tags HEAD^^2^ check_describe e --tags HEAD^^^ check_describe heads/main --all HEAD -check_describe tags/c-* --all HEAD^ +check_describe tags/c-6-gHASH --all HEAD^ check_describe tags/e --all HEAD^^^ -check_describe B-0-* --long HEAD^^2^ -check_describe A-3-* --long HEAD^^2 +check_describe B-0-gHASH --long HEAD^^2^ +check_describe A-3-gHASH --long HEAD^^2 -check_describe c-7-* --tags -check_describe e-3-* --first-parent --tags +check_describe c-7-gHASH --tags +check_describe e-3-gHASH --first-parent --tags test_expect_success 'describe --contains defaults to HEAD without commit-ish' ' echo "A^0" >expect && @@ -92,20 +105,17 @@ test_expect_success 'describe --contains defaults to HEAD without commit-ish' ' ' check_describe tags/A --all A^0 -test_expect_success 'no warning was displayed for A' ' - test_must_be_empty err.actual -' -test_expect_success 'rename tag A to Q locally' ' - mv .git/refs/tags/A .git/refs/tags/Q -' -cat - >err.expect <<EOF -warning: tag 'Q' is externally known as 'A' -EOF -check_describe A-* HEAD -test_expect_success 'warning was displayed for Q' ' - test_cmp err.expect err.actual -' +test_expect_success 'renaming tag A to Q locally produces a warning' " + mv .git/refs/tags/A .git/refs/tags/Q && + git describe HEAD 2>err >out && + cat >expected <<-\EOF && + warning: tag 'Q' is externally known as 'A' + EOF + test_cmp expected err && + grep -E '^A-8-g[0-9a-f]+$' out +" + test_expect_success 'misnamed annotated tag forces long output' ' description=$(git describe --no-long Q^0) && expr "$description" : "A-0-g[0-9a-f]*$" && @@ -129,46 +139,46 @@ test_expect_success 'rename tag Q back to A' ' ' test_expect_success 'pack tag refs' 'git pack-refs' -check_describe A-* HEAD +check_describe A-8-gHASH HEAD test_expect_success 'describe works from outside repo using --git-dir' ' git clone --bare "$TRASH_DIRECTORY" "$TRASH_DIRECTORY/bare" && git --git-dir "$TRASH_DIRECTORY/bare" describe >out && - grep -E "^A-[1-9][0-9]?-g[0-9a-f]+$" out + grep -E "^A-8-g[0-9a-f]+$" out ' -check_describe "A-*[0-9a-f]" --dirty +check_describe "A-8-gHASH" --dirty test_expect_success 'describe --dirty with --work-tree' ' ( cd "$TEST_DIRECTORY" && git --git-dir "$TRASH_DIRECTORY/.git" --work-tree "$TRASH_DIRECTORY" describe --dirty >"$TRASH_DIRECTORY/out" ) && - grep -E "^A-[1-9][0-9]?-g[0-9a-f]+$" out + grep -E "^A-8-g[0-9a-f]+$" out ' test_expect_success 'set-up dirty work tree' ' echo >>file ' -check_describe "A-*[0-9a-f]-dirty" --dirty - test_expect_success 'describe --dirty with --work-tree (dirty)' ' + git describe --dirty >expected && ( cd "$TEST_DIRECTORY" && git --git-dir "$TRASH_DIRECTORY/.git" --work-tree "$TRASH_DIRECTORY" describe --dirty >"$TRASH_DIRECTORY/out" ) && - grep -E "^A-[1-9][0-9]?-g[0-9a-f]+-dirty$" out + grep -E "^A-8-g[0-9a-f]+-dirty$" out && + test_cmp expected out ' -check_describe "A-*[0-9a-f].mod" --dirty=.mod - test_expect_success 'describe --dirty=.mod with --work-tree (dirty)' ' + git describe --dirty=.mod >expected && ( cd "$TEST_DIRECTORY" && git --git-dir "$TRASH_DIRECTORY/.git" --work-tree "$TRASH_DIRECTORY" describe --dirty=.mod >"$TRASH_DIRECTORY/out" ) && - grep -E "^A-[1-9][0-9]?-g[0-9a-f]+.mod$" out + grep -E "^A-8-g[0-9a-f]+.mod$" out && + test_cmp expected out ' test_expect_success 'describe --dirty HEAD' ' @@ -191,21 +201,21 @@ test_expect_success 'set-up matching pattern tests' ' ' -check_describe "test-annotated-*" --match="test-*" +check_describe "test-annotated-3-gHASH" --match="test-*" -check_describe "test1-lightweight-*" --tags --match="test1-*" +check_describe "test1-lightweight-2-gHASH" --tags --match="test1-*" -check_describe "test2-lightweight-*" --tags --match="test2-*" +check_describe "test2-lightweight-1-gHASH" --tags --match="test2-*" -check_describe "test2-lightweight-*" --long --tags --match="test2-*" HEAD^ +check_describe "test2-lightweight-0-gHASH" --long --tags --match="test2-*" HEAD^ -check_describe "test2-lightweight-*" --long --tags --match="test1-*" --match="test2-*" HEAD^ +check_describe "test2-lightweight-0-gHASH" --long --tags --match="test1-*" --match="test2-*" HEAD^ -check_describe "test2-lightweight-*" --long --tags --match="test1-*" --no-match --match="test2-*" HEAD^ +check_describe "test2-lightweight-0-gHASH" --long --tags --match="test1-*" --no-match --match="test2-*" HEAD^ -check_describe "test1-lightweight-*" --long --tags --match="test1-*" --match="test3-*" HEAD +check_describe "test1-lightweight-2-gHASH" --long --tags --match="test1-*" --match="test3-*" HEAD -check_describe "test1-lightweight-*" --long --tags --match="test3-*" --match="test1-*" HEAD +check_describe "test1-lightweight-2-gHASH" --long --tags --match="test3-*" --match="test1-*" HEAD test_expect_success 'set-up branches' ' git branch branch_A A && @@ -215,11 +225,11 @@ test_expect_success 'set-up branches' ' git update-ref refs/original/original_branch_A test-annotated~2 ' -check_describe "heads/branch_A*" --all --match="branch_*" --exclude="branch_C" HEAD +check_describe "heads/branch_A-11-gHASH" --all --match="branch_*" --exclude="branch_C" HEAD -check_describe "remotes/origin/remote_branch_A*" --all --match="origin/remote_branch_*" --exclude="origin/remote_branch_C" HEAD +check_describe "remotes/origin/remote_branch_A-11-gHASH" --all --match="origin/remote_branch_*" --exclude="origin/remote_branch_C" HEAD -check_describe "original/original_branch_A*" --all test-annotated~1 +check_describe "original/original_branch_A-6-gHASH" --all test-annotated~1 test_expect_success '--match does not work for other types' ' test_must_fail git describe --all --match="*original_branch_*" test-annotated~1 @@ -474,7 +484,7 @@ test_expect_success 'name-rev covers all conditions while looking at parents' ' # o-----o---o----x # A # -test_expect_success 'describe commits with disjoint bases' ' +test_expect_success 'setup: describe commits with disjoint bases' ' git init disjoint1 && ( cd disjoint1 && @@ -487,19 +497,19 @@ test_expect_success 'describe commits with disjoint bases' ' git checkout --orphan branch && rm file && echo B > file2 && git add file2 && git commit -m B && git tag B -a -m B && - git merge --no-ff --allow-unrelated-histories main -m x && - - check_describe "A-3-*" HEAD + git merge --no-ff --allow-unrelated-histories main -m x ) ' +check_describe -C disjoint1 "A-3-gHASH" HEAD + # B # o---o---o------------. # \ # o---o---x # A # -test_expect_success 'describe commits with disjoint bases 2' ' +test_expect_success 'setup: describe commits with disjoint bases 2' ' git init disjoint2 && ( cd disjoint2 && @@ -513,10 +523,10 @@ test_expect_success 'describe commits with disjoint bases 2' ' echo o >> file2 && git add file2 && GIT_COMMITTER_DATE="2020-01-01 15:01" git commit -m o && echo B >> file2 && git add file2 && GIT_COMMITTER_DATE="2020-01-01 15:02" git commit -m B && git tag B -a -m B && - git merge --no-ff --allow-unrelated-histories main -m x && - - check_describe "B-3-*" HEAD + git merge --no-ff --allow-unrelated-histories main -m x ) ' +check_describe -C disjoint2 "B-3-gHASH" HEAD + test_done diff --git a/t/t6400-merge-df.sh b/t/t6400-merge-df.sh index 38700d29b5..57a67cf362 100755 --- a/t/t6400-merge-df.sh +++ b/t/t6400-merge-df.sh @@ -82,13 +82,13 @@ test_expect_success 'modify/delete + directory/file conflict' ' git checkout delete^0 && test_must_fail git merge modify && - test 5 -eq $(git ls-files -s | wc -l) && - test 4 -eq $(git ls-files -u | wc -l) && + test_stdout_line_count = 5 git ls-files -s && + test_stdout_line_count = 4 git ls-files -u && if test "$GIT_TEST_MERGE_ALGORITHM" = ort then - test 0 -eq $(git ls-files -o | wc -l) + test_stdout_line_count = 0 git ls-files -o else - test 1 -eq $(git ls-files -o | wc -l) + test_stdout_line_count = 1 git ls-files -o fi && test_path_is_file letters/file && @@ -103,13 +103,13 @@ test_expect_success 'modify/delete + directory/file conflict; other way' ' test_must_fail git merge delete && - test 5 -eq $(git ls-files -s | wc -l) && - test 4 -eq $(git ls-files -u | wc -l) && + test_stdout_line_count = 5 git ls-files -s && + test_stdout_line_count = 4 git ls-files -u && if test "$GIT_TEST_MERGE_ALGORITHM" = ort then - test 0 -eq $(git ls-files -o | wc -l) + test_stdout_line_count = 0 git ls-files -o else - test 1 -eq $(git ls-files -o | wc -l) + test_stdout_line_count = 1 git ls-files -o fi && test_path_is_file letters/file && diff --git a/t/t6402-merge-rename.sh b/t/t6402-merge-rename.sh index 425dad97d5..3da2896e3b 100755 --- a/t/t6402-merge-rename.sh +++ b/t/t6402-merge-rename.sh @@ -105,10 +105,8 @@ test_expect_success 'pull renaming branch into unrenaming one' \ git show-branch && test_expect_code 1 git pull . white && git ls-files -s && - git ls-files -u B >b.stages && - test_line_count = 3 b.stages && - git ls-files -s N >n.stages && - test_line_count = 1 n.stages && + test_stdout_line_count = 3 git ls-files -u B && + test_stdout_line_count = 1 git ls-files -s N && sed -ne "/^g/{ p q @@ -122,10 +120,8 @@ test_expect_success 'pull renaming branch into another renaming one' \ git reset --hard && git checkout red && test_expect_code 1 git pull . white && - git ls-files -u B >b.stages && - test_line_count = 3 b.stages && - git ls-files -s N >n.stages && - test_line_count = 1 n.stages && + test_stdout_line_count = 3 git ls-files -u B && + test_stdout_line_count = 1 git ls-files -s N && sed -ne "/^g/{ p q @@ -138,10 +134,8 @@ test_expect_success 'pull unrenaming branch into renaming one' \ git reset --hard && git show-branch && test_expect_code 1 git pull . main && - git ls-files -u B >b.stages && - test_line_count = 3 b.stages && - git ls-files -s N >n.stages && - test_line_count = 1 n.stages && + test_stdout_line_count = 3 git ls-files -u B && + test_stdout_line_count = 1 git ls-files -s N && sed -ne "/^g/{ p q @@ -154,14 +148,10 @@ test_expect_success 'pull conflicting renames' \ git reset --hard && git show-branch && test_expect_code 1 git pull . blue && - git ls-files -u A >a.stages && - test_line_count = 1 a.stages && - git ls-files -u B >b.stages && - test_line_count = 1 b.stages && - git ls-files -u C >c.stages && - test_line_count = 1 c.stages && - git ls-files -s N >n.stages && - test_line_count = 1 n.stages && + test_stdout_line_count = 1 git ls-files -u A && + test_stdout_line_count = 1 git ls-files -u B && + test_stdout_line_count = 1 git ls-files -u C && + test_stdout_line_count = 1 git ls-files -s N && sed -ne "/^g/{ p q @@ -330,8 +320,8 @@ test_expect_success 'Rename+D/F conflict; renamed file merges but dir in way' ' test_i18ngrep "Adding as dir~HEAD instead" output fi && - test 3 -eq "$(git ls-files -u | wc -l)" && - test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" && + test_stdout_line_count = 3 git ls-files -u && + test_stdout_line_count = 2 git ls-files -u dir/file-in-the-way && test_must_fail git diff --quiet && test_must_fail git diff --cached --quiet && @@ -357,8 +347,8 @@ test_expect_success 'Same as previous, but merged other way' ' test_i18ngrep "Adding as dir~renamed-file-has-no-conflicts instead" output fi && - test 3 -eq "$(git ls-files -u | wc -l)" && - test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" && + test_stdout_line_count = 3 git ls-files -u && + test_stdout_line_count = 2 git ls-files -u dir/file-in-the-way && test_must_fail git diff --quiet && test_must_fail git diff --cached --quiet && @@ -374,8 +364,8 @@ test_expect_success 'Rename+D/F conflict; renamed file cannot merge, dir not in git checkout -q renamed-file-has-conflicts^0 && test_must_fail git merge --strategy=recursive dir-not-in-way && - test 3 -eq "$(git ls-files -u | wc -l)" && - test 3 -eq "$(git ls-files -u dir | wc -l)" && + test_stdout_line_count = 3 git ls-files -u && + test_stdout_line_count = 3 git ls-files -u dir && test_must_fail git diff --quiet && test_must_fail git diff --cached --quiet && @@ -409,14 +399,16 @@ test_expect_success 'Rename+D/F conflict; renamed file cannot merge and dir in t git checkout -q renamed-file-has-conflicts^0 && test_must_fail git merge --strategy=recursive dir-in-way && - test 5 -eq "$(git ls-files -u | wc -l)" && + test_stdout_line_count = 5 git ls-files -u && if test "$GIT_TEST_MERGE_ALGORITHM" = ort then - test 3 -eq "$(git ls-files -u dir~HEAD | wc -l)" + test_stdout_line_count = 3 git ls-files -u dir~HEAD else - test 3 -eq "$(git ls-files -u dir | grep -v file-in-the-way | wc -l)" + git ls-files -u dir >out && + test 3 -eq $(grep -v file-in-the-way out | wc -l) && + rm -f out fi && - test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" && + test_stdout_line_count = 2 git ls-files -u dir/file-in-the-way && test_must_fail git diff --quiet && test_must_fail git diff --cached --quiet && @@ -432,14 +424,16 @@ test_expect_success 'Same as previous, but merged other way' ' git checkout -q dir-in-way^0 && test_must_fail git merge --strategy=recursive renamed-file-has-conflicts && - test 5 -eq "$(git ls-files -u | wc -l)" && + test_stdout_line_count = 5 git ls-files -u && if test "$GIT_TEST_MERGE_ALGORITHM" = ort then - test 3 -eq "$(git ls-files -u dir~renamed-file-has-conflicts | wc -l)" + test_stdout_line_count = 3 git ls-files -u dir~renamed-file-has-conflicts else - test 3 -eq "$(git ls-files -u dir | grep -v file-in-the-way | wc -l)" + git ls-files -u dir >out && + test 3 -eq $(grep -v file-in-the-way out | wc -l) && + rm -f out fi && - test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" && + test_stdout_line_count = 2 git ls-files -u dir/file-in-the-way && test_must_fail git diff --quiet && test_must_fail git diff --cached --quiet && @@ -496,9 +490,9 @@ test_expect_success 'both rename source and destination involved in D/F conflict if test "$GIT_TEST_MERGE_ALGORITHM" = ort then - test 2 -eq "$(git ls-files -u | wc -l)" + test_stdout_line_count = 2 git ls-files -u else - test 1 -eq "$(git ls-files -u | wc -l)" + test_stdout_line_count = 1 git ls-files -u fi && test_must_fail git diff --quiet && @@ -540,9 +534,9 @@ then mkdir one && test_must_fail git merge --strategy=recursive rename-two && - test 4 -eq "$(git ls-files -u | wc -l)" && - test 2 -eq "$(git ls-files -u one | wc -l)" && - test 2 -eq "$(git ls-files -u two | wc -l)" && + test_stdout_line_count = 4 git ls-files -u && + test_stdout_line_count = 2 git ls-files -u one && + test_stdout_line_count = 2 git ls-files -u two && test_must_fail git diff --quiet && @@ -559,9 +553,9 @@ else mkdir one && test_must_fail git merge --strategy=recursive rename-two && - test 2 -eq "$(git ls-files -u | wc -l)" && - test 1 -eq "$(git ls-files -u one | wc -l)" && - test 1 -eq "$(git ls-files -u two | wc -l)" && + test_stdout_line_count = 2 git ls-files -u && + test_stdout_line_count = 1 git ls-files -u one && + test_stdout_line_count = 1 git ls-files -u two && test_must_fail git diff --quiet && @@ -582,13 +576,13 @@ test_expect_success 'pair rename to parent of other (D/F conflicts) w/ clean sta if test "$GIT_TEST_MERGE_ALGORITHM" = ort then - test 4 -eq "$(git ls-files -u | wc -l)" && - test 2 -eq "$(git ls-files -u one | wc -l)" && - test 2 -eq "$(git ls-files -u two | wc -l)" + test_stdout_line_count = 4 git ls-files -u && + test_stdout_line_count = 2 git ls-files -u one && + test_stdout_line_count = 2 git ls-files -u two else - test 2 -eq "$(git ls-files -u | wc -l)" && - test 1 -eq "$(git ls-files -u one | wc -l)" && - test 1 -eq "$(git ls-files -u two | wc -l)" + test_stdout_line_count = 2 git ls-files -u && + test_stdout_line_count = 1 git ls-files -u one && + test_stdout_line_count = 1 git ls-files -u two fi && test_must_fail git diff --quiet && @@ -631,19 +625,19 @@ test_expect_success 'check handling of differently renamed file with D/F conflic if test "$GIT_TEST_MERGE_ALGORITHM" = ort then - test 5 -eq "$(git ls-files -s | wc -l)" && - test 3 -eq "$(git ls-files -u | wc -l)" && - test 1 -eq "$(git ls-files -u one~HEAD | wc -l)" && - test 1 -eq "$(git ls-files -u two~second-rename | wc -l)" && - test 1 -eq "$(git ls-files -u original | wc -l)" && - test 0 -eq "$(git ls-files -o | wc -l)" + test_stdout_line_count = 5 git ls-files -s && + test_stdout_line_count = 3 git ls-files -u && + test_stdout_line_count = 1 git ls-files -u one~HEAD && + test_stdout_line_count = 1 git ls-files -u two~second-rename && + test_stdout_line_count = 1 git ls-files -u original && + test_stdout_line_count = 0 git ls-files -o else - test 5 -eq "$(git ls-files -s | wc -l)" && - test 3 -eq "$(git ls-files -u | wc -l)" && - test 1 -eq "$(git ls-files -u one | wc -l)" && - test 1 -eq "$(git ls-files -u two | wc -l)" && - test 1 -eq "$(git ls-files -u original | wc -l)" && - test 2 -eq "$(git ls-files -o | wc -l)" + test_stdout_line_count = 5 git ls-files -s && + test_stdout_line_count = 3 git ls-files -u && + test_stdout_line_count = 1 git ls-files -u one && + test_stdout_line_count = 1 git ls-files -u two && + test_stdout_line_count = 1 git ls-files -u original && + test_stdout_line_count = 2 git ls-files -o fi && test_path_is_file one/file && @@ -679,11 +673,11 @@ test_expect_success 'check handling of differently renamed file with D/F conflic git checkout -q first-rename-redo^0 && test_must_fail git merge --strategy=recursive second-rename-redo && - test 3 -eq "$(git ls-files -u | wc -l)" && - test 1 -eq "$(git ls-files -u one | wc -l)" && - test 1 -eq "$(git ls-files -u two | wc -l)" && - test 1 -eq "$(git ls-files -u original | wc -l)" && - test 0 -eq "$(git ls-files -o | wc -l)" && + test_stdout_line_count = 3 git ls-files -u && + test_stdout_line_count = 1 git ls-files -u one && + test_stdout_line_count = 1 git ls-files -u two && + test_stdout_line_count = 1 git ls-files -u original && + test_stdout_line_count = 0 git ls-files -o && test_path_is_file one && test_path_is_file two && @@ -861,9 +855,11 @@ test_expect_success 'setup merge of rename + small change' ' test_expect_success 'merge rename + small change' ' git merge rename_branch && - test 1 -eq $(git ls-files -s | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && - test $(git rev-parse HEAD:renamed_file) = $(git rev-parse HEAD~1:file) + test_stdout_line_count = 1 git ls-files -s && + test_stdout_line_count = 0 git ls-files -o && + newhash=$(git rev-parse HEAD:renamed_file) && + oldhash=$(git rev-parse HEAD~1:file) && + test $newhash = $oldhash ' test_expect_success 'setup for use of extended merge markers' ' diff --git a/t/t6406-merge-attr.sh b/t/t6406-merge-attr.sh index d5a4ac2d81..8494645837 100755 --- a/t/t6406-merge-attr.sh +++ b/t/t6406-merge-attr.sh @@ -207,4 +207,22 @@ test_expect_success 'custom merge does not lock index' ' git merge main ' +test_expect_success 'binary files with union attribute' ' + git checkout -b bin-main && + printf "base\0" >bin.txt && + echo "bin.txt merge=union" >.gitattributes && + git add bin.txt .gitattributes && + git commit -m base && + + printf "one\0" >bin.txt && + git commit -am one && + + git checkout -b bin-side HEAD^ && + printf "two\0" >bin.txt && + git commit -am two && + + test_must_fail git merge bin-main 2>stderr && + grep -i "warning.*cannot merge.*HEAD vs. bin-main" stderr +' + test_done diff --git a/t/t6421-merge-partial-clone.sh b/t/t6421-merge-partial-clone.sh new file mode 100755 index 0000000000..36bcd7c328 --- /dev/null +++ b/t/t6421-merge-partial-clone.sh @@ -0,0 +1,440 @@ +#!/bin/sh + +test_description="limiting blob downloads when merging with partial clones" +# Uses a methodology similar to +# t6042: corner cases with renames but not criss-cross merges +# t6036: corner cases with both renames and criss-cross merges +# t6423: directory rename detection +# +# The setup for all of them, pictorially, is: +# +# A +# o +# / \ +# O o ? +# \ / +# o +# B +# +# To help make it easier to follow the flow of tests, they have been +# divided into sections and each test will start with a quick explanation +# of what commits O, A, and B contain. +# +# Notation: +# z/{b,c} means files z/b and z/c both exist +# x/d_1 means file x/d exists with content d1. (Purpose of the +# underscore notation is to differentiate different +# files that might be renamed into each other's paths.) + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-merge.sh + +test_setup_repo () { + test -d server && return + test_create_repo server && + ( + cd server && + + git config uploadpack.allowfilter 1 && + git config uploadpack.allowanysha1inwant 1 && + + mkdir -p general && + test_seq 2 9 >general/leap1 && + cp general/leap1 general/leap2 && + echo leap2 >>general/leap2 && + + mkdir -p basename && + cp general/leap1 basename/numbers && + cp general/leap1 basename/sequence && + cp general/leap1 basename/values && + echo numbers >>basename/numbers && + echo sequence >>basename/sequence && + echo values >>basename/values && + + mkdir -p dir/unchanged && + mkdir -p dir/subdir/tweaked && + echo a >dir/subdir/a && + echo b >dir/subdir/b && + echo c >dir/subdir/c && + echo d >dir/subdir/d && + echo e >dir/subdir/e && + cp general/leap1 dir/subdir/Makefile && + echo toplevel makefile >>dir/subdir/Makefile && + echo f >dir/subdir/tweaked/f && + echo g >dir/subdir/tweaked/g && + echo h >dir/subdir/tweaked/h && + echo subdirectory makefile >dir/subdir/tweaked/Makefile && + for i in $(test_seq 1 88) + do + echo content $i >dir/unchanged/file_$i + done && + git add . && + git commit -m "O" && + + git branch O && + git branch A && + git branch B-single && + git branch B-dir && + git branch B-many && + + git switch A && + + git rm general/leap* && + mkdir general/ && + test_seq 1 9 >general/jump1 && + cp general/jump1 general/jump2 && + echo leap2 >>general/jump2 && + + rm basename/numbers basename/sequence basename/values && + mkdir -p basename/subdir/ + cp general/jump1 basename/subdir/numbers && + cp general/jump1 basename/subdir/sequence && + cp general/jump1 basename/subdir/values && + echo numbers >>basename/subdir/numbers && + echo sequence >>basename/subdir/sequence && + echo values >>basename/subdir/values && + + git rm dir/subdir/tweaked/f && + echo more >>dir/subdir/e && + echo more >>dir/subdir/Makefile && + echo more >>dir/subdir/tweaked/Makefile && + mkdir dir/subdir/newsubdir && + echo rust code >dir/subdir/newsubdir/newfile.rs && + git mv dir/subdir/e dir/subdir/newsubdir/ && + git mv dir folder && + git add . && + git commit -m "A" && + + git switch B-single && + echo new first line >dir/subdir/Makefile && + cat general/leap1 >>dir/subdir/Makefile && + echo toplevel makefile >>dir/subdir/Makefile && + echo perl code >general/newfile.pl && + git add . && + git commit -m "B-single" && + + git switch B-dir && + echo java code >dir/subdir/newfile.java && + echo scala code >dir/subdir/newfile.scala && + echo groovy code >dir/subdir/newfile.groovy && + git add . && + git commit -m "B-dir" && + + git switch B-many && + test_seq 2 10 >general/leap1 && + rm general/leap2 && + cp general/leap1 general/leap2 && + echo leap2 >>general/leap2 && + + rm basename/numbers basename/sequence basename/values && + mkdir -p basename/subdir/ + cp general/leap1 basename/subdir/numbers && + cp general/leap1 basename/subdir/sequence && + cp general/leap1 basename/subdir/values && + echo numbers >>basename/subdir/numbers && + echo sequence >>basename/subdir/sequence && + echo values >>basename/subdir/values && + + mkdir dir/subdir/newsubdir/ && + echo c code >dir/subdir/newfile.c && + echo python code >dir/subdir/newsubdir/newfile.py && + git add . && + git commit -m "B-many" && + + git switch A + ) +} + +# Testcase: Objects downloaded for single relevant rename +# Commit O: +# general/{leap1_O, leap2_O} +# basename/{numbers_O, sequence_O, values_O} +# dir/subdir/{a,b,c,d,e_O,Makefile_TOP_O} +# dir/subdir/tweaked/{f,g,h,Makefile_SUB_O} +# dir/unchanged/<LOTS OF FILES> +# Commit A: +# (Rename leap->jump, rename basename/ -> basename/subdir/, rename dir/ +# -> folder/, move e into newsubdir, add newfile.rs, remove f, modify +# both both Makefiles and jumps) +# general/{jump1_A, jump2_A} +# basename/subdir/{numbers_A, sequence_A, values_A} +# folder/subdir/{a,b,c,d,Makefile_TOP_A} +# folder/subdir/newsubdir/{e_A,newfile.rs} +# folder/subdir/tweaked/{g,h,Makefile_SUB_A} +# folder/unchanged/<LOTS OF FILES> +# Commit B(-single): +# (add newfile.pl, tweak Makefile_TOP) +# general/{leap1_O, leap2_O,newfile.pl} +# basename/{numbers_O, sequence_O, values_O} +# dir/{a,b,c,d,e_O,Makefile_TOP_B} +# dir/tweaked/{f,g,h,Makefile_SUB_O} +# dir/unchanged/<LOTS OF FILES> +# Expected: +# general/{jump1_A, jump2_A,newfile.pl} +# basename/subdir/{numbers_A, sequence_A, values_A} +# folder/subdir/{a,b,c,d,Makefile_TOP_Merged} +# folder/subdir/newsubdir/{e_A,newfile.rs} +# folder/subdir/tweaked/{g,h,Makefile_SUB_A} +# folder/unchanged/<LOTS OF FILES> +# +# Objects that need to be fetched: +# Rename detection: +# Side1 (O->A): +# Basename-matches rename detection only needs to fetch these objects: +# Makefile_TOP_O, Makefile_TOP_A +# (Despite many renames, all others are content irrelevant. They +# are also location irrelevant because newfile.rs was added on +# the side doing the directory rename, and newfile.pl was added to +# a directory that was not renamed on either side.) +# General rename detection only needs to fetch these objects: +# <None> +# (Even though newfile.rs, jump[12], basename/subdir/*, and e +# could all be used as destinations in rename detection, the +# basename detection for Makefile matches up all relevant +# sources, so these other files never end up needing to be +# used) +# Side2 (O->B): +# Basename-matches rename detection only needs to fetch these objects: +# <None> +# (there are no deleted files, so no possible sources) +# General rename detection only needs to fetch these objects: +# <None> +# (there are no deleted files, so no possible sources) +# Merge: +# 3-way content merge needs to grab these objects: +# Makefile_TOP_B +# Nothing else needs to fetch objects +# +# Summary: 2 fetches (1 for 2 objects, 1 for 1 object) +# +test_expect_merge_algorithm failure success 'Objects downloaded for single relevant rename' ' + test_setup_repo && + git clone --sparse --filter=blob:none "file://$(pwd)/server" objects-single && + ( + cd objects-single && + + git rev-list --objects --all --missing=print | + grep "^?" | sort >missing-objects-before && + + git checkout -q origin/A && + + GIT_TRACE2_PERF="$(pwd)/trace.output" git \ + -c merge.directoryRenames=true merge --no-stat \ + --no-progress origin/B-single && + + # Check the number of objects we reported we would fetch + cat >expect <<-EOF && + fetch_count:2 + fetch_count:1 + EOF + grep fetch_count trace.output | cut -d "|" -f 9 | tr -d " ." >actual && + test_cmp expect actual && + + # Check the number of fetch commands exec-ed + grep d0.*fetch.negotiationAlgorithm trace.output >fetches && + test_line_count = 2 fetches && + + git rev-list --objects --all --missing=print | + grep "^?" | sort >missing-objects-after && + comm -2 -3 missing-objects-before missing-objects-after >old && + comm -1 -3 missing-objects-before missing-objects-after >new && + # No new missing objects + test_must_be_empty new && + # Fetched 2 + 1 = 3 objects + test_line_count = 3 old + ) +' + +# Testcase: Objects downloaded for directory rename +# Commit O: +# general/{leap1_O, leap2_O} +# basename/{numbers_O, sequence_O, values_O} +# dir/subdir/{a,b,c,d,e_O,Makefile_TOP_O} +# dir/subdir/tweaked/{f,g,h,Makefile_SUB_O} +# dir/unchanged/<LOTS OF FILES> +# Commit A: +# (Rename leap->jump, rename basename/ -> basename/subdir/, rename dir/ -> +# folder/, move e into newsubdir, add newfile.rs, remove f, modify +# both Makefiles and jumps) +# general/{jump1_A, jump2_A} +# basename/subdir/{numbers_A, sequence_A, values_A} +# folder/subdir/{a,b,c,d,Makefile_TOP_A} +# folder/subdir/newsubdir/{e_A,newfile.rs} +# folder/subdir/tweaked/{g,h,Makefile_SUB_A} +# folder/unchanged/<LOTS OF FILES> +# Commit B(-dir): +# (add dir/subdir/newfile.{java,scala,groovy} +# general/{leap1_O, leap2_O} +# basename/{numbers_O, sequence_O, values_O} +# dir/subdir/{a,b,c,d,e_O,Makefile_TOP_O, +# newfile.java,newfile.scala,newfile.groovy} +# dir/subdir/tweaked/{f,g,h,Makefile_SUB_O} +# dir/unchanged/<LOTS OF FILES> +# Expected: +# general/{jump1_A, jump2_A} +# basename/subdir/{numbers_A, sequence_A, values_A} +# folder/subdir/{a,b,c,d,Makefile_TOP_A, +# newfile.java,newfile.scala,newfile.groovy} +# folder/subdir/newsubdir/{e_A,newfile.rs} +# folder/subdir/tweaked/{g,h,Makefile_SUB_A} +# folder/unchanged/<LOTS OF FILES> +# +# Objects that need to be fetched: +# Makefile_TOP_O, Makefile_TOP_A +# Makefile_SUB_O, Makefile_SUB_A +# e_O, e_A +# * Despite A's rename of jump->leap, those renames are irrelevant. +# * Despite A's rename of basename/ -> basename/subdir/, those renames are +# irrelevant. +# * Because of A's rename of dir/ -> folder/ and B-dir's addition of +# newfile.* into dir/subdir/, we need to determine directory renames. +# (Technically, there are enough exact renames to determine directory +# rename detection, but the current implementation always does +# basename searching before directory rename detection. Running it +# also before basename searching would mean doing directory rename +# detection twice, but it's a bit expensive to do that and cases like +# this are not all that common.) +# Summary: 1 fetches for 6 objects +# +test_expect_merge_algorithm failure success 'Objects downloaded when a directory rename triggered' ' + test_setup_repo && + git clone --sparse --filter=blob:none "file://$(pwd)/server" objects-dir && + ( + cd objects-dir && + + git rev-list --objects --all --missing=print | + grep "^?" | sort >missing-objects-before && + + git checkout -q origin/A && + + GIT_TRACE2_PERF="$(pwd)/trace.output" git \ + -c merge.directoryRenames=true merge --no-stat \ + --no-progress origin/B-dir && + + # Check the number of objects we reported we would fetch + cat >expect <<-EOF && + fetch_count:6 + EOF + grep fetch_count trace.output | cut -d "|" -f 9 | tr -d " ." >actual && + test_cmp expect actual && + + # Check the number of fetch commands exec-ed + grep d0.*fetch.negotiationAlgorithm trace.output >fetches && + test_line_count = 1 fetches && + + git rev-list --objects --all --missing=print | + grep "^?" | sort >missing-objects-after && + comm -2 -3 missing-objects-before missing-objects-after >old && + comm -1 -3 missing-objects-before missing-objects-after >new && + # No new missing objects + test_must_be_empty new && + # Fetched 6 objects + test_line_count = 6 old + ) +' + +# Testcase: Objects downloaded with lots of renames and modifications +# Commit O: +# general/{leap1_O, leap2_O} +# basename/{numbers_O, sequence_O, values_O} +# dir/subdir/{a,b,c,d,e_O,Makefile_TOP_O} +# dir/subdir/tweaked/{f,g,h,Makefile_SUB_O} +# dir/unchanged/<LOTS OF FILES> +# Commit A: +# (Rename leap->jump, rename basename/ -> basename/subdir/, rename dir/ +# -> folder/, move e into newsubdir, add newfile.rs, remove f, modify +# both both Makefiles and jumps) +# general/{jump1_A, jump2_A} +# basename/subdir/{numbers_A, sequence_A, values_A} +# folder/subdir/{a,b,c,d,Makefile_TOP_A} +# folder/subdir/newsubdir/{e_A,newfile.rs} +# folder/subdir/tweaked/{g,h,Makefile_SUB_A} +# folder/unchanged/<LOTS OF FILES> +# Commit B(-minimal): +# (modify both leaps, rename basename/ -> basename/subdir/, add +# newfile.{c,py}) +# general/{leap1_B, leap2_B} +# basename/subdir/{numbers_B, sequence_B, values_B} +# dir/{a,b,c,d,e_O,Makefile_TOP_O,newfile.c} +# dir/tweaked/{f,g,h,Makefile_SUB_O,newfile.py} +# dir/unchanged/<LOTS OF FILES> +# Expected: +# general/{jump1_Merged, jump2_Merged} +# basename/subdir/{numbers_Merged, sequence_Merged, values_Merged} +# folder/subdir/{a,b,c,d,Makefile_TOP_A,newfile.c} +# folder/subdir/newsubdir/e_A +# folder/subdir/tweaked/{g,h,Makefile_SUB_A,newfile.py} +# folder/unchanged/<LOTS OF FILES> +# +# Objects that need to be fetched: +# Rename detection: +# Side1 (O->A): +# Basename-matches rename detection only needs to fetch these objects: +# numbers_O, numbers_A +# sequence_O, sequence_A +# values_O, values_A +# Makefile_TOP_O, Makefile_TOP_A +# Makefile_SUB_O, Makefile_SUB_A +# e_O, e_A +# General rename detection only needs to fetch these objects: +# leap1_O, leap2_O +# jump1_A, jump2_A, newfile.rs +# (only need remaining relevant sources, but any relevant sources need +# to be matched against all possible unpaired destinations) +# Side2 (O->B): +# Basename-matches rename detection only needs to fetch these objects: +# numbers_B +# sequence_B +# values_B +# (because numbers_O, sequence_O, and values_O already fetched above) +# General rename detection only needs to fetch these objects: +# <None> +# Merge: +# 3-way content merge needs to grab these objects: +# leap1_B +# leap2_B +# Nothing else needs to fetch objects +# +# Summary: 4 fetches (1 for 6 objects, 1 for 8, 1 for 3, 1 for 2) +# +test_expect_merge_algorithm failure success 'Objects downloaded with lots of renames and modifications' ' + test_setup_repo && + git clone --sparse --filter=blob:none "file://$(pwd)/server" objects-many && + ( + cd objects-many && + + git rev-list --objects --all --missing=print | + grep "^?" | sort >missing-objects-before && + + git checkout -q origin/A && + + GIT_TRACE2_PERF="$(pwd)/trace.output" git \ + -c merge.directoryRenames=true merge --no-stat \ + --no-progress origin/B-many && + + # Check the number of objects we reported we would fetch + cat >expect <<-EOF && + fetch_count:12 + fetch_count:5 + fetch_count:3 + fetch_count:2 + EOF + grep fetch_count trace.output | cut -d "|" -f 9 | tr -d " ." >actual && + test_cmp expect actual && + + # Check the number of fetch commands exec-ed + grep d0.*fetch.negotiationAlgorithm trace.output >fetches && + test_line_count = 4 fetches && + + git rev-list --objects --all --missing=print | + grep "^?" | sort >missing-objects-after && + comm -2 -3 missing-objects-before missing-objects-after >old && + comm -1 -3 missing-objects-before missing-objects-after >new && + # No new missing objects + test_must_be_empty new && + # Fetched 12 + 5 + 3 + 2 = 22 objects + test_line_count = 22 old + ) +' + +test_done diff --git a/t/t6423-merge-rename-directories.sh b/t/t6423-merge-rename-directories.sh index be84d22419..4af4fb0038 100755 --- a/t/t6423-merge-rename-directories.sh +++ b/t/t6423-merge-rename-directories.sh @@ -454,7 +454,7 @@ test_expect_success '1f: Split a directory into two other directories' ' # the directory renamed, but the files within it. (see 1b) # # If renames split a directory into two or more others, the directory -# with the most renames, "wins" (see 1c). However, see the testcases +# with the most renames, "wins" (see 1f). However, see the testcases # in section 2, plus testcases 3a and 4a. ########################################################################### @@ -5024,6 +5024,181 @@ test_expect_failure '12h: renaming a file within a renamed directory' ' ) ' +# Testcase 12i, Directory rename causes rename-to-self +# Commit O: source/{subdir/foo, bar, baz_1} +# Commit A: source/{foo, bar, baz_1} +# Commit B: source/{subdir/{foo, bar}, baz_2} +# Expected: source/{foo, bar, baz_2}, with conflicts on +# source/bar vs. source/subdir/bar + +test_setup_12i () { + test_create_repo 12i && + ( + cd 12i && + + mkdir -p source/subdir && + echo foo >source/subdir/foo && + echo bar >source/bar && + echo baz >source/baz && + git add source && + git commit -m orig && + + git branch O && + git branch A && + git branch B && + + git switch A && + git mv source/subdir/foo source/foo && + git commit -m A && + + git switch B && + git mv source/bar source/subdir/bar && + echo more baz >>source/baz && + git commit -m B + ) +} + +test_expect_success '12i: Directory rename causes rename-to-self' ' + test_setup_12i && + ( + cd 12i && + + git checkout A^0 && + + test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 && + + test_path_is_missing source/subdir && + test_path_is_file source/bar && + test_path_is_file source/baz && + + git ls-files | uniq >tracked && + test_line_count = 3 tracked && + + git status --porcelain -uno >actual && + cat >expect <<-\EOF && + UU source/bar + M source/baz + EOF + test_cmp expect actual + ) +' + +# Testcase 12j, Directory rename to root causes rename-to-self +# Commit O: {subdir/foo, bar, baz_1} +# Commit A: {foo, bar, baz_1} +# Commit B: {subdir/{foo, bar}, baz_2} +# Expected: {foo, bar, baz_2}, with conflicts on bar vs. subdir/bar + +test_setup_12j () { + test_create_repo 12j && + ( + cd 12j && + + mkdir -p subdir && + echo foo >subdir/foo && + echo bar >bar && + echo baz >baz && + git add . && + git commit -m orig && + + git branch O && + git branch A && + git branch B && + + git switch A && + git mv subdir/foo foo && + git commit -m A && + + git switch B && + git mv bar subdir/bar && + echo more baz >>baz && + git commit -m B + ) +} + +test_expect_success '12j: Directory rename to root causes rename-to-self' ' + test_setup_12j && + ( + cd 12j && + + git checkout A^0 && + + test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 && + + test_path_is_missing subdir && + test_path_is_file bar && + test_path_is_file baz && + + git ls-files | uniq >tracked && + test_line_count = 3 tracked && + + git status --porcelain -uno >actual && + cat >expect <<-\EOF && + UU bar + M baz + EOF + test_cmp expect actual + ) +' + +# Testcase 12k, Directory rename with sibling causes rename-to-self +# Commit O: dirB/foo, dirA/{bar, baz_1} +# Commit A: dirA/{foo, bar, baz_1} +# Commit B: dirB/{foo, bar}, dirA/baz_2 +# Expected: dirA/{foo, bar, baz_2}, with conflicts on dirA/bar vs. dirB/bar + +test_setup_12k () { + test_create_repo 12k && + ( + cd 12k && + + mkdir dirA dirB && + echo foo >dirB/foo && + echo bar >dirA/bar && + echo baz >dirA/baz && + git add . && + git commit -m orig && + + git branch O && + git branch A && + git branch B && + + git switch A && + git mv dirB/* dirA/ && + git commit -m A && + + git switch B && + git mv dirA/bar dirB/bar && + echo more baz >>dirA/baz && + git commit -m B + ) +} + +test_expect_success '12k: Directory rename with sibling causes rename-to-self' ' + test_setup_12k && + ( + cd 12k && + + git checkout A^0 && + + test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 && + + test_path_is_missing dirB && + test_path_is_file dirA/bar && + test_path_is_file dirA/baz && + + git ls-files | uniq >tracked && + test_line_count = 3 tracked && + + git status --porcelain -uno >actual && + cat >expect <<-\EOF && + UU dirA/bar + M dirA/baz + EOF + test_cmp expect actual + ) +' + ########################################################################### # SECTION 13: Checking informational and conflict messages # diff --git a/t/t6500-gc.sh b/t/t6500-gc.sh index 60d961b526..10c7ae7f09 100755 --- a/t/t6500-gc.sh +++ b/t/t6500-gc.sh @@ -95,6 +95,52 @@ test_expect_success 'gc --keep-largest-pack' ' ) ' +test_expect_success 'pre-auto-gc hook can stop auto gc' ' + cat >err.expect <<-\EOF && + no gc for you + EOF + + git init pre-auto-gc-hook && + ( + cd pre-auto-gc-hook && + write_script ".git/hooks/pre-auto-gc" <<-\EOF && + echo >&2 no gc for you && + exit 1 + EOF + + git config gc.auto 3 && + git config gc.autoDetach false && + + # We need to create two object whose sha1s start with 17 + # since this is what git gc counts. As it happens, these + # two blobs will do so. + test_commit "$(test_oid obj1)" && + test_commit "$(test_oid obj2)" && + + git gc --auto >../out.actual 2>../err.actual + ) && + test_must_be_empty out.actual && + test_cmp err.expect err.actual && + + cat >err.expect <<-\EOF && + will gc for you + Auto packing the repository for optimum performance. + See "git help gc" for manual housekeeping. + EOF + + ( + cd pre-auto-gc-hook && + write_script ".git/hooks/pre-auto-gc" <<-\EOF && + echo >&2 will gc for you && + exit 0 + EOF + git gc --auto >../out.actual 2>../err.actual + ) && + + test_must_be_empty out.actual && + test_cmp err.expect err.actual +' + test_expect_success 'auto gc with too many loose objects does not attempt to create bitmaps' ' test_config gc.auto 3 && test_config gc.autodetach false && diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh index 1349e5b232..e18a218952 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -395,8 +395,11 @@ test_expect_success '--prune-empty is able to prune root commit' ' test_expect_success '--prune-empty is able to prune entire branch' ' git branch prune-entire B && git filter-branch -f --prune-empty --index-filter "git update-index --remove A.t B.t" prune-entire && - test_path_is_missing .git/refs/heads/prune-entire && - test_must_fail git reflog exists refs/heads/prune-entire + test_must_fail git rev-parse refs/heads/prune-entire && + if test_have_prereq REFFILES + then + test_must_fail git reflog exists refs/heads/prune-entire + fi ' test_expect_success '--remap-to-ancestor with filename filters' ' diff --git a/t/t7500-commit-template-squash-signoff.sh b/t/t7500-commit-template-squash-signoff.sh index 7d02f79c0d..54c2082acb 100755 --- a/t/t7500-commit-template-squash-signoff.sh +++ b/t/t7500-commit-template-squash-signoff.sh @@ -498,7 +498,7 @@ test_expect_success 'invalid message options when using --fixup' ' cat >expected-template <<EOF # Please enter the commit message for your changes. Lines starting -# with '#' will be ignored, and an empty message aborts the commit. +# with '#' will be ignored. # # Author: A U Thor <author@example.com> # diff --git a/t/t7509-commit-authorship.sh b/t/t7509-commit-authorship.sh index ee6c47416e..d568593382 100755 --- a/t/t7509-commit-authorship.sh +++ b/t/t7509-commit-authorship.sh @@ -147,7 +147,7 @@ test_expect_success 'commit respects CHERRY_PICK_HEAD and MERGE_MSG' ' test_tick && git commit -am "cherry-pick 1" --author="Cherry <cherry@pick.er>" && git tag cherry-pick-head && - git rev-parse cherry-pick-head >.git/CHERRY_PICK_HEAD && + git update-ref CHERRY_PICK_HEAD $(git rev-parse cherry-pick-head) && echo "This is a MERGE_MSG" >.git/MERGE_MSG && echo "cherry-pick 1b" >>foo && test_tick && @@ -162,7 +162,7 @@ test_expect_success 'commit respects CHERRY_PICK_HEAD and MERGE_MSG' ' ' test_expect_success '--reset-author with CHERRY_PICK_HEAD' ' - git rev-parse cherry-pick-head >.git/CHERRY_PICK_HEAD && + git update-ref CHERRY_PICK_HEAD $(git rev-parse cherry-pick-head) && echo "cherry-pick 2" >>foo && test_tick && git commit -am "cherry-pick 2" --reset-author && diff --git a/t/t7519-status-fsmonitor.sh b/t/t7519-status-fsmonitor.sh index 637391c6ce..deea88d443 100755 --- a/t/t7519-status-fsmonitor.sh +++ b/t/t7519-status-fsmonitor.sh @@ -73,6 +73,7 @@ test_expect_success 'setup' ' expect* actual* marker* + trace2* EOF ' @@ -383,4 +384,52 @@ test_expect_success 'status succeeds after staging/unstaging' ' ) ' +# Usage: +# check_sparse_index_behavior [!] +# If "!" is supplied, then we verify that we do not call ensure_full_index +# during a call to 'git status'. Otherwise, we verify that we _do_ call it. +check_sparse_index_behavior () { + git status --porcelain=v2 >expect && + git sparse-checkout init --cone --sparse-index && + git sparse-checkout set dir1 dir2 && + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \ + git status --porcelain=v2 >actual && + test_region $1 index ensure_full_index trace2.txt && + test_region fsm_hook query trace2.txt && + test_cmp expect actual && + rm trace2.txt && + git sparse-checkout disable +} + +test_expect_success 'status succeeds with sparse index' ' + git reset --hard && + + test_config core.fsmonitor "$TEST_DIRECTORY/t7519/fsmonitor-all" && + check_sparse_index_behavior ! && + + write_script .git/hooks/fsmonitor-test<<-\EOF && + printf "last_update_token\0" + EOF + git config core.fsmonitor .git/hooks/fsmonitor-test && + check_sparse_index_behavior ! && + + write_script .git/hooks/fsmonitor-test<<-\EOF && + printf "last_update_token\0" + printf "dir1/modified\0" + EOF + check_sparse_index_behavior ! && + + cp -r dir1 dir1a && + git add dir1a && + git commit -m "add dir1a" && + + # This one modifies outside the sparse-checkout definition + # and hence we expect to expand the sparse-index. + write_script .git/hooks/fsmonitor-test<<-\EOF && + printf "last_update_token\0" + printf "dir1a/modified\0" + EOF + check_sparse_index_behavior +' + test_done diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh index 5830733f3d..6b6423a07c 100755 --- a/t/t7810-grep.sh +++ b/t/t7810-grep.sh @@ -11,6 +11,13 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh +test_invalid_grep_expression() { + params="$@" && + test_expect_success "invalid expression: grep $params" ' + test_must_fail git grep $params -- nonexisting + ' +} + cat >hello.c <<EOF #include <assert.h> #include <stdio.h> @@ -89,6 +96,8 @@ test_expect_success 'grep should not segfault with a bad input' ' test_must_fail git grep "(" ' +test_invalid_grep_expression --and -e A + for H in HEAD '' do case "$H" in diff --git a/t/t7816-grep-binary-pattern.sh b/t/t7816-grep-binary-pattern.sh index 60bab291e4..9d67a5fc4c 100755 --- a/t/t7816-grep-binary-pattern.sh +++ b/t/t7816-grep-binary-pattern.sh @@ -59,7 +59,7 @@ test_expect_success 'setup' " git commit -m. " -# Simple fixed-string matching that can use kwset (no -i && non-ASCII) +# Simple fixed-string matching nul_match P P P '-F' 'yQf' nul_match P P P '-F' 'yQx' nul_match P P P '-Fi' 'YQf' @@ -78,7 +78,7 @@ nul_match P P P '-Fi' '[Y]QF' nul_match P P P '-F' 'æQ[ð]' nul_match P P P '-F' '[æ]Qð' -# The -F kwset codepath can't handle -i && non-ASCII... +# Matching pattern and subject case with -i nul_match P 1 1 '-i' '[æ]Qð' # ...PCRE v2 only matches non-ASCII with -i casefolding under UTF-8 diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh index b93ae014ee..58f46c77e6 100755 --- a/t/t7900-maintenance.sh +++ b/t/t7900-maintenance.sh @@ -356,8 +356,6 @@ test_expect_success 'pack-refs task' ' done && GIT_TRACE2_EVENT="$(pwd)/pack-refs.txt" \ git maintenance run --task=pack-refs && - ls .git/refs/heads/ >after && - test_must_be_empty after && test_subcommand git pack-refs --all --prune <pack-refs.txt ' diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index e9dc58f508..57fc10e7f8 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -1368,6 +1368,16 @@ test_expect_success $PREREQ 'sendemail.identity: bool variable fallback' ' ! grep "X-Mailer" stdout ' +test_expect_success $PREREQ 'sendemail.identity: bool variable without a value' ' + git -c sendemail.xmailer \ + send-email \ + --dry-run \ + --from="nobody@example.com" \ + $patches >stdout && + grep "To: default@example.com" stdout && + grep "X-Mailer" stdout +' + test_expect_success $PREREQ '--no-to overrides sendemail.to' ' git send-email \ --dry-run \ @@ -2092,6 +2102,18 @@ test_expect_success $PREREQ '--[no-]xmailer with sendemail.xmailer=true' ' do_xmailer_test 1 "--xmailer" ' +test_expect_success $PREREQ '--[no-]xmailer with sendemail.xmailer' ' + test_when_finished "test_unconfig sendemail.xmailer" && + cat >>.git/config <<-\EOF && + [sendemail] + xmailer + EOF + test_config sendemail.xmailer true && + do_xmailer_test 1 "" && + do_xmailer_test 0 "--no-xmailer" && + do_xmailer_test 1 "--xmailer" +' + test_expect_success $PREREQ '--[no-]xmailer with sendemail.xmailer=false' ' test_config sendemail.xmailer false && do_xmailer_test 0 "" && @@ -2099,6 +2121,13 @@ test_expect_success $PREREQ '--[no-]xmailer with sendemail.xmailer=false' ' do_xmailer_test 1 "--xmailer" ' +test_expect_success $PREREQ '--[no-]xmailer with sendemail.xmailer=' ' + test_config sendemail.xmailer "" && + do_xmailer_test 0 "" && + do_xmailer_test 0 "--no-xmailer" && + do_xmailer_test 1 "--xmailer" +' + test_expect_success $PREREQ 'setup expected-list' ' git send-email \ --dry-run \ diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index 5c47ac4465..aa55b41b9a 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -392,7 +392,7 @@ test_expect_success 'B: accept branch name "TEMP_TAG"' ' git gc git prune" && git fast-import <input && - test -f .git/TEMP_TAG && + test $(test-tool ref-store main resolve-ref TEMP_TAG 0 | cut -f1 -d " " ) != "$ZERO_OID" && test $(git rev-parse main) = $(git rev-parse TEMP_TAG^) ' @@ -1538,7 +1538,6 @@ test_expect_success 'O: comments are all skipped' ' commit refs/heads/O1 # -- ignore all of this text committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - # $GIT_COMMITTER_NAME has inserted here for his benefit. data <<COMMIT dirty directory copy COMMIT diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index b2810478a2..e28411bb75 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -845,6 +845,32 @@ test_line_count () { fi } +# SYNOPSIS: +# test_stdout_line_count <bin-ops> <value> <cmd> [<args>...] +# +# test_stdout_line_count checks that the output of a command has the number +# of lines it ought to. For example: +# +# test_stdout_line_count = 3 git ls-files -u +# test_stdout_line_count -gt 10 ls +test_stdout_line_count () { + local ops val trashdir && + if test "$#" -le 3 + then + BUG "expect 3 or more arguments" + fi && + ops="$1" && + val="$2" && + shift 2 && + if ! trashdir="$(git rev-parse --git-dir)/trash"; then + BUG "expect to be run inside a worktree" + fi && + mkdir -p "$trashdir" && + "$@" >"$trashdir/output" && + test_line_count "$ops" "$val" "$trashdir/output" +} + + test_file_size () { test "$#" -ne 1 && BUG "1 param" test-tool path-utils file-size "$1" @@ -1453,46 +1479,24 @@ nongit () { ) } 7>&2 2>&4 -# convert function arguments or stdin (if not arguments given) to pktline -# representation. If multiple arguments are given, they are separated by -# whitespace and put in a single packet. Note that data containing NULs must be -# given on stdin, and that empty input becomes an empty packet, not a flush -# packet (for that you can just print 0000 yourself). +# These functions are historical wrappers around "test-tool pkt-line" +# for older tests. Use "test-tool pkt-line" itself in new tests. packetize () { if test $# -gt 0 then packet="$*" printf '%04x%s' "$((4 + ${#packet}))" "$packet" else - perl -e ' - my $packet = do { local $/; <STDIN> }; - printf "%04x%s", 4 + length($packet), $packet; - ' + test-tool pkt-line pack fi } -# Parse the input as a series of pktlines, writing the result to stdout. -# Sideband markers are removed automatically, and the output is routed to -# stderr if appropriate. -# -# NUL bytes are converted to "\\0" for ease of parsing with text tools. +packetize_raw () { + test-tool pkt-line pack-raw-stdin +} + depacketize () { - perl -e ' - while (read(STDIN, $len, 4) == 4) { - if ($len eq "0000") { - print "FLUSH\n"; - } else { - read(STDIN, $buf, hex($len) - 4); - $buf =~ s/\0/\\0/g; - if ($buf =~ s/^[\x2\x3]//) { - print STDERR $buf; - } else { - $buf =~ s/^\x1//; - print $buf; - } - } - } - ' + test-tool pkt-line unpack } # Converts base-16 data into base-8. The output is given as a sequence of diff --git a/t/test-lib.sh b/t/test-lib.sh index 7036f83b33..9e26860544 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -1509,6 +1509,8 @@ parisc* | hppa*) ;; esac +test_set_prereq REFFILES + ( COLUMNS=1 && test $COLUMNS = 1 ) && test_set_prereq COLUMNS_CAN_BE_1 test -z "$NO_PERL" && test_set_prereq PERL test -z "$NO_PTHREADS" && test_set_prereq PTHREADS diff --git a/transport.c b/transport.c index 50f5830eb6..17e9629710 100644 --- a/transport.c +++ b/transport.c @@ -147,9 +147,11 @@ static struct ref *get_refs_from_bundle(struct transport *transport, transport->hash_algo = data->header.hash_algo; for (i = 0; i < data->header.references.nr; i++) { - struct ref_list_entry *e = data->header.references.list + i; - struct ref *ref = alloc_ref(e->name); - oidcpy(&ref->old_oid, &e->oid); + struct string_list_item *e = data->header.references.items + i; + const char *name = e->string; + struct ref *ref = alloc_ref(name); + struct object_id *oid = e->util; + oidcpy(&ref->old_oid, oid); ref->next = result; result = ref; } @@ -175,6 +177,7 @@ static int close_bundle(struct transport *transport) struct bundle_transport_data *data = transport->data; if (data->fd > 0) close(data->fd); + bundle_header_release(&data->header); free(data); return 0; } @@ -1052,7 +1055,7 @@ struct transport *transport_get(struct remote *remote, const char *url) struct transport *ret = xcalloc(1, sizeof(*ret)); ret->progress = isatty(2); - string_list_init(&ret->pack_lockfiles, 1); + string_list_init_dup(&ret->pack_lockfiles); if (!remote) BUG("No remote provided to transport_get()"); @@ -1081,6 +1084,7 @@ struct transport *transport_get(struct remote *remote, const char *url) die(_("git-over-rsync is no longer supported")); } else if (url_is_local_not_ssh(url) && is_file(url) && is_bundle(url, 1)) { struct bundle_transport_data *data = xcalloc(1, sizeof(*data)); + bundle_header_init(&data->header); transport_check_allowed("file"); ret->data = data; ret->vtable = &bundle_vtable; diff --git a/unpack-trees.c b/unpack-trees.c index f88a69f8e7..0a5135ab39 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -600,6 +600,13 @@ static void mark_ce_used(struct cache_entry *ce, struct unpack_trees_options *o) { ce->ce_flags |= CE_UNPACKED; + /* + * If this is a sparse directory, don't advance cache_bottom. + * That will be advanced later using the cache-tree data. + */ + if (S_ISSPARSEDIR(ce->ce_mode)) + return; + if (o->cache_bottom < o->src_index->cache_nr && o->src_index->cache[o->cache_bottom] == ce) { int bottom = o->cache_bottom; @@ -797,7 +804,7 @@ static int traverse_by_cache_tree(int pos, int nr_entries, int nr_names, BUG("We need cache-tree to do this optimization"); /* - * Do what unpack_callback() and unpack_nondirectories() normally + * Do what unpack_callback() and unpack_single_entry() normally * do. But we walk all paths in an iterative loop instead. * * D/F conflicts and higher stage entries are not a concern @@ -976,6 +983,7 @@ static int do_compare_entry(const struct cache_entry *ce, int pathlen, ce_len; const char *ce_name; int cmp; + unsigned ce_mode; /* * If we have not precomputed the traverse path, it is quicker @@ -998,7 +1006,8 @@ static int do_compare_entry(const struct cache_entry *ce, ce_len -= pathlen; ce_name = ce->name + pathlen; - return df_name_compare(ce_name, ce_len, S_IFREG, name, namelen, mode); + ce_mode = S_ISSPARSEDIR(ce->ce_mode) ? S_IFDIR : S_IFREG; + return df_name_compare(ce_name, ce_len, ce_mode, name, namelen, mode); } static int compare_entry(const struct cache_entry *ce, const struct traverse_info *info, const struct name_entry *n) @@ -1008,6 +1017,16 @@ static int compare_entry(const struct cache_entry *ce, const struct traverse_inf return cmp; /* + * At this point, we know that we have a prefix match. If ce + * is a sparse directory, then allow an exact match. This only + * works when the input name is a directory, since ce->name + * ends in a directory separator. + */ + if (S_ISSPARSEDIR(ce->ce_mode) && + ce->ce_namelen == traverse_path_len(info, tree_entry_len(n)) + 1) + return 0; + + /* * Even if the beginning compared identically, the ce should * compare as bigger than a directory leading up to it! */ @@ -1033,13 +1052,15 @@ static struct cache_entry *create_ce_entry(const struct traverse_info *info, const struct name_entry *n, int stage, struct index_state *istate, - int is_transient) + int is_transient, + int is_sparse_directory) { size_t len = traverse_path_len(info, tree_entry_len(n)); + size_t alloc_len = is_sparse_directory ? len + 1 : len; struct cache_entry *ce = is_transient ? - make_empty_transient_cache_entry(len, NULL) : - make_empty_cache_entry(istate, len); + make_empty_transient_cache_entry(alloc_len, NULL) : + make_empty_cache_entry(istate, alloc_len); ce->ce_mode = create_ce_mode(n->mode); ce->ce_flags = create_ce_flags(stage); @@ -1048,6 +1069,13 @@ static struct cache_entry *create_ce_entry(const struct traverse_info *info, /* len+1 because the cache_entry allocates space for NUL */ make_traverse_path(ce->name, len + 1, info, n->path, n->pathlen); + if (is_sparse_directory) { + ce->name[len] = '/'; + ce->name[len + 1] = '\0'; + ce->ce_namelen++; + ce->ce_flags |= CE_SKIP_WORKTREE; + } + return ce; } @@ -1056,21 +1084,28 @@ static struct cache_entry *create_ce_entry(const struct traverse_info *info, * without actually calling it. If you change the logic here you may need to * check and change there as well. */ -static int unpack_nondirectories(int n, unsigned long mask, - unsigned long dirmask, - struct cache_entry **src, - const struct name_entry *names, - const struct traverse_info *info) +static int unpack_single_entry(int n, unsigned long mask, + unsigned long dirmask, + struct cache_entry **src, + const struct name_entry *names, + const struct traverse_info *info) { int i; struct unpack_trees_options *o = info->data; unsigned long conflicts = info->df_conflicts | dirmask; - /* Do we have *only* directories? Nothing to do */ if (mask == dirmask && !src[0]) return 0; /* + * When we have a sparse directory entry for src[0], + * then this isn't necessarily a directory-file conflict. + */ + if (mask == dirmask && src[0] && + S_ISSPARSEDIR(src[0]->ce_mode)) + conflicts = 0; + + /* * Ok, we've filled in up to any potential index entry in src[0], * now do the rest. */ @@ -1099,7 +1134,9 @@ static int unpack_nondirectories(int n, unsigned long mask, * not stored in the index. otherwise construct the * cache entry from the index aware logic. */ - src[i + o->merge] = create_ce_entry(info, names + i, stage, &o->result, o->merge); + src[i + o->merge] = create_ce_entry(info, names + i, stage, + &o->result, o->merge, + bit & dirmask); } if (o->merge) { @@ -1203,16 +1240,71 @@ static int find_cache_pos(struct traverse_info *info, return -1; } +/* + * Given a sparse directory entry 'ce', compare ce->name to + * info->name + '/' + p->path + '/' if info->name is non-empty. + * Compare ce->name to p->path + '/' otherwise. Note that + * ce->name must end in a trailing '/' because it is a sparse + * directory entry. + */ +static int sparse_dir_matches_path(const struct cache_entry *ce, + struct traverse_info *info, + const struct name_entry *p) +{ + assert(S_ISSPARSEDIR(ce->ce_mode)); + assert(ce->name[ce->ce_namelen - 1] == '/'); + + if (info->namelen) + return ce->ce_namelen == info->namelen + p->pathlen + 2 && + ce->name[info->namelen] == '/' && + !strncmp(ce->name, info->name, info->namelen) && + !strncmp(ce->name + info->namelen + 1, p->path, p->pathlen); + return ce->ce_namelen == p->pathlen + 1 && + !strncmp(ce->name, p->path, p->pathlen); +} + static struct cache_entry *find_cache_entry(struct traverse_info *info, const struct name_entry *p) { + struct cache_entry *ce; int pos = find_cache_pos(info, p->path, p->pathlen); struct unpack_trees_options *o = info->data; if (0 <= pos) return o->src_index->cache[pos]; - else + + /* + * Check for a sparse-directory entry named "path/". + * Due to the input p->path not having a trailing + * slash, the negative 'pos' value overshoots the + * expected position, hence "-2" instead of "-1". + */ + pos = -pos - 2; + + if (pos < 0 || pos >= o->src_index->cache_nr) return NULL; + + /* + * Due to lexicographic sorting and sparse directory + * entries ending with a trailing slash, our path as a + * sparse directory (e.g "subdir/") and our path as a + * file (e.g. "subdir") might be separated by other + * paths (e.g. "subdir-"). + */ + while (pos >= 0) { + ce = o->src_index->cache[pos]; + + if (strncmp(ce->name, p->path, p->pathlen)) + return NULL; + + if (S_ISSPARSEDIR(ce->ce_mode) && + sparse_dir_matches_path(ce, info, p)) + return ce; + + pos--; + } + + return NULL; } static void debug_path(struct traverse_info *info) @@ -1248,6 +1340,21 @@ static void debug_unpack_callback(int n, } /* + * Returns true if and only if the given cache_entry is a + * sparse-directory entry that matches the given name_entry + * from the tree walk at the given traverse_info. + */ +static int is_sparse_directory_entry(struct cache_entry *ce, + struct name_entry *name, + struct traverse_info *info) +{ + if (!ce || !name || !S_ISSPARSEDIR(ce->ce_mode)) + return 0; + + return sparse_dir_matches_path(ce, info, name); +} + +/* * Note that traverse_by_cache_tree() duplicates some logic in this function * without actually calling it. If you change the logic here you may need to * check and change there as well. @@ -1303,7 +1410,7 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str } } - if (unpack_nondirectories(n, mask, dirmask, src, names, info) < 0) + if (unpack_single_entry(n, mask, dirmask, src, names, info) < 0) return -1; if (o->merge && src[0]) { @@ -1333,9 +1440,12 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str } } - if (traverse_trees_recursive(n, dirmask, mask & ~dirmask, - names, info) < 0) + if (!is_sparse_directory_entry(src[0], names, info) && + traverse_trees_recursive(n, dirmask, mask & ~dirmask, + names, info) < 0) { return -1; + } + return mask; } diff --git a/worktree.c b/worktree.c index 237517baee..092a4f92ad 100644 --- a/worktree.c +++ b/worktree.c @@ -265,6 +265,7 @@ const char *worktree_prune_reason(struct worktree *wt, timestamp_t expire) } /* convenient wrapper to deal with NULL strbuf */ +__attribute__((format (printf, 2, 3))) static void strbuf_addf_gently(struct strbuf *buf, const char *fmt, ...) { va_list params; diff --git a/wt-status.c b/wt-status.c index 42b6735716..eaed30eafb 100644 --- a/wt-status.c +++ b/wt-status.c @@ -639,7 +639,7 @@ static void wt_status_collect_changes_index(struct wt_status *s) * mode by passing a command line option we do not ignore any * changed submodule SHA-1s when comparing index and HEAD, no * matter what is configured. Otherwise the user won't be - * shown any submodules she manually added (and which are + * shown any submodules manually added (and which are * staged to be committed), which would be really confusing. */ handle_ignore_submodules_arg(&rev.diffopt, "dirty"); @@ -657,6 +657,36 @@ static void wt_status_collect_changes_index(struct wt_status *s) clear_pathspec(&rev.prune_data); } +static int add_file_to_list(const struct object_id *oid, + struct strbuf *base, const char *path, + unsigned int mode, void *context) +{ + struct string_list_item *it; + struct wt_status_change_data *d; + struct wt_status *s = context; + struct strbuf full_name = STRBUF_INIT; + + if (S_ISDIR(mode)) + return READ_TREE_RECURSIVE; + + strbuf_add(&full_name, base->buf, base->len); + strbuf_addstr(&full_name, path); + it = string_list_insert(&s->change, full_name.buf); + d = it->util; + if (!d) { + CALLOC_ARRAY(d, 1); + it->util = d; + } + + d->index_status = DIFF_STATUS_ADDED; + /* Leave {mode,oid}_head zero for adds. */ + d->mode_index = mode; + oidcpy(&d->oid_index, oid); + s->committable = 1; + strbuf_release(&full_name); + return 0; +} + static void wt_status_collect_changes_initial(struct wt_status *s) { struct index_state *istate = s->repo->index; @@ -671,6 +701,27 @@ static void wt_status_collect_changes_initial(struct wt_status *s) continue; if (ce_intent_to_add(ce)) continue; + if (S_ISSPARSEDIR(ce->ce_mode)) { + /* + * This is a sparse directory entry, so we want to collect all + * of the added files within the tree. This requires recursively + * expanding the trees to find the elements that are new in this + * tree and marking them with DIFF_STATUS_ADDED. + */ + struct strbuf base = STRBUF_INIT; + struct pathspec ps = { 0 }; + struct tree *tree = lookup_tree(istate->repo, &ce->oid); + + ps.recursive = 1; + ps.has_wildcard = 1; + ps.max_depth = -1; + + strbuf_add(&base, ce->name, ce->ce_namelen); + read_tree_at(istate->repo, tree, &base, &ps, + add_file_to_list, s); + continue; + } + it = string_list_insert(&s->change, ce->name); d = it->util; if (!d) { @@ -699,14 +750,13 @@ static void wt_status_collect_changes_initial(struct wt_status *s) static void wt_status_collect_untracked(struct wt_status *s) { int i; - struct dir_struct dir; + struct dir_struct dir = DIR_INIT; uint64_t t_begin = getnanotime(); struct index_state *istate = s->repo->index; if (!s->show_untracked_files) return; - dir_init(&dir); if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES) dir.flags |= DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES; @@ -1493,9 +1543,12 @@ static void show_sparse_checkout_in_use(struct wt_status *s, if (s->state.sparse_checkout_percentage == SPARSE_CHECKOUT_DISABLED) return; - status_printf_ln(s, color, - _("You are in a sparse checkout with %d%% of tracked files present."), - s->state.sparse_checkout_percentage); + if (s->state.sparse_checkout_percentage == SPARSE_CHECKOUT_SPARSE_INDEX) + status_printf_ln(s, color, _("You are in a sparse checkout.")); + else + status_printf_ln(s, color, + _("You are in a sparse checkout with %d%% of tracked files present."), + s->state.sparse_checkout_percentage); wt_longstatus_print_trailer(s); } @@ -1653,6 +1706,11 @@ static void wt_status_check_sparse_checkout(struct repository *r, return; } + if (r->index->sparse_index) { + state->sparse_checkout_percentage = SPARSE_CHECKOUT_SPARSE_INDEX; + return; + } + for (i = 0; i < r->index->cache_nr; i++) { struct cache_entry *ce = r->index->cache[i]; if (ce_skip_worktree(ce)) diff --git a/wt-status.h b/wt-status.h index 0d32799b28..ab9cc9d8f0 100644 --- a/wt-status.h +++ b/wt-status.h @@ -78,6 +78,7 @@ enum wt_status_format { }; #define SPARSE_CHECKOUT_DISABLED -1 +#define SPARSE_CHECKOUT_SPARSE_INDEX -2 struct wt_status_state { int merge_in_progress; diff --git a/xdiff-interface.c b/xdiff-interface.c index 609615db2c..75b32aef51 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -31,29 +31,36 @@ static int xdiff_out_hunk(void *priv_, return 0; } -static void consume_one(void *priv_, char *s, unsigned long size) +static int consume_one(void *priv_, char *s, unsigned long size) { struct xdiff_emit_state *priv = priv_; char *ep; while (size) { unsigned long this_size; + int ret; ep = memchr(s, '\n', size); this_size = (ep == NULL) ? size : (ep - s + 1); - priv->line_fn(priv->consume_callback_data, s, this_size); + ret = priv->line_fn(priv->consume_callback_data, s, this_size); + if (ret) + return ret; size -= this_size; s += this_size; } + return 0; } static int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf) { struct xdiff_emit_state *priv = priv_; int i; + int stop = 0; if (!priv->line_fn) return 0; for (i = 0; i < nbuf; i++) { + if (stop) + return 1; if (mb[i].ptr[mb[i].size-1] != '\n') { /* Incomplete line */ strbuf_add(&priv->remainder, mb[i].ptr, mb[i].size); @@ -62,17 +69,21 @@ static int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf) /* we have a complete line */ if (!priv->remainder.len) { - consume_one(priv, mb[i].ptr, mb[i].size); + stop = consume_one(priv, mb[i].ptr, mb[i].size); continue; } strbuf_add(&priv->remainder, mb[i].ptr, mb[i].size); - consume_one(priv, priv->remainder.buf, priv->remainder.len); + stop = consume_one(priv, priv->remainder.buf, priv->remainder.len); strbuf_reset(&priv->remainder); } + if (stop) + return -1; if (priv->remainder.len) { - consume_one(priv, priv->remainder.buf, priv->remainder.len); + stop = consume_one(priv, priv->remainder.buf, priv->remainder.len); strbuf_reset(&priv->remainder); } + if (stop) + return -1; return 0; } @@ -115,12 +126,6 @@ int xdi_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t co return xdl_diff(&a, &b, xpp, xecfg, xecb); } -void discard_hunk_line(void *priv, - long ob, long on, long nb, long nn, - const char *func, long funclen) -{ -} - int xdi_diff_outf(mmfile_t *mf1, mmfile_t *mf2, xdiff_emit_hunk_fn hunk_fn, xdiff_emit_line_fn line_fn, diff --git a/xdiff-interface.h b/xdiff-interface.h index 93df26900c..4301a7eef2 100644 --- a/xdiff-interface.h +++ b/xdiff-interface.h @@ -11,7 +11,28 @@ */ #define MAX_XDIFF_SIZE (1024UL * 1024 * 1023) -typedef void (*xdiff_emit_line_fn)(void *, char *, unsigned long); +/** + * The `xdiff_emit_line_fn` function can return 1 to abort early, or 0 + * to continue processing. Note that doing so is an all-or-nothing + * affair, as returning 1 will return all the way to the top-level, + * e.g. the xdi_diff_outf() call to generate the diff. + * + * Thus returning 1 means you won't be getting any more diff lines. If + * you need something in-between those two options you'll to use + * `xdl_emit_hunk_consume_func_t` and implement your own version of + * xdl_emit_diff(). + * + * We may extend the interface in the future to understand other more + * granular return values. While you should return 1 to exit early, + * doing so will currently make your early return indistinguishable + * from an error internal to xdiff, xdiff itself will see that + * non-zero return and translate it to -1. + * + * See "diff_grep" in diffcore-pickaxe.c for a trick to work around + * this, i.e. using the "consume_callback_data" to note the desired + * early return. + */ +typedef int (*xdiff_emit_line_fn)(void *, char *, unsigned long); typedef void (*xdiff_emit_hunk_fn)(void *data, long old_begin, long old_nr, long new_begin, long new_nr, @@ -33,14 +54,6 @@ int git_xmerge_config(const char *var, const char *value, void *cb); extern int git_xmerge_style; /* - * Can be used as a no-op hunk_fn for xdi_diff_outf(), since a NULL - * one just sends the hunk line to the line_fn callback). - */ -void discard_hunk_line(void *priv, - long ob, long on, long nb, long nn, - const char *func, long funclen); - -/* * Compare the strings l1 with l2 which are of size s1 and s2 respectively. * Returns 1 if the strings are deemed equal, 0 otherwise. * The `flags` given as XDF_WHITESPACE_FLAGS determine how white spaces diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h index 7a04605146..b29deca5de 100644 --- a/xdiff/xdiff.h +++ b/xdiff/xdiff.h @@ -50,6 +50,7 @@ extern "C" { /* xdemitconf_t.flags */ #define XDL_EMIT_FUNCNAMES (1 << 0) +#define XDL_EMIT_NO_HUNK_HDR (1 << 1) #define XDL_EMIT_FUNCCONTEXT (1 << 2) /* merge simplification levels */ diff --git a/xdiff/xemit.c b/xdiff/xemit.c index 9d7d6c5087..1cbf2b9829 100644 --- a/xdiff/xemit.c +++ b/xdiff/xemit.c @@ -278,7 +278,8 @@ pre_context_calculation: s1 - 1, funclineprev); funclineprev = s1 - 1; } - if (xdl_emit_hunk_hdr(s1 + 1, e1 - s1, s2 + 1, e2 - s2, + if (!(xecfg->flags & XDL_EMIT_NO_HUNK_HDR) && + xdl_emit_hunk_hdr(s1 + 1, e1 - s1, s2 + 1, e2 - s2, func_line.buf, func_line.len, ecb) < 0) return -1; |
