diff options
101 files changed, 1811 insertions, 523 deletions
diff --git a/.github/workflows/check-whitespace.yml b/.github/workflows/check-whitespace.yml index f1483059c76..8c4358d805c 100644 --- a/.github/workflows/check-whitespace.yml +++ b/.github/workflows/check-whitespace.yml @@ -12,15 +12,9 @@ jobs: check-whitespace: runs-on: ubuntu-latest steps: - - name: Set commit count - shell: bash - run: echo "COMMIT_DEPTH=$((1+$COMMITS))" >>$GITHUB_ENV - env: - COMMITS: ${{ github.event.pull_request.commits }} - - uses: actions/checkout@v2 with: - fetch-depth: ${{ env.COMMIT_DEPTH }} + fetch-depth: 0 - name: git log --check id: check_out @@ -47,25 +41,9 @@ jobs: echo "${dash} ${etc}" ;; esac - done <<< $(git log --check --pretty=format:"---% h% s" -${{github.event.pull_request.commits}}) + done <<< $(git log --check --pretty=format:"---% h% s" ${{github.event.pull_request.base.sha}}..) if test -n "${log}" then - echo "::set-output name=checkout::"${log}"" exit 2 fi - - - name: Add Check Output as Comment - uses: actions/github-script@v3 - id: add-comment - env: - log: ${{ steps.check_out.outputs.checkout }} - with: - script: | - await github.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: `Whitespace errors found in workflow ${{ github.workflow }}:\n\n\`\`\`\n${process.env.log.replace(/\\n/g, "\n")}\n\`\`\`` - }) - if: ${{ failure() }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 73856bafc9d..47876a4f02e 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 e3af089ecf2..711cb9171e0 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 d4c56de5cb7..94b75f4b611 100644 --- a/Documentation/RelNotes/2.33.0.txt +++ b/Documentation/RelNotes/2.33.0.txt @@ -30,6 +30,12 @@ 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. + + * "git worktree add --lock" learned to record why the worktree is + locked with a custom message. + Performance, Internal Implementation, Development Support etc. @@ -54,6 +60,39 @@ Performance, Internal Implementation, Development Support etc. * Code cleanup around struct_type_init() functions. + * "git send-email" optimization. + + * GitHub Actions / CI update. + (merge 0dc787a9f2 js/ci-windows-update later to maint). + + * Object accesses in repositories with many alternate object store + have been optimized. + + * "git log" has been optimized not to waste cycles to load ref + decoration data that may not be needed. + + * Many "printf"-like helper functions we have have been annotated + with __attribute__() to catch placeholder/parameter mismatches. + + * Tests that cover protocol bits have been updated and helpers + used there have been consolidated. + + * The CI gained a new job to run "make sparse" check. + + * "git status" codepath learned to work with sparsely populated index + without hydrating it fully. + + * A guideline for gender neutral documentation has been added. + + * Documentation on "git diff -l<n>" and diff.renameLimit have been + updated, and the defaults for these limits have been raised. + + * The completion support used to offer alternate spelling of options + that exist only for compatibility, which has been corrected. + + * "TEST_OUTPUT_DIRECTORY=there make test" failed to work, which has + been corrected. + Fixes since v2.32 ----------------- @@ -175,6 +214,15 @@ Fixes since v2.32 and adds to guidelines to do so. (merge 46a237f42f ds/gender-neutral-doc later to maint). + * "git commit --allow-empty-message" won't abort the operation upon + an empty message, but the hint shown in the editor said otherwise. + (merge 6f70f00b4f hj/commit-allow-empty-message later to maint). + + * The code that gives an error message in "git multi-pack-index" when + no subcommand is given tried to print a NULL pointer as a strong, + which has been corrected. + (merge 88617d11f9 tb/reverse-midx 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). @@ -208,3 +256,8 @@ Fixes since v2.32 (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). + (merge 8232a0ff48 dl/packet-read-response-end-fix later to maint). + (merge eb448631fb dl/diff-merge-base later to maint). + (merge c510928a25 hn/refs-debug-empty-prefix later to maint). diff --git a/Documentation/config/diff.txt b/Documentation/config/diff.txt index 2d3331f55c2..32f84838ac1 100644 --- a/Documentation/config/diff.txt +++ b/Documentation/config/diff.txt @@ -118,9 +118,10 @@ diff.orderFile:: relative to the top of the working tree. diff.renameLimit:: - The number of files to consider when performing the copy/rename - detection; equivalent to the 'git diff' option `-l`. This setting - has no effect if rename detection is turned off. + The number of files to consider in the exhaustive portion of + copy/rename detection; equivalent to the 'git diff' option + `-l`. If not set, the default value is currently 1000. This + setting has no effect if rename detection is turned off. diff.renames:: Whether and how Git detects renames. If set to "false", diff --git a/Documentation/config/merge.txt b/Documentation/config/merge.txt index 6b66c83eabe..e27cc639447 100644 --- a/Documentation/config/merge.txt +++ b/Documentation/config/merge.txt @@ -33,10 +33,12 @@ merge.verifySignatures:: include::fmt-merge-msg.txt[] merge.renameLimit:: - The number of files to consider when performing rename detection - during a merge; if not specified, defaults to the value of - diff.renameLimit. This setting has no effect if rename detection - is turned off. + The number of files to consider in the exhaustive portion of + rename detection during a merge. If not specified, defaults + to the value of diff.renameLimit. If neither + merge.renameLimit nor diff.renameLimit are specified, + currently defaults to 7000. This setting has no effect if + rename detection is turned off. merge.renames:: Whether Git detects renames. If set to "false", rename detection diff --git a/Documentation/config/sendemail.txt b/Documentation/config/sendemail.txt index cbc5af42fdf..50baa5d6bfb 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/config/submodule.txt b/Documentation/config/submodule.txt index d7a63c8c12b..ee454f8126a 100644 --- a/Documentation/config/submodule.txt +++ b/Documentation/config/submodule.txt @@ -58,8 +58,9 @@ submodule.active:: commands. See linkgit:gitsubmodules[7] for details. submodule.recurse:: - Specifies if commands recurse into submodules by default. This - applies to all commands that have a `--recurse-submodules` option + A boolean indicating if commands should enable the `--recurse-submodules` + option by default. + Applies to all commands that support this option (`checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`, `reset`, `restore` and `switch`) except `clone` and `ls-files`. Defaults to false. diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 32e6dee5ac3..0aebe832057 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -588,11 +588,17 @@ When used together with `-B`, omit also the preimage in the deletion part of a delete/create pair. -l<num>:: - The `-M` and `-C` options require O(n^2) processing time where n - is the number of potential rename/copy targets. This - option prevents rename/copy detection from running if - the number of rename/copy targets exceeds the specified - number. + The `-M` and `-C` options involve some preliminary steps that + can detect subsets of renames/copies cheaply, followed by an + exhaustive fallback portion that compares all remaining + unpaired destinations to all relevant sources. (For renames, + only remaining unpaired sources are relevant; for copies, all + original sources are relevant.) For N sources and + destinations, this exhaustive check is O(N^2). This option + prevents the exhaustive portion of rename/copy detection from + running if the number of source/destination files involved + exceeds the specified number. Defaults to diff.renameLimit. + Note that a value of 0 is treated as unlimited. ifndef::git-format-patch[] --diff-filter=[(A|C|D|M|R|T|U|X|B)...[*]]:: diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index 5cddadafd2e..2dc4bae6da9 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -71,6 +71,7 @@ codes are: On success, the command returns the exit code 0. +[[OPTIONS]] OPTIONS ------- @@ -143,7 +144,13 @@ See also <<FILES>>. -f config-file:: --file config-file:: - Use the given config file instead of the one specified by GIT_CONFIG. + For writing options: write to the specified file rather than the + repository `.git/config`. ++ +For reading options: read only from the specified file rather than from all +available files. ++ +See also <<FILES>>. --blob blob:: Similar to `--file` but use the given blob instead of a file. E.g. @@ -325,21 +332,14 @@ All writing options will per default write to the repository specific configuration file. Note that this also affects options like `--replace-all` and `--unset`. *'git config' will only ever change one file at a time*. -You can override these rules either by command-line options or by environment -variables. The `--global`, `--system` and `--worktree` options will limit -the file used to the global, system-wide or per-worktree file respectively. -The `GIT_CONFIG` environment variable has a similar effect, but you -can specify any filename you want. +You can override these rules using the `--global`, `--system`, +`--local`, `--worktree`, and `--file` command-line options; see +<<OPTIONS>> above. ENVIRONMENT ----------- -GIT_CONFIG:: - Take the configuration from the given file instead of .git/config. - Using the "--global" option forces this to ~/.gitconfig. Using the - "--system" option forces this to $(prefix)/etc/gitconfig. - GIT_CONFIG_GLOBAL:: GIT_CONFIG_SYSTEM:: Take the configuration from the given files instead from global or @@ -367,6 +367,12 @@ This is useful for cases where you want to spawn multiple git commands with a common configuration but cannot depend on a configuration file, for example when writing scripts. +GIT_CONFIG:: + If no `--file` option is provided to `git config`, use the file + given by `GIT_CONFIG` as if it were provided via `--file`. This + variable has no effect on other Git commands, and is mostly for + historical compatibility; there is generally no reason to use it + instead of the `--file` option. [[EXAMPLES]] EXAMPLES diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index 7f4c8a8ce7f..6236c75c9b8 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-worktree.txt b/Documentation/git-worktree.txt index 66e67e6cbfa..8a7cbdd19c1 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 53e7d5c914d..f8a1fc20147 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 5bf2a85f69d..24569b06d19 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:: @@ -726,6 +726,7 @@ 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 @@ -850,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 @@ -945,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 @@ -2687,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 2fad92ca373..8c41cdfe39b 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); diff --git a/builtin/am.c b/builtin/am.c index 0b2d886c81b..0c2ad96b70e 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 9d9540a0abf..f184eaeac6d 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/commit.c b/builtin/commit.c index 190d215d43b..7436262aae2 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 2d87c37a176..dd8ce688ba7 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/index-pack.c b/builtin/index-pack.c index 3fbc5d70777..8336466865c 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 516a1142ddf..3d7717ba5ca 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/multi-pack-index.c b/builtin/multi-pack-index.c index 5d3ea445fdb..8ff0dee2ecb 100644 --- a/builtin/multi-pack-index.c +++ b/builtin/multi-pack-index.c @@ -176,8 +176,8 @@ int cmd_multi_pack_index(int argc, const char **argv, else if (!strcmp(argv[0], "expire")) return cmd_multi_pack_index_expire(argc, argv); else { -usage: error(_("unrecognized subcommand: %s"), argv[0]); +usage: usage_with_options(builtin_multi_pack_index_usage, builtin_multi_pack_index_options); } diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index a34742513ac..2d1f97e1ca7 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/rev-list.c b/builtin/rev-list.c index 7677b1af5a4..36cb909ebaa 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/worktree.c b/builtin/worktree.c index 976bf8ed063..0d0a80da61f 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); @@ -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 00000000000..b0c65d810f5 --- /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 00000000000..fe4587087e8 --- /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 67852d0d37f..5772081b6e5 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/commit-graph.c b/commit-graph.c index 1a2602da618..3860a0d8477 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; diff --git a/contrib/credential/osxkeychain/git-credential-osxkeychain.c b/contrib/credential/osxkeychain/git-credential-osxkeychain.c index bcd3f575a3e..0b44a9b7cc6 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 5bdad41de1f..5091048f9c6 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/diff-lib.c b/diff-lib.c index c2ac9250fe9..f9eadc4fc1a 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) @@ -35,7 +35,7 @@ static int diff_detect_rename_default; static int diff_indent_heuristic = 1; -static int diff_rename_limit_default = 400; +static int diff_rename_limit_default = 1000; static int diff_suppress_blank_empty; static int diff_use_color_default = -1; static int diff_color_moved_default; @@ -6295,7 +6295,7 @@ static int is_summary_empty(const struct diff_queue_struct *q) } static const char rename_limit_warning[] = -N_("inexact rename detection was skipped due to too many files."); +N_("exhaustive rename detection was skipped due to too many files."); static const char degrade_cc_to_c_warning[] = N_("only found copies from modified paths due to too many files."); diff --git a/diffcore-rename.c b/diffcore-rename.c index 4ef0459cfb5..2618bb07c1f 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -1091,7 +1091,7 @@ static int too_many_rename_candidates(int num_destinations, int num_sources, * memory for the matrix anyway. */ if (rename_limit <= 0) - rename_limit = 32767; + return 0; /* treat as unlimited */ if (st_mult(num_destinations, num_sources) <= st_mult(rename_limit, rename_limit)) return 0; @@ -78,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) @@ -1370,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, @@ -1391,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; @@ -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. diff --git a/gettext.c b/gettext.c index af2413b47e8..bb5ba1fe7cc 100644 --- a/gettext.c +++ b/gettext.c @@ -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-send-email.perl b/git-send-email.perl index 7ba0b3433d7..e65d969d0bb 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(); @@ -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 bb085d66d10..a0540ba5cf4 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/log-tree.c b/log-tree.c index 7b823786c2c..6dc4412268b 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; diff --git a/mailmap.c b/mailmap.c index d1f7c0d272d..462b3956340 100644 --- a/mailmap.c +++ b/mailmap.c @@ -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 8cfa0ccd3c8..ec0c5904211 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -530,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; @@ -2562,7 +2563,7 @@ static void detect_regular_renames(struct merge_options *opt, diff_opts.detect_rename = DIFF_DETECT_RENAME; diff_opts.rename_limit = opt->rename_limit; if (opt->rename_limit <= 0) - diff_opts.rename_limit = 1000; + diff_opts.rename_limit = 7000; diff_opts.rename_score = opt->rename_score; diff_opts.show_rename_progress = opt->show_rename_progress; diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; diff --git a/merge-recursive.c b/merge-recursive.c index a67bf2acfa5..7008a90df5e 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -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; @@ -1879,7 +1880,7 @@ static struct diff_queue_struct *get_diffpairs(struct merge_options *opt, */ if (opts.detect_rename > DIFF_DETECT_RENAME) opts.detect_rename = DIFF_DETECT_RENAME; - opts.rename_limit = (opt->rename_limit >= 0) ? opt->rename_limit : 1000; + opts.rename_limit = (opt->rename_limit >= 0) ? opt->rename_limit : 7000; opts.rename_score = opt->rename_score; opts.show_rename_progress = opt->show_rename_progress; opts.output_format = DIFF_FORMAT_NO_OUTPUT; @@ -1172,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; diff --git a/object-file.c b/object-file.c index ecca5a8da00..3d27dc8dea3 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); @@ -1178,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; @@ -2454,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 64202de60b1..3263c19457f 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 ec32c23dcb5..e679acc4c31 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 00000000000..7eb0e9ba050 --- /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 00000000000..77898f510a1 --- /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-bitmap.c b/pack-bitmap.c index bfc10148f56..c8cb9ef13b3 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -809,8 +809,9 @@ static void filter_bitmap_exclude_type(struct bitmap_index *bitmap_git, tips = find_tip_objects(bitmap_git, tip_objects, type); /* - * We can use the blob type-bitmap to work in whole words - * for the objects that are actually in the bitmapped packfile. + * We can use the type-level bitmap for 'type' to work in whole + * words for the objects that are actually in the bitmapped + * packfile. */ for (i = 0, init_type_iterator(&it, bitmap_git, type); i < to_filter->word_alloc && ewah_iterator_next(&mask, &it); @@ -821,9 +822,9 @@ static void filter_bitmap_exclude_type(struct bitmap_index *bitmap_git, } /* - * Clear any blobs that weren't in the packfile (and so would not have - * been caught by the loop above. We'll have to check them - * individually. + * Clear any objects that weren't in the packfile (and so would + * not have been caught by the loop above. We'll have to check + * them individually. */ for (i = 0; i < eindex->count; i++) { uint32_t pos = i + bitmap_git->pack->num_objects; diff --git a/parse-options.c b/parse-options.c index e6f56768ca5..2abff136a17 100644 --- a/parse-options.c +++ b/parse-options.c @@ -585,7 +585,7 @@ static int show_gitcomp(const struct option *opts, int show_all) if (!opts->long_name) continue; if (!show_all && - (opts->flags & (PARSE_OPT_HIDDEN | PARSE_OPT_NOCOMPLETE))) + (opts->flags & (PARSE_OPT_HIDDEN | PARSE_OPT_NOCOMPLETE | PARSE_OPT_FROM_ALIAS))) continue; switch (opts->type) { diff --git a/perl/Git.pm b/perl/Git.pm index 02eacef0c2a..090a7df63fc 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 98304ce374d..9f63eae2e64 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); /* @@ -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/reachable.c b/reachable.c index c59847257a5..84e3d0d75ed 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 ba2b012a6cb..46ccd66f34d 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; diff --git a/ref-filter.c b/ref-filter.c index 4db0e40ff4c..f45d3a1b26d 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; diff --git a/refs/debug.c b/refs/debug.c index 7db4abccc34..1a7a9e11cfa 100644 --- a/refs/debug.c +++ b/refs/debug.c @@ -232,7 +232,8 @@ debug_ref_iterator_begin(struct ref_store *ref_store, const char *prefix, struct debug_ref_iterator *diter = xcalloc(1, sizeof(*diter)); base_ref_iterator_init(&diter->base, &debug_ref_iterator_vtable, 1); diter->iter = res; - trace_printf_key(&trace_refs, "ref_iterator_begin: %s (0x%x)\n", prefix, flags); + trace_printf_key(&trace_refs, "ref_iterator_begin: \"%s\" (0x%x)\n", + prefix, flags); return &diter->base; } diff --git a/remote-curl.c b/remote-curl.c index 9d432c299a2..6c320d57045 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/revision.h b/revision.h index 5c5510d4220..fbb068da9fb 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/sequencer.c b/sequencer.c index 0bec01cf38e..7f07cd00f3f 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 de0aa4498c6..7701d7c20a1 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; diff --git a/sparse-index.c b/sparse-index.c index affc4048f27..53c8f711ccc 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); @@ -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, ...); /** diff --git a/t/helper/test-advise.c b/t/helper/test-advise.c index a7043df1d38..cb881139f73 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 00000000000..180ee28dd93 --- /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-pkt-line.c b/t/helper/test-pkt-line.c index 5e638f0b970..c5e052e5378 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-tool.c b/t/helper/test-tool.c index b21e8f15190..490ac026c51 100644 --- a/t/helper/test-tool.c +++ b/t/helper/test-tool.c @@ -43,6 +43,7 @@ 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 }, diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h index f845ced4b3a..f8dc266721f 100644 --- a/t/helper/test-tool.h +++ b/t/helper/test-tool.h @@ -32,6 +32,7 @@ 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); diff --git a/t/perf/aggregate.perl b/t/perf/aggregate.perl index 14e4cda287d..82c0df45533 100755 --- a/t/perf/aggregate.perl +++ b/t/perf/aggregate.perl @@ -58,6 +58,7 @@ sub usage { Options: --codespeed * Format output for Codespeed --reponame <str> * Send given reponame to codespeed + --results-dir <str> * Directory where test results are located --sort-by <str> * Sort output (only "regression" criteria is supported) --subsection <str> * Use results from given subsection @@ -91,11 +92,13 @@ sub sane_backticks { my (@dirs, %dirnames, %dirabbrevs, %prefixes, @tests, $codespeed, $sortby, $subsection, $reponame); +my $resultsdir = "test-results"; Getopt::Long::Configure qw/ require_order /; my $rc = GetOptions("codespeed" => \$codespeed, "reponame=s" => \$reponame, + "results-dir=s" => \$resultsdir, "sort-by=s" => \$sortby, "subsection=s" => \$subsection); usage() unless $rc; @@ -137,8 +140,6 @@ if (not @tests) { @tests = glob "p????-*.sh"; } -my $resultsdir = "test-results"; - if (! $subsection and exists $ENV{GIT_PERF_SUBSECTION} and $ENV{GIT_PERF_SUBSECTION} ne "") { diff --git a/t/perf/perf-lib.sh b/t/perf/perf-lib.sh index 601d9f67ddb..f5ed092ee59 100644 --- a/t/perf/perf-lib.sh +++ b/t/perf/perf-lib.sh @@ -45,7 +45,7 @@ export TEST_DIRECTORY TRASH_DIRECTORY GIT_BUILD_DIR GIT_TEST_CMP MODERN_GIT=$GIT_BUILD_DIR/bin-wrappers/git export MODERN_GIT -perf_results_dir=$TEST_OUTPUT_DIRECTORY/test-results +perf_results_dir=$TEST_RESULTS_DIR test -n "$GIT_PERF_SUBSECTION" && perf_results_dir="$perf_results_dir/$GIT_PERF_SUBSECTION" mkdir -p "$perf_results_dir" rm -f "$perf_results_dir"/$(basename "$0" .sh).subtests @@ -253,7 +253,10 @@ test_size () { # and does it after running everything) test_at_end_hook_ () { if test -z "$GIT_PERF_AGGREGATING_LATER"; then - ( cd "$TEST_DIRECTORY"/perf && ./aggregate.perl $(basename "$0") ) + ( + cd "$TEST_DIRECTORY"/perf && + ./aggregate.perl --results-dir="$TEST_RESULTS_DIR" $(basename "$0") + ) fi } diff --git a/t/perf/run b/t/perf/run index c7b86104e12..d19dec258a2 100755 --- a/t/perf/run +++ b/t/perf/run @@ -188,10 +188,10 @@ run_subsection () { if test -z "$GIT_PERF_SEND_TO_CODESPEED" then - ./aggregate.perl $codespeed_opt "$@" + ./aggregate.perl --results-dir="$TEST_RESULTS_DIR" $codespeed_opt "$@" else - json_res_file="test-results/$GIT_PERF_SUBSECTION/aggregate.json" - ./aggregate.perl --codespeed "$@" | tee "$json_res_file" + json_res_file=""$TEST_RESULTS_DIR"/$GIT_PERF_SUBSECTION/aggregate.json" + ./aggregate.perl --results-dir="$TEST_RESULTS_DIR" --codespeed "$@" | tee "$json_res_file" send_data_url="$GIT_PERF_SEND_TO_CODESPEED/result/add/json/" curl -v --request POST --data-urlencode "json=$(cat "$json_res_file")" "$send_data_url" fi @@ -203,10 +203,17 @@ get_var_from_env_or_config "GIT_PERF_SEND_TO_CODESPEED" "perf" "sendToCodespeed" cd "$(dirname $0)" . ../../GIT-BUILD-OPTIONS -mkdir -p test-results -get_subsections "perf" >test-results/run_subsections.names +if test -n "$TEST_OUTPUT_DIRECTORY" +then + TEST_RESULTS_DIR="$TEST_OUTPUT_DIRECTORY/test-results" +else + TEST_RESULTS_DIR=test-results +fi + +mkdir -p "$TEST_RESULTS_DIR" +get_subsections "perf" >"$TEST_RESULTS_DIR"/run_subsections.names -if test $(wc -l <test-results/run_subsections.names) -eq 0 +if test $(wc -l <"$TEST_RESULTS_DIR"/run_subsections.names) -eq 0 then if test -n "$GIT_PERF_SUBSECTION" then @@ -222,10 +229,10 @@ then ) elif test -n "$GIT_PERF_SUBSECTION" then - egrep "^$GIT_PERF_SUBSECTION\$" test-results/run_subsections.names >/dev/null || + egrep "^$GIT_PERF_SUBSECTION\$" "$TEST_RESULTS_DIR"/run_subsections.names >/dev/null || die "subsection '$GIT_PERF_SUBSECTION' not found in '$GIT_PERF_CONFIG_FILE'" - egrep "^$GIT_PERF_SUBSECTION\$" test-results/run_subsections.names | while read -r subsec + egrep "^$GIT_PERF_SUBSECTION\$" "$TEST_RESULTS_DIR"/run_subsections.names | while read -r subsec do ( GIT_PERF_SUBSECTION="$subsec" @@ -243,5 +250,5 @@ else echo "======== Run for subsection '$GIT_PERF_SUBSECTION' ========" run_subsection "$@" ) - done <test-results/run_subsections.names + done <"$TEST_RESULTS_DIR"/run_subsections.names fi diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 2c6e34b9478..cb87768513c 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 @@ -89,8 +106,13 @@ _run_sub_test_lib_test_common () { EOF cat >>"$name.sh" && export TEST_DIRECTORY && - TEST_OUTPUT_DIRECTORY=$(pwd) && - export TEST_OUTPUT_DIRECTORY && + # The child test re-sources GIT-BUILD-OPTIONS and may thus + # override the test output directory. We thus pass it as an + # explicit override to the child. + TEST_OUTPUT_DIRECTORY_OVERRIDE=$(pwd) && + export TEST_OUTPUT_DIRECTORY_OVERRIDE && + GIT_SKIP_TESTS=$skip && + export GIT_SKIP_TESTS && sane_unset GIT_TEST_FAIL_PREREQS && if test -z "$neg" then @@ -319,9 +341,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 +362,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 +386,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 +410,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 +427,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 00000000000..bfb1397d7b2 --- /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/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh index d028b73eba1..cabbd42e339 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/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index 96dfca15542..37ad79470fb 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/t4202-log.sh b/t/t4202-log.sh index 39e746fbcbe..9dfead936b7 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -1915,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/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh index 7609f1ea641..3d4d9f10c31 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh @@ -837,4 +837,9 @@ test_expect_success 'load reverse index when missing .idx, .pack' ' ) ' +test_expect_success 'usage shown without sub-command' ' + test_expect_code 129 git multi-pack-index 2>err && + ! test_i18ngrep "unrecognized subcommand" err +' + test_done diff --git a/t/t5411/once-0010-report-status-v1.sh b/t/t5411/once-0010-report-status-v1.sh index 1233a46eac5..297b10925d5 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 e5d3d15ba8d..05a58069b0c 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 82c31ab6cd8..b87ca06a585 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/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index 35a2f623925..41d0ca00b1c 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/t7500-commit-template-squash-signoff.sh b/t/t7500-commit-template-squash-signoff.sh index 7d02f79c0de..54c2082acb8 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/t7519-status-fsmonitor.sh b/t/t7519-status-fsmonitor.sh index 637391c6ce4..deea88d4431 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/t9001-send-email.sh b/t/t9001-send-email.sh index e9dc58f508d..57fc10e7f82 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/t9902-completion.sh b/t/t9902-completion.sh index cb057ef1613..11573936d58 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -2404,6 +2404,19 @@ test_expect_success 'sourcing the completion script clears cached --options' ' verbose test -z "$__gitcomp_builtin_notes_edit" ' +test_expect_success 'option aliases are not shown by default' ' + test_completion "git clone --recurs" "--recurse-submodules " +' + +test_expect_success 'option aliases are shown with GIT_COMPLETION_SHOW_ALL' ' + . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" && + GIT_COMPLETION_SHOW_ALL=1 && export GIT_COMPLETION_SHOW_ALL && + test_completion "git clone --recurs" <<-\EOF + --recurse-submodules Z + --recursive Z + EOF +' + test_expect_success '__git_complete' ' unset -f __git_wrap__git_main && diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index ce415965145..e28411bb75a 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -1479,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 9e268605449..da13190970f 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -57,6 +57,15 @@ fi . "$GIT_BUILD_DIR"/GIT-BUILD-OPTIONS export PERL_PATH SHELL_PATH +# In t0000, we need to override test directories of nested testcases. In case +# the developer has TEST_OUTPUT_DIRECTORY part of his build options, then we'd +# reset this value to instead contain what the developer has specified. We thus +# have this knob to allow overriding the directory. +if test -n "${TEST_OUTPUT_DIRECTORY_OVERRIDE}" +then + TEST_OUTPUT_DIRECTORY="${TEST_OUTPUT_DIRECTORY_OVERRIDE}" +fi + # Disallow the use of abbreviated options in the test suite by default if test -z "${GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS}" then diff --git a/unpack-trees.c b/unpack-trees.c index f88a69f8e71..0a5135ab397 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 237517baee6..092a4f92ad2 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 c0dbf967498..eaed30eafba 100644 --- a/wt-status.c +++ b/wt-status.c @@ -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) { @@ -1492,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); } @@ -1652,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 0d32799b28e..ab9cc9d8f03 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; |