Merge branch 'js/objects-larger-than-4gb-on-windows' A hotfix to an earlier attempt to update code paths that assumed "unsigned long" was long enough for "size_t". * js/objects-larger-than-4gb-on-windows: zlib: properly clamp to uLong
diff --git a/.github/workflows/check-style.yml b/.github/workflows/check-style.yml index 19a145d..108a2de 100644 --- a/.github/workflows/check-style.yml +++ b/.github/workflows/check-style.yml
@@ -20,7 +20,7 @@ jobname: ClangFormat runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0
diff --git a/.github/workflows/check-whitespace.yml b/.github/workflows/check-whitespace.yml index 928fd4c..ea6f49f 100644 --- a/.github/workflows/check-whitespace.yml +++ b/.github/workflows/check-whitespace.yml
@@ -19,7 +19,7 @@ check-whitespace: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0
diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml index 3435bae..58a78f1 100644 --- a/.github/workflows/coverity.yml +++ b/.github/workflows/coverity.yml
@@ -38,10 +38,10 @@ COVERITY_LANGUAGE: cxx COVERITY_PLATFORM: overridden-below steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: install minimal Git for Windows SDK if: contains(matrix.os, 'windows') - uses: git-for-windows/setup-git-for-windows-sdk@v1 + uses: git-for-windows/setup-git-for-windows-sdk@v2 - run: ci/install-dependencies.sh if: contains(matrix.os, 'ubuntu') || contains(matrix.os, 'macos') env:
diff --git a/.github/workflows/l10n.yml b/.github/workflows/l10n.yml index 95e5513..114a12a 100644 --- a/.github/workflows/l10n.yml +++ b/.github/workflows/l10n.yml
@@ -92,7 +92,7 @@ cat git-po-helper.out exit $exit_code - name: Create comment in pull request for report - uses: mshick/add-pr-comment@v2 + uses: mshick/add-pr-comment@v3 if: >- always() && github.event_name == 'pull_request_target' &&
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6f3d94e..cf341d7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml
@@ -63,7 +63,7 @@ echo "skip_concurrent=$skip_concurrent" >>$GITHUB_OUTPUT - name: skip if the commit or tree was already tested id: skip-if-redundant - uses: actions/github-script@v8 + uses: actions/github-script@v9 if: steps.check-ref.outputs.enabled == 'yes' with: github-token: ${{secrets.GITHUB_TOKEN}} @@ -112,8 +112,8 @@ group: windows-build-${{ github.ref }} cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }} steps: - - uses: actions/checkout@v5 - - uses: git-for-windows/setup-git-for-windows-sdk@v1 + - uses: actions/checkout@v6 + - uses: git-for-windows/setup-git-for-windows-sdk@v2 - name: build shell: bash env: @@ -123,7 +123,7 @@ - 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@v5 + uses: actions/upload-artifact@v7 with: name: windows-artifacts path: artifacts @@ -140,14 +140,14 @@ cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }} steps: - name: download tracked files and build artifacts - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v8 with: name: windows-artifacts path: ${{github.workspace}} - name: extract tracked files and build artifacts shell: bash run: tar xf artifacts.tar.gz && tar xf tracked.tar.gz - - uses: git-for-windows/setup-git-for-windows-sdk@v1 + - uses: git-for-windows/setup-git-for-windows-sdk@v2 - name: test shell: bash run: . /etc/profile && ci/run-test-slice.sh $((${{matrix.nr}} + 1)) 10 @@ -157,7 +157,7 @@ run: ci/print-test-failures.sh - name: Upload failed tests' directories if: failure() && env.FAILED_TEST_ARTIFACTS != '' - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v7 with: name: failed-tests-windows-${{ matrix.nr }} path: ${{env.FAILED_TEST_ARTIFACTS}} @@ -173,10 +173,10 @@ group: vs-build-${{ github.ref }} cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }} steps: - - uses: actions/checkout@v5 - - uses: git-for-windows/setup-git-for-windows-sdk@v1 + - uses: actions/checkout@v6 + - uses: git-for-windows/setup-git-for-windows-sdk@v2 - name: initialize vcpkg - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: repository: 'microsoft/vcpkg' path: 'compat/vcbuild/vcpkg' @@ -186,7 +186,7 @@ repository: git/git definitionId: 9 - name: add msbuild to PATH - uses: microsoft/setup-msbuild@v2 + uses: microsoft/setup-msbuild@v3 - name: copy dlls to root shell: cmd run: compat\vcbuild\vcpkg_copy_dlls.bat release @@ -208,7 +208,7 @@ - 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@v5 + uses: actions/upload-artifact@v7 with: name: vs-artifacts path: artifacts @@ -224,9 +224,9 @@ group: vs-test-${{ matrix.nr }}-${{ github.ref }} cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }} steps: - - uses: git-for-windows/setup-git-for-windows-sdk@v1 + - uses: git-for-windows/setup-git-for-windows-sdk@v2 - name: download tracked files and build artifacts - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v8 with: name: vs-artifacts path: ${{github.workspace}} @@ -244,7 +244,7 @@ run: ci/print-test-failures.sh - name: Upload failed tests' directories if: failure() && env.FAILED_TEST_ARTIFACTS != '' - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v7 with: name: failed-tests-windows-vs-${{ matrix.nr }} path: ${{env.FAILED_TEST_ARTIFACTS}} @@ -258,7 +258,7 @@ group: windows-meson-build-${{ github.ref }} cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 - name: Set up dependencies shell: pwsh @@ -270,7 +270,7 @@ shell: pwsh run: meson compile -C build - name: Upload build artifacts - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v7 with: name: windows-meson-artifacts path: build @@ -286,13 +286,13 @@ group: windows-meson-test-${{ matrix.nr }}-${{ github.ref }} cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 - name: Set up dependencies shell: pwsh run: pip install meson ninja - name: Download build artifacts - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v8 with: name: windows-meson-artifacts path: build @@ -305,7 +305,7 @@ run: ci/print-test-failures.sh - name: Upload failed tests' directories if: failure() && env.FAILED_TEST_ARTIFACTS != '' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: failed-tests-windows-meson-${{ matrix.nr }} path: ${{env.FAILED_TEST_ARTIFACTS}} @@ -341,7 +341,7 @@ TEST_OUTPUT_DIRECTORY: ${{github.workspace}}/t runs-on: ${{matrix.vector.pool}} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - run: ci/install-dependencies.sh - run: ci/run-build-and-tests.sh - name: print test failures @@ -349,7 +349,7 @@ run: ci/print-test-failures.sh - name: Upload failed tests' directories if: failure() && env.FAILED_TEST_ARTIFACTS != '' - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v7 with: name: failed-tests-${{matrix.vector.jobname}} path: ${{env.FAILED_TEST_ARTIFACTS}} @@ -362,7 +362,7 @@ CI_JOB_IMAGE: ubuntu-latest runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - run: ci/install-dependencies.sh - run: ci/run-build-and-minimal-fuzzers.sh dockerized: @@ -407,7 +407,7 @@ image: alpine:latest # Supported until 2025-04-02. - jobname: linux32 - image: i386/ubuntu:focal + image: i386/ubuntu:20.04 # A RHEL 8 compatible distro. Supported until 2029-05-31. - jobname: almalinux-8 image: almalinux:8 @@ -439,7 +439,7 @@ else apt-get -q update && apt-get -q -y install git fi - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - run: ci/install-dependencies.sh - run: useradd builder --create-home - run: chown -R builder . @@ -449,7 +449,7 @@ run: sudo --preserve-env --set-home --user=builder ci/print-test-failures.sh - name: Upload failed tests' directories if: failure() && env.FAILED_TEST_ARTIFACTS != '' - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v7 with: name: failed-tests-${{matrix.vector.jobname}} path: ${{env.FAILED_TEST_ARTIFACTS}} @@ -464,7 +464,7 @@ group: static-analysis-${{ github.ref }} cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - run: ci/install-dependencies.sh - run: ci/run-static-analysis.sh - run: ci/check-directional-formatting.bash @@ -480,7 +480,7 @@ group: rust-analysis-${{ github.ref }} cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - run: ci/install-dependencies.sh - run: ci/run-rust-checks.sh sparse: @@ -494,7 +494,7 @@ group: sparse-${{ github.ref }} cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install other dependencies run: ci/install-dependencies.sh - run: make sparse @@ -510,6 +510,6 @@ CI_JOB_IMAGE: ubuntu-latest runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - run: ci/install-dependencies.sh - run: ci/test-documentation.sh
diff --git a/.gitignore b/.gitignore index 24635cf..4da58c6 100644 --- a/.gitignore +++ b/.gitignore
@@ -71,6 +71,7 @@ /git-for-each-ref /git-for-each-repo /git-format-patch +/git-format-rev /git-fsck /git-fsck-objects /git-fsmonitor--daemon @@ -182,6 +183,7 @@ /git-update-server-info /git-upload-archive /git-upload-pack +/git-url-parse /git-var /git-verify-commit /git-verify-pack
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 83ec786..49f3689 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml
@@ -42,15 +42,15 @@ - jobname: linux-reftable image: ubuntu:rolling CC: clang - - jobname: linux-breaking-changes - image: ubuntu:20.04 - CC: gcc - - jobname: fedora-breaking-changes-meson - image: fedora:latest - jobname: linux-TEST-vars image: ubuntu:20.04 CC: gcc CC_PACKAGE: gcc-8 + - jobname: linux-breaking-changes + image: ubuntu:rolling + CC: gcc + - jobname: fedora-breaking-changes-meson + image: fedora:latest - jobname: linux-leaks image: ubuntu:rolling CC: gcc @@ -60,13 +60,20 @@ - jobname: linux-asan-ubsan image: ubuntu:rolling CC: clang - - jobname: linux-musl-meson - image: alpine:latest - - jobname: linux32 - image: i386/ubuntu:20.04 - jobname: linux-meson image: ubuntu:rolling CC: gcc + - jobname: linux-musl-meson + image: alpine:latest + # Supported until 2025-04-02. + - jobname: linux32 + image: i386/ubuntu:20.04 + # A RHEL 8 compatible distro. Supported until 2029-05-31. + - jobname: almalinux-8 + image: almalinux:8 + # Supported until 2026-08-31. + - jobname: debian-11 + image: debian:11 artifacts: paths: - t/failed-test-artifacts @@ -79,7 +86,7 @@ stage: test needs: [ ] tags: - - saas-macos-medium-m1 + - saas-macos-large-m2pro variables: TEST_OUTPUT_DIRECTORY: "/Volumes/RAMDisk" before_script: @@ -104,10 +111,10 @@ image: macos-15-xcode-16 CC: clang - jobname: osx-reftable - image: macos-15-xcode-16 + image: macos-26-xcode-26 CC: clang - jobname: osx-meson - image: macos-15-xcode-16 + image: macos-26-xcode-26 CC: clang artifacts: paths:
diff --git a/.tsan-suppressions b/.tsan-suppressions index 5ba86d6..d84883b 100644 --- a/.tsan-suppressions +++ b/.tsan-suppressions
@@ -7,7 +7,6 @@ # A static variable is written to racily, but we always write the same value, so # in practice it (hopefully!) doesn't matter. race:^want_color$ -race:^transfer_debug$ # A boolean value, which tells whether the replace_map has been initialized or # not, is read racily with an update. As this variable is written to only once,
diff --git a/Documentation/BreakingChanges.adoc b/Documentation/BreakingChanges.adoc index af59c43..73bb939 100644 --- a/Documentation/BreakingChanges.adoc +++ b/Documentation/BreakingChanges.adoc
@@ -216,6 +216,30 @@ release. This evaluation will also take into account our own experience with how painful it is to keep Rust an optional component. +* The default value of `safe.bareRepository` will change from `all` to + `explicit`. It is all too easy for an attacker to trick a user into cloning a + repository that contains an embedded bare repository with malicious hooks + configured. If the user enters that subdirectory and runs any Git command, Git + discovers the bare repository and the hooks fire. The user does not even need + to run a Git command explicitly: many shell prompts run `git status` in the + background to display branch and dirty state information, and `git status` in + turn may invoke the fsmonitor hook if so configured, making the user + vulnerable the moment they `cd` into the directory. The `safe.bareRepository` + configuration variable was introduced in 8959555cee (setup_git_directory(): + add an owner check for the top-level directory, 2022-03-02) with a default of + `all` to preserve backwards compatibility. ++ +Changing the default to `explicit` means that Git will refuse to work with bare +repositories that are discovered implicitly by walking up the directory tree. +Bare repositories specified explicitly via the `--git-dir` command-line option +or the `GIT_DIR` environment variable continue to work regardless of this +setting. Repositories that look like a `.git` directory, a worktree, or a +submodule directory are also unaffected. ++ +Users who rely on implicit discovery of bare repositories can restore the +previous behavior by setting `safe.bareRepository=all` in their global or +system configuration. + === Removals * Support for grafting commits has long been superseded by git-replace(1).
diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index 2aafcec..c06f5d3 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines
@@ -693,6 +693,12 @@ char *dogs[] = ...; walk_all_dogs(dogs); + - For file timestamps, do not use "st_mtim" (and other timestamp + members in "struct stat") unconditionally; not everybody is POSIX + (grep for USE_ST_TIMESPEC). If you only need a timestamp in whole + second resolution, "st_mtime" should work fine everywhere. + + For Perl programs: - Most of the C guidelines above apply.
diff --git a/Documentation/RelNotes/2.54.1.adoc b/Documentation/RelNotes/2.54.1.adoc new file mode 100644 index 0000000..f561dbb --- /dev/null +++ b/Documentation/RelNotes/2.54.1.adoc
@@ -0,0 +1,29 @@ +Git v2.54.1 Release Notes +========================= + +This release is primarily to merge fixes accumulated on the 'master' +front to prepare for 2.55 release that are still relevant to 2.54.x +maintenance track. + +Fixes since v2.54 +----------------- + + * Headers from glibc 2.43 when used with clang does not allow + disabling C11 language features, causing build failures.. + + * Revert a recent change that introduced a regression to help mksh users. + + * Update various GitHub Actions versions. + + * Avoid hitting the pathname limit for socks proxy socket during the + test. + + * To help Windows 10 installations, avoid removing files whose + contents are still mmap()'ed. + + * Stop using unmaintained custom allocator in Windows build which was + the last user of the code. + + * Further update to the i18n alias support to avoid regressions. + +Also contains minor documentation updates and code clean-ups.
diff --git a/Documentation/RelNotes/2.55.0.adoc b/Documentation/RelNotes/2.55.0.adoc new file mode 100644 index 0000000..ecc7617 --- /dev/null +++ b/Documentation/RelNotes/2.55.0.adoc
@@ -0,0 +1,448 @@ +Git v2.55 Release Notes +======================= + +UI, Workflows & Features +------------------------ + + * Hook scripts defined via the configuration system can now be + configured to run in parallel. + + * The userdiff driver for the Scheme language has been extended to + cover other Lisp dialects. + + * Terminal control sequences coming over the sideband while talking + to a remote repository are mostly disabled by default, except for + ANSI color escape sequences. + + * "ort" merge backend improvements. + + * "git checkout -m another-branch" was invented to deal with local + changes to paths that are different between the current and the new + branch, but it gave only one chance to resolve conflicts. The command + was taught to create a stash to save the local changes. + + * A new builtin "git format-rev" is introduced for pretty formatting + one revision expression per line or commit object names found in + running text. + + * "git history" learned "fixup" command. + + * The internal URL parsing logic has been made accessible via a new + subcommand "git url-parse". + + * Misspelt proxy URL (e.g., httt://...) did not trigger any warning + or failure, which has been corrected. + + * Document the fact that .git/info/exclude is shared across worktrees + linked to the same repository. + + * The command line parser for "git diff" learned a few options take + only non-negative integers. + + * The graph output from commands like "git log --graph" can now be + limited to a specified number of lanes, preventing overly wide output + in repositories with many branches. + + * The fsmonitor daemon has been implemented for Linux. + + * "git cat-file --batch" learns an in-line command "mailmap" + that lets the user toggle use of mailmap. + + * The "git pack-objects --path-walk" traversal has been integrated + with several object filters, including blobless and sparse filters. + + * "git push" learned to take a "remote group" name to push to, which + causes pushes to multiple places, just like "git fetch" would do. + + * The 'git-jump' command (in contrib/) has been taught to automatically + pick a mode (merge, diff, or ws) when invoked without arguments. + + * The documentation for `push.default = simple` has been clarified to + better explain its behavior, making it clear that it pushes the + current branch to a same-named branch on the remote, and detailing + the upstream requirements for centralized workflows. + + * The documentation for "--word-diff" has been extended with a bit of + implementation detail of where these different words come from. + + * "git config foo.bar=baz" is not likely to be a request to read the + value of such a variable with '=' in its name; rather it is plausible + that the user meant "git config set foo.bar baz". Give advice when + giving an error message. + + * "git rev-list" (and "git log" family of commands) learned a new "--max-count-oldest" + that picks oldest N commits in the range instead of the usual newest. + + * Various AsciiDoc markup fixes in 'git config' documentation and + related files to ensure lists and formatting are rendered correctly. + + +Performance, Internal Implementation, Development Support etc. +-------------------------------------------------------------- + + * Promisor remote handling has been refactored and fixed in + preparation for auto-configuration of advertised remotes. + + * Rust support is enabled by default (but still allows opting out) in + some future version of Git. + + * Preparation of the xdiff/ codebase to work with Rust. + + * Use a larger buffer size in the code paths to ingest pack stream. + + * Refactor service routines in the ref subsystem backends. + + * Shrink wasted memory in Myers diff that does not account for common + prefix and suffix removal. + + * Enable expensive tests to catch topics that may cause breakages on + integration branches closer to their origin in the contributor PR + builds. + + * "git merge-base" optimization. + + * The limit_list() function that is one of the core part of the + revision traversal infrastructure has been optimized by replacing + its use of linear list with priority queue. + + * In a lazy clone, "git cherry" and "git grep" often fetch necessary + blob objects one by one from promisor remotes. It has been corrected + to collect necessary object names and fetch them in bulk to gain + reasonable performance. + + * The logic to determine that branches in an octopus merge are + independent has been optimized. + + * The consistency checks for the files reference backend have been updated + to skip lock files earlier, avoiding unnecessary parsing of + intermediate files. + + * The negotiation tip options in "git fetch" have been reworked to + allow requiring certain refs to be sent as "have" lines, and to + restrict negotiation to a specific set of refs. + + * The repacking code has been refactored and compaction of MIDX layers + have been implemented, and incremental strategy that does not require + all-into-one repacking has been introduced. + + * ODB transaction interface is being reworked to explicitly handle + object writes. + + * Add a new odb "in-memory" source that is meant to only hold + tentative objects (like the virtual blob object that represents the + working tree file used by "git blame"). + + * Many uses of the_repository has been updated to use a more + appropriate struct repository instance in setup.c codepath. + + * Revision traversal optimization. + + * Build update. + + * The logic to lazy-load trees from the commit-graph has been made + more robust by falling back to reading the commit object when + the commit-graph is no longer available. + + * The "name" argument in git_connect() and related functions has been + converted to a "service" enum to improve type safety and clarify its + purpose. + + * 'git restore --staged' has been optimized to avoid unnecessarily expanding + the sparse index when operating on paths within the sparse checkout + definition, by handling sparse directory entries at the tree level. + + * "git stash -p" has been optimized by reusing cached index + entries in its temporary index, avoiding unnecessary lstat() + calls on unchanged files. + + * The check for non-stale commits in the priority queue used by + `paint_down_to_common` and `ahead_behind` has been optimized by + replacing an O(N) scan with an O(1) counter, yielding performance + improvements in repositories with wide histories. + + * Reachability bitmap generation has been significantly optimized. By + reordering tree traversal, caching object positions, and refining how + pseudo-merge bitmaps are constructed, the performance of "git repack + --write-midx-bitmaps" is improved, especially for large repositories + and when using pseudo-merges. + + * Adding a decimal integer with strbuf_addf("%u") appears commonly; + they have been optimized by using a custom formatter. + + * Formatting object name in full hexadecimal form has been optimized + by using a new strbuf_add_oid_hex() helper function. + + * Encourage original authors to monitor the CI status. + + * The `git log -L` implementation has been refactored to use the + standard diff output pipeline, enabling pickaxe and diff-filter to + work as expected. Additionally, metadata-only diff formats like + --raw and --name-only are now supported with -L. + + * The loose object source has been refactored into a proper `struct + odb_source`. + + * Guidelines on how to write a cover letter for a multi-patch series + have been added to SubmittingPatches, which also got a new marker + to separate the section for typofixes. + + * The setup logic to discover and configure repositories has been + refactored, and the initialization of the object database has been + centralized. + + * Many core configuration variables have been migrated from global + variables into 'repo_config_values' to tie them to a specific + repository instance, avoiding cross-repository state leakage. + + * Streaming revision walks have been optimized by using a priority queue + for date-sorting commits, speeding up walks repositories with many + merges. + + * A recent regression in t7527 that broke TAP output has been fixed, + some other test noise that also broke TAP output has been silenced, + and 'prove' is now configured to fail on invalid TAP output to + prevent future regressions. + + * A handful of inappropriate uses of the_repository have been + rewritten to use the right repository structure instance in the + unpack-trees.c codepath. + + * "git index-pack" has been optimized by retaining child bases in the + delta cache instead of immediately freeing them, letting the existing + cache limit policy decide eviction. + + * `git ls-files --modified` and `git ls-files --deleted` have been + optimized to filter with pathspec before calling lstat() when there is + only a single pathspec item, avoiding unnecessary filesystem access + for entries that will not be shown. + + * The UNUSED macro in 'compat/posix.h' has been updated to use a + newly introduced GIT_CLANG_PREREQ macro for compiler version + checks, and the existing GIT_GNUC_PREREQ macro has been modernized + to use explicit major/minor comparisons rather than bit-shifting. + + +Fixes since v2.54 +----------------- + + * Code clean-up to use the right instance of a repository instance in + calls inside refs subsystem. + (merge 57c590feb9 sp/refs-reduce-the-repository later to maint). + + * The check that implements the logic to see if an in-core cache-tree + is fully ready to write out a tree object was broken, which has + been corrected. + (merge 521731213c dl/cache-tree-fully-valid-fix later to maint). + + * The test suite harness and many individual test scripts have been + updated to work correctly when 'set -e' is in effect, which helps + detect misspelled test commands. + (merge ffe8005b9d ps/test-set-e-clean later to maint). + + * Revert a recent change that introduced a regression to help mksh users. + + * Update various GitHub Actions versions. + + * Avoid hitting the pathname limit for socks proxy socket during the + test.. + + * To help Windows 10 installations, avoid removing files whose + contents are still mmap()'ed. + + * The 'git backfill' command now rejects revision-limiting options that + are incompatible with its operation, uses standard documentation for + revision ranges, and includes blobs from boundary commits by default + to improve performance of subsequent operations. + (merge a1ad4a0fca en/backfill-fixes-and-edges later to maint). + + * "git grep" update. + (merge 9ff4b5ab1b rs/grep-column-only-match-fix later to maint). + + * Headers from glibc 2.43 when used with clang does not allow + disabling C11 language features, causing build failures.. + + * The 'http.emptyAuth=auto' configuration now correctly attempts + Negotiate authentication before falling back to manual credentials. + This allows seamless Kerberos ticket-based authentication without + requiring users to explicitly set 'http.emptyAuth=true'. + (merge 4919938d28 mc/http-emptyauth-negotiate-fix later to maint). + + * Ramifications of turning off commit-graph has been documented a bit + more clearly. + (merge 48c855bb8f kh/doc-commit-graph later to maint). + + * "git rebase --update-refs", when used with an rebase.instructionFormat + with "%d" (describe) in it, tried to update local branch HEAD by + mistake, which has been corrected. + (merge 106b6885c7 ag/rebase-update-refs-limit-to-branches later to maint). + + * Tweak the way how sideband messages from remote are printed while + we talk with a remote repository to avoid tickling terminal + emulator glitches. + (merge 31e8fcabd8 rs/sideband-clear-line-before-print later to maint). + + * The configuration variable submodule.fetchJobs was not read correctly, + which has been corrected. + (merge aa45a5902f sj/submodule-update-clone-config-fix later to maint). + + * Update code paths that assumed "unsigned long" was long enough for + "size_t". + (merge 7a094d68a2 js/objects-larger-than-4gb-on-windows later to maint). + + * Stop using unmaintained custom allocator in Windows build which was + the last user of the code. + + * The computation to shorten the filenames shown in diffstat measured + width of individual UTF-8 characters to add up, but forgot to take + into account error cases (e.g., an invalid UTF-8 sequence, or a + control character). + (merge 09d86a3b98 en/diffstat-utf8-truncation-fix later to maint). + + * Some tests assume that bare repository accesses are by default + allowed; rewrite some of them to avoid the assumption, rewrite + others to explicitly set safe.bareRepository to allow them. + (merge 985b38ca6c js/adjust-tests-to-explicitly-access-bare-repo later to maint). + + * Signing commit with custom encoding was passing the data to be + signed at a wrong stage in the pipeline, which has been corrected. + (merge 7735d7eee3 bc/sign-commit-with-custom-encoding later to maint). + + * Further update to the i18n alias support to avoid regressions. + + * "git fetch --deepen=<n>" in a full clone truncated the history to <n> + commits deep, which has been corrected to be a no-op instead. + (merge 2431f5e0e5 sp/shallow-deepen-on-non-shallow-repo-fix later to maint). + + * "git maintenance" that goes background did not use the lockfile to + prevent multiple maintenance processes from running at the same + time, which has been corrected. + (merge 29364f1624 ps/maintenance-daemonize-lockfix later to maint). + + * Remove ineffective strbuf presizing that would have computed an + allocation that would not have fit in the available memory anyway, + or too small due to integer wraparound to cause immediate automatic + growing. + (merge a9ce8526dc jk/pretty-no-strbuf-presizing later to maint). + + * The HTTP walker misinterpreted the alternates file that gives an + absolute path when the server URL does not have the final slash + (i.e., "https://example.com" not "https://example.com/"). + (merge b92387cd55 jk/dumb-http-alternate-fix later to maint). + + * "git bisect" now uses the selected terms (e.g., old/new) more + consistently in its output. + (merge cb55991825 jr/bisect-custom-terms-in-output later to maint). + + * Update GitLab CI jobs that exercise macOS. + (merge 62319b49bb ps/gitlab-ci-macOS-improvements later to maint). + + * "Friday noon" asked in the morning on Sunday was parsed to be one + day before the specified time, which has been corrected. + (merge b809304101 ta/approxidate-noon-fix later to maint). + + * The GIT_WORK_TREE variable prepared to invoke the push-to-checkout + hook was leaking into the environment even when there was no hook + used and broke the default push-to-deploy (i.e., let "git checkout" + update the working tree only when the working tree is clean). + (merge 44d04e4426 ar/receive-pack-worktree-env later to maint). + + * A batch of documentation pages has been updated to use the modern + synopsis style. + (merge 2ef248ae45 ja/doc-synopsis-style-again later to maint). + + * The "promisor.quiet" configuration variable was not used from + relevant submodules when commands like "grep --recurse-submodules" + triggered a lazy fetch, which has been corrected. + (merge fa1468a1f7 th/promisor-quiet-per-repo later to maint). + + * Correct use of sockaddr API in "git daemon". + (merge 422a5bf575 st/daemon-sockaddr-fixes later to maint). + + * A memory leak in `fetch_and_setup_pack_index()` when verification of + the downloaded pack index fails has been plugged. Also an obsolete + `unlink()` call on parse failure has been cleaned up. + + * In t3070-wildmatch, "via ls-files" test variants with patterns + containing backslash escapes are now skipped on Windows, avoiding 36 + test failures caused by pathspec separator conversion. + (merge 8c84e6802c kk/wildmatch-windows-ls-files-prereq later to maint). + + * A linker warning on macOS when building with Xcode 16.3 or newer has + been avoided by passing -fno-common to the compiler when a + sufficiently new linker is detected. + (merge 5cd4d0d850 hn/macos-linker-warning later to maint). + + * Documentation and tests have been added to clarify that Git's internal + raw timestamp format requires a `@` prefix for values less than + 100,000,000 to prevent ambiguity with other formats like YYYYMMDD. + (merge 4018dc29ee ls/doc-raw-timestamp-prefix later to maint). + + * Wording used in "format-patch --subject-prefix" documentation + has been improved. + (merge 4a1eb9304a lo/doc-format-patch-subject-prefix later to maint). + + * Advanced emulation of kill() used on Windows in GfW has been + upstreamed to improve the symptoms like left-behind .lock files and + that fails to let the child clean-up itself when it gets killed. + (merge 363f1d8b3a js/win-kill-child-more-gently later to maint). + + * The 'git describe --contains --all' command has been fixed to + properly honor the '--match' and '--exclude' options by passing + them down to 'git name-rev' with the appropriate reference + prefixes. + (merge 1891707d1b jk/describe-contains-all-match-fix later to maint). + + * Various typos, grammatical errors, and duplicated words in both + documentation and code comments have been corrected. + (merge dc6068df67 wy/docs-typofixes later to maint). + + * The subprocess handshake during startup has been made gentler by using + packet_read_line_gently() instead of packet_read_line() to prevent the + parent Git process from dying abruptly when a configured subprocess + (e.g., a clean/smudge filter) fails to start. + (merge 061a68e443 mm/subprocess-handshake-fix later to maint). + + * The TSAN race in transfer_debug() within transport-helper.c has been + resolved by initializing the debug flag early in + bidirectional_transfer_loop() before spawning worker threads, allowing + the removal of a TSAN suppression. + (merge 85704eda18 ps/transport-helper-tsan-fix later to maint). + + * 'git describe' has been taught to pass the 'refs/tags/' prefix down to + the ref iterator when '--all' is not requested, avoiding unnecessary + iteration over non-tag refs. + (merge 55088ac8a4 td/describe-tag-iteration later to maint). + + * compute_reachable_generation_numbers() in commit-graph used a 32-bit + integer to accumulate parent generations, which is OK for generation + number v1 (topological levels), but with generation number v2 + (adjusted committer timestamps), it truncated timestamps beyond + 2106. Fixed by widening the accumulator to timestamp_t. + (merge fbcc5408fc en/commit-graph-timestamp-fix later to maint). + + * Other code cleanup, docfix, build fix, etc. + (merge 80f4b802e9 ja/doc-difftool-synopsis-style later to maint). + (merge b96490241e jc/doc-timestamps-in-stat later to maint). + (merge ef85286e51 ss/t7004-unhide-git-failures later to maint). + (merge 7584d10bc2 mf/format-patch-cover-letter-format-docfix later to maint). + (merge 8547908eb3 pw/rename-to-get-current-worktree later to maint). + (merge 890229b3f3 sg/t6112-unwanted-tilde-expansion-fix later to maint). + (merge ab9753e7bc kh/doc-restore-double-underscores-fix later to maint). + (merge 4a9e097228 za/t2000-modernise-more later to maint). + (merge b635fd0725 kh/doc-log-decorate-list later to maint). + (merge 65ea197dca jk/commit-sign-overflow-fix later to maint). + (merge 3ccb16052a jk/apply-leakfix later to maint). + (merge 5e6e8dc786 tb/pseudo-merge-bugfixes later to maint). + (merge 6d09e798bc pb/doc-diff-format-updates later to maint). + (merge 34a891a2d3 rs/trailer-fold-optim later to maint). + (merge 499f9048e0 ps/t3903-cover-stash-include-untracked later to maint). + (merge b56ab270aa jk/sq-dequote-cleanup later to maint). + (merge 29d9fdcf10 rs/use-builtin-add-overflow-explicitly-on-clang later to maint). + (merge d9982e8290 ed/check-connected-close-err-fd-2.53 later to maint). + (merge 1740cc35d0 ed/check-connected-close-err-fd later to maint). + (merge f4d7eb3d1c sp/doc-range-diff-takes-notes later to maint). + (merge 83e7f3bd2b kh/free-commit-list later to maint). + (merge d1b72b29e9 am/doc-tech-hash-typofix later to maint). + (merge 014c454799 ak/typofixes later to maint). + (merge 522ea8ef7d js/osxkeychain-build-wo-rust later to maint). + (merge e8f12e0e95 jc/t1400-fifo-cleanup later to maint).
diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index d570184..f042bb5 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches
@@ -92,7 +92,7 @@ topic are appropriate, so such an incremental updates are limited to small corrections and polishing. After a topic cooks for some time (like 7 calendar days) in 'next' without needing further tweaks on - top, it gets merged to the 'master' branch and wait to become part + top, it gets merged to the 'master' branch and waits to become part of the next major release. In the following sections, many techniques and conventions are listed @@ -237,6 +237,7 @@ behavior and make sure that the resulting documentation set formats well (try the Documentation/doc-diff script). +[[typofixes]] We currently have a liberal mixture of US and UK English norms for spelling and grammar, which is somewhat unfortunate. A huge patch that touches the files all over the place only to correct the inconsistency @@ -471,6 +472,30 @@ Only capitalize the very first letter of the trailer, i.e. favor "Signed-off-by" over "Signed-Off-By" and "Acked-by:" over "Acked-By". +[[cover-letter]] +=== Cover Letter + +The purpose of your cover letter is to sell your changes, explain what +they are about, and get your target audience interested enough to read +the patches. + +. Every code change comes with risk of regression and maintenance cost. + The cover letter should clearly communicate why the value of your + proposed change is worth applying. You can also describe how the risk + is reduced by the design choices you made while writing the patches. + +. Make sure your target audience can understand what the patches are + about and why they are needed without prior context. + +. For a second or subsequent iteration of the same topic, make sure + people who missed the earlier discussion can still understand what + the patches are about, so they can judge if the topic is worth their + time to read and comment on. + +. To help those who are familiar with earlier iterations, give a + summary of changes since the previous rounds. + + [[ai]] === Use of Artificial Intelligence (AI) @@ -817,6 +842,17 @@ Then fix the problem and push your fix to your GitHub fork. This will trigger a new CI build to ensure all tests pass. +Even if you do not use GitHub CI to test your changes, pay close +attention to new failures on the branches when the maintainer pushes +out after your topic gets merged to the 'seen' branch to make sure +that your topic is not breaking the CI, and retract your breaking +topic quickly while you fix the breakage you caused. + +To see maintainer's push, keep an eye on this page: + + `https://github.com/git/git/actions/workflows/main.yml?query=event%3Apush+actor%3Agitster` + + [[mua]] == MUA specific hints
diff --git a/Documentation/asciidoc.conf.in b/Documentation/asciidoc.conf.in index 31b883a..93c63b2 100644 --- a/Documentation/asciidoc.conf.in +++ b/Documentation/asciidoc.conf.in
@@ -84,6 +84,9 @@ [blockdef-open] synopsis-style=template="verseparagraph",filter="sed 's!…\\(\\]\\|$\\)!<phrase>\\0</phrase>!g;s!\\([\\[ |()]\\|^\\|\\]\\|>\\)\\([-=a-zA-Z0-9:+@,\\/_^\\$.\\\\\\*]\\+\\|…\\)!\\1<literal>\\2</literal>!g;s!<[-a-zA-Z0-9.]\\+>!<emphasis>\\0</emphasis>!g'" +[blockdef-listing] +synopsis-style=template="verseparagraph",filter="sed 's!…\\(\\]\\|$\\)!<phrase>\\0</phrase>!g;s!\\([\\[ |()]\\|^\\|\\]\\|>\\)\\([-=a-zA-Z0-9:+@,\\/_^\\$.\\\\\\*]\\+\\|…\\)!\\1<literal>\\2</literal>!g;s!<[-a-zA-Z0-9.]\\+>!<emphasis>\\0</emphasis>!g'" + [paradef-default] synopsis-style=template="verseparagraph",filter="sed 's!…\\(\\]\\|$\\)!<phrase>\\0</phrase>!g;s!\\([\\[ |()]\\|^\\|\\]\\|>\\)\\([-=a-zA-Z0-9:+@,\\/_^\\$.\\\\\\*]\\+\\|…\\)!\\1<literal>\\2</literal>!g;s!<[-a-zA-Z0-9.]\\+>!<emphasis>\\0</emphasis>!g'" endif::doctype-manpage[] @@ -93,6 +96,9 @@ [blockdef-open] synopsis-style=template="verseparagraph",filter="sed 's!…\\(\\]\\|$\\)!<span>\\0</span>!g;s!\\([\\[ |()]\\|^\\|\\]\\|>\\)\\([-=a-zA-Z0-9:+@,\\/_^\\$.\\\\\\*]\\+\\|…\\)!\\1<code>\\2</code>!g;s!<[-a-zA-Z0-9.]\\+>!<em>\\0</em>!g'" +[blockdef-listing] +synopsis-style=template="verseparagraph",filter="sed 's!…\\(\\]\\|$\\)!<span>\\0</span>!g;s!\\([\\[ |()]\\|^\\|\\]\\|>\\)\\([-=a-zA-Z0-9:+@,\\/_^\\$.\\\\\\*]\\+\\|…\\)!\\1<code>\\2</code>!g;s!<[-a-zA-Z0-9.]\\+>!<em>\\0</em>!g'" + [paradef-default] synopsis-style=template="verseparagraph",filter="sed 's!…\\(\\]\\|$\\)!<span>\\0</span>!g;s!\\([\\[ |()]\\|^\\|\\]\\|>\\)\\([-=a-zA-Z0-9:+@,\\/_^\\$.\\\\\\*]\\+\\|…\\)!\\1<code>\\2</code>!g;s!<[-a-zA-Z0-9.]\\+>!<em>\\0</em>!g'" endif::backend-xhtml11[]
diff --git a/Documentation/config.adoc b/Documentation/config.adoc index 62eebe7..15b1a4d 100644 --- a/Documentation/config.adoc +++ b/Documentation/config.adoc
@@ -276,13 +276,15 @@ When a variable is said to take a boolean value, many synonyms are accepted for 'true' and 'false'; these are all case-insensitive. - ++ +-- true;; Boolean true literals are `yes`, `on`, `true`, and `1`. Also, a variable defined without `= <value>` is taken as true. false;; Boolean false literals are `no`, `off`, `false`, `0` and the empty string. +-- + When converting a value to its canonical form using the `--type=bool` type specifier, 'git config' will ensure that the output is "true" or @@ -451,6 +453,8 @@ include::config/help.adoc[] +include::config/hook.adoc[] + include::config/http.adoc[] include::config/i18n.adoc[] @@ -523,6 +527,8 @@ include::config/showbranch.adoc[] +include::config/sideband.adoc[] + include::config/sparse.adoc[] include::config/splitindex.adoc[]
diff --git a/Documentation/config/am.adoc b/Documentation/config/am.adoc index e9561e1..250e6b5 100644 --- a/Documentation/config/am.adoc +++ b/Documentation/config/am.adoc
@@ -1,11 +1,11 @@ -am.keepcr:: +`am.keepcr`:: If true, linkgit:git-am[1] will call linkgit:git-mailsplit[1] for patches in mbox format with parameter `--keep-cr`. In this case linkgit:git-mailsplit[1] will not remove `\r` from lines ending with `\r\n`. Can be overridden by giving `--no-keep-cr` from the command line. -am.threeWay:: +`am.threeWay`:: By default, linkgit:git-am[1] will fail if the patch does not apply cleanly. When set to true, this setting tells linkgit:git-am[1] to fall back on 3-way merge if the patch @@ -13,7 +13,7 @@ have those blobs available locally (equivalent to giving the `--3way` option from the command line). Defaults to `false`. -am.messageId:: +`am.messageId`:: Add a `Message-ID` trailer based on the email header to the commit when using linkgit:git-am[1] (see linkgit:git-interpret-trailers[1]). See also the `--message-id`
diff --git a/Documentation/config/apply.adoc b/Documentation/config/apply.adoc index f9908e2..36fcea6 100644 --- a/Documentation/config/apply.adoc +++ b/Documentation/config/apply.adoc
@@ -1,11 +1,16 @@ -apply.ignoreWhitespace:: - When set to 'change', tells 'git apply' to ignore changes in +`apply.ignoreWhitespace`:: + When set to `change`, tells `git apply` to ignore changes in whitespace, in the same way as the `--ignore-space-change` option. - When set to one of: no, none, never, false, it tells 'git apply' to + When set to one of: `no`, `none`, `never`, `false`, it tells `git apply` to respect all whitespace differences. +ifndef::git-apply[] See linkgit:git-apply[1]. +endif::git-apply[] -apply.whitespace:: - Tells 'git apply' how to handle whitespace, in the same way - as the `--whitespace` option. See linkgit:git-apply[1]. +`apply.whitespace`:: + Tells `git apply` how to handle whitespace, in the same way + as the `--whitespace` option. +ifndef::git-apply[] + See linkgit:git-apply[1]. +endif::git-apply[]
diff --git a/Documentation/config/bitmap-pseudo-merge.adoc b/Documentation/config/bitmap-pseudo-merge.adoc index 1f264ec..6bf52c8 100644 --- a/Documentation/config/bitmap-pseudo-merge.adoc +++ b/Documentation/config/bitmap-pseudo-merge.adoc
@@ -47,8 +47,8 @@ bitmapPseudoMerge.<name>.sampleRate:: Determines the proportion of non-bitmapped commits (among reference tips) which are selected for inclusion in an - unstable pseudo-merge bitmap. Must be between `0` and `1` - (inclusive). The default is `1`. + unstable pseudo-merge bitmap. Must be greater than `0` and + less than or equal to `1`. The default is `1`. bitmapPseudoMerge.<name>.threshold:: Determines the minimum age of non-bitmapped commits (among
diff --git a/Documentation/config/difftool.adoc b/Documentation/config/difftool.adoc index 4f7d40c..1b8d483 100644 --- a/Documentation/config/difftool.adoc +++ b/Documentation/config/difftool.adoc
@@ -1,43 +1,43 @@ -diff.tool:: +`diff.tool`:: Controls which diff tool is used by linkgit:git-difftool[1]. This variable overrides the value configured in `merge.tool`. The list below shows the valid built-in values. Any other value is treated as a custom diff tool and requires - that a corresponding difftool.<tool>.cmd variable is defined. + that a corresponding `difftool.<tool>.cmd` variable is defined. -diff.guitool:: +`diff.guitool`:: Controls which diff tool is used by linkgit:git-difftool[1] when - the -g/--gui flag is specified. This variable overrides the value + the `-g`/`--gui` flag is specified. This variable overrides the value configured in `merge.guitool`. The list below shows the valid built-in values. Any other value is treated as a custom diff tool - and requires that a corresponding difftool.<guitool>.cmd variable + and requires that a corresponding `difftool.<guitool>.cmd` variable is defined. include::{build_dir}/mergetools-diff.adoc[] -difftool.<tool>.cmd:: +`difftool.<tool>.cmd`:: Specify the command to invoke the specified diff tool. The specified command is evaluated in shell with the following - variables available: 'LOCAL' is set to the name of the temporary - file containing the contents of the diff pre-image and 'REMOTE' + variables available: `LOCAL` is set to the name of the temporary + file containing the contents of the diff pre-image and `REMOTE` is set to the name of the temporary file containing the contents of the diff post-image. + See the `--tool=<tool>` option in linkgit:git-difftool[1] for more details. -difftool.<tool>.path:: +`difftool.<tool>.path`:: Override the path for the given tool. This is useful in case your tool is not in the PATH. -difftool.trustExitCode:: +`difftool.trustExitCode`:: Exit difftool if the invoked diff tool returns a non-zero exit status. + See the `--trust-exit-code` option in linkgit:git-difftool[1] for more details. -difftool.prompt:: +`difftool.prompt`:: Prompt before each invocation of the diff tool. -difftool.guiDefault:: +`difftool.guiDefault`:: Set `true` to use the `diff.guitool` by default (equivalent to specifying the `--gui` argument), or `auto` to select `diff.guitool` or `diff.tool` depending on the presence of a `DISPLAY` environment variable value. The
diff --git a/Documentation/config/fetch.adoc b/Documentation/config/fetch.adoc index cd40db0..04ac909 100644 --- a/Documentation/config/fetch.adoc +++ b/Documentation/config/fetch.adoc
@@ -76,7 +76,7 @@ default is `skipping`. Unknown values will cause `git fetch` to error out. + -See also the `--negotiate-only` and `--negotiation-tip` options to +See also the `--negotiate-only` and `--negotiation-restrict` options to linkgit:git-fetch[1]. `fetch.showForcedUpdates`::
diff --git a/Documentation/config/format.adoc b/Documentation/config/format.adoc index dbd1862..95d1913 100644 --- a/Documentation/config/format.adoc +++ b/Documentation/config/format.adoc
@@ -102,7 +102,7 @@ Default is false. format.commitListFormat:: - When the `--cover-letter-format` option is not given, `format-patch` + When the `--commit-list-format` option is not given, `format-patch` uses the value of this variable to decide how to format the entry of each commit. Defaults to `shortlog`.
diff --git a/Documentation/config/fsmonitor--daemon.adoc b/Documentation/config/fsmonitor--daemon.adoc index 671f9b9..6f8386e 100644 --- a/Documentation/config/fsmonitor--daemon.adoc +++ b/Documentation/config/fsmonitor--daemon.adoc
@@ -4,8 +4,8 @@ behavior. Only respected when `core.fsmonitor` is set to `true`. fsmonitor.socketDir:: - This Mac OS-specific option, if set, specifies the directory in + This Mac OS and Linux-specific option, if set, specifies the directory in which to create the Unix domain socket used for communication between the fsmonitor daemon and various Git commands. The directory must - reside on a native Mac OS filesystem. Only respected when `core.fsmonitor` + reside on a native filesystem. Only respected when `core.fsmonitor` is set to `true`.
diff --git a/Documentation/config/grep.adoc b/Documentation/config/grep.adoc index 10041f2..83d4b76 100644 --- a/Documentation/config/grep.adoc +++ b/Documentation/config/grep.adoc
@@ -1,28 +1,28 @@ -grep.lineNumber:: - If set to true, enable `-n` option by default. +`grep.lineNumber`:: + If set to `true`, enable `-n` option by default. -grep.column:: - If set to true, enable the `--column` option by default. +`grep.column`:: + If set to `true`, enable the `--column` option by default. -grep.patternType:: - Set the default matching behavior. Using a value of 'basic', 'extended', - 'fixed', or 'perl' will enable the `--basic-regexp`, `--extended-regexp`, +`grep.patternType`:: + Set the default matching behavior. Using a value of `basic`, `extended`, + `fixed`, or `perl` will enable the `--basic-regexp`, `--extended-regexp`, `--fixed-strings`, or `--perl-regexp` option accordingly, while the - value 'default' will use the `grep.extendedRegexp` option to choose - between 'basic' and 'extended'. + value `default` will use the `grep.extendedRegexp` option to choose + between `basic` and `extended`. -grep.extendedRegexp:: - If set to true, enable `--extended-regexp` option by default. This +`grep.extendedRegexp`:: + If set to `true`, enable `--extended-regexp` option by default. This option is ignored when the `grep.patternType` option is set to a value - other than 'default'. + other than `default`. -grep.threads:: +`grep.threads`:: Number of grep worker threads to use. If unset (or set to 0), Git will use as many threads as the number of logical cores available. -grep.fullName:: - If set to true, enable `--full-name` option by default. +`grep.fullName`:: + If set to `true`, enable `--full-name` option by default. -grep.fallbackToNoIndex:: - If set to true, fall back to `git grep --no-index` if `git grep` - is executed outside of a git repository. Defaults to false. +`grep.fallbackToNoIndex`:: + If set to `true`, fall back to `git grep --no-index` if `git grep` + is executed outside of a git repository. Defaults to `false`.
diff --git a/Documentation/config/hook.adoc b/Documentation/config/hook.adoc index 9e78f26..083dc60 100644 --- a/Documentation/config/hook.adoc +++ b/Documentation/config/hook.adoc
@@ -1,10 +1,17 @@ +ifdef::git-hook[] +:see-git-hook: +endif::git-hook[] +ifndef::git-hook[] +:see-git-hook: See linkgit:git-hook[1]. +endif::git-hook[] + hook.<friendly-name>.command:: The command to execute for `hook.<friendly-name>`. `<friendly-name>` is a unique name that identifies this hook. The hook events that trigger the command are configured with `hook.<friendly-name>.event`. The value can be an executable path or a shell oneliner. If more than one value is specified for the same `<friendly-name>`, only the last - value parsed is used. See linkgit:git-hook[1]. + value parsed is used. {see-git-hook} hook.<friendly-name>.event:: The hook events that trigger `hook.<friendly-name>`. The value is the @@ -14,11 +21,87 @@ This is a multi-valued key. To run `hook.<friendly-name>` on multiple events, specify the key more than once. An empty value resets the list of events, clearing any previously defined events for - `hook.<friendly-name>`. See linkgit:git-hook[1]. + `hook.<friendly-name>`. {see-git-hook} ++ +The `<friendly-name>` must not be the same as a known hook event name +(e.g. do not use `hook.pre-commit.event`). Using a known event name as +a friendly-name is a fatal error because it creates an ambiguity with +`hook.<event>.enabled` and `hook.<event>.jobs`. For unknown event names, +a warning is issued when `<friendly-name>` matches the event value. hook.<friendly-name>.enabled:: Whether the hook `hook.<friendly-name>` is enabled. Defaults to `true`. Set to `false` to disable the hook without removing its configuration. This is particularly useful when a hook is defined in a system or global config file and needs to be disabled for a - specific repository. See linkgit:git-hook[1]. + specific repository. {see-git-hook} + +hook.<friendly-name>.parallel:: + Whether the hook `hook.<friendly-name>` may run in parallel with other hooks + for the same event. Defaults to `false`. Set to `true` only when the + hook script is safe to run concurrently with other hooks for the same + event. If any hook for an event does not have this set to `true`, + all hooks for that event run sequentially regardless of `hook.jobs`. + Only configured (named) hooks need to declare this. Traditional hooks + found in the hooks directory do not need to, and run in parallel when + the effective job count is greater than 1. {see-git-hook} + +hook.<event>.enabled:: + Switch to enable or disable all hooks for the `<event>` hook event. + When set to `false`, no hooks fire for that event, regardless of any + per-hook `hook.<friendly-name>.enabled` settings. Defaults to `true`. + {see-git-hook} ++ +Note on naming: `<event>` must be the event name (e.g. `pre-commit`), +not a hook friendly-name. Since using a known event name as a +friendly-name is disallowed (see `hook.<friendly-name>.event` above), +there is no ambiguity between event-level and per-hook `.enabled` +settings for known events. For unknown events, if a friendly-name +matches the event name despite the warning, `.enabled` is treated +as per-hook only. + +hook.<event>.jobs:: + Specifies how many hooks can be run simultaneously for the `<event>` + hook event (e.g. `hook.post-receive.jobs = 4`). Overrides `hook.jobs` + for this specific event. The same parallelism restrictions apply: this + setting has no effect unless all configured hooks for the event have + `hook.<friendly-name>.parallel` set to `true`. Set to `-1` to use the + number of available CPU cores. Must be a positive integer or `-1`; + zero is rejected with a warning. {see-git-hook} ++ +Note on naming: although this key resembles `hook.<friendly-name>.*` +(a per-hook setting), `<event>` must be the event name, not a hook +friendly name. The key component is stored literally and looked up by +event name at runtime with no translation between the two namespaces. +A key like `hook.my-hook.jobs` is stored under `"my-hook"` but the +lookup at runtime uses the event name (e.g. `"post-receive"`), so +`hook.my-hook.jobs` is silently ignored even when `my-hook` is +registered for that event. Use `hook.post-receive.jobs` or any other +valid event name when setting `hook.<event>.jobs`. + +hook.jobs:: + Specifies how many hooks can be run simultaneously during parallelized + hook execution. If unspecified, defaults to 1 (serial execution). + Set to `-1` to use the number of available CPU cores. + Can be overridden on a per-event basis with `hook.<event>.jobs`. + Some hooks always run sequentially regardless of this setting because + they operate on shared data and cannot safely be parallelized: ++ +-- +`applypatch-msg`;; +`prepare-commit-msg`;; +`commit-msg`;; + Receive a commit message file and may rewrite it in place. +`pre-commit`;; +`post-checkout`;; +`push-to-checkout`;; +`post-commit`;; + Access the working tree, index, or repository state. +-- ++ +This setting has no effect unless all configured hooks for the event have +`hook.<friendly-name>.parallel` set to `true`. ++ +For `pre-push` hooks, which normally keep stdout and stderr separate, +setting this to a value greater than 1 (or passing `-j`) will merge stdout +into stderr to allow correct de-interleaving of parallel output.
diff --git a/Documentation/config/http.adoc b/Documentation/config/http.adoc index 849c89f..792a71b 100644 --- a/Documentation/config/http.adoc +++ b/Documentation/config/http.adoc
@@ -59,7 +59,18 @@ Attempt authentication without seeking a username or password. This can be used to attempt GSS-Negotiate authentication without specifying a username in the URL, as libcurl normally requires a username for - authentication. + authentication. Possible values are: ++ +-- +* `auto` (default) - Send empty credentials only if the server's 401 response + advertises an authentication mechanism that requires them (such as + GSS-Negotiate); otherwise fall back to prompting via the credential helper. +* `true` - Always send empty credentials on the very first request, before + receiving any 401 response from the server. +* `false` - Never send empty credentials. Mechanisms that require + empty credentials or an explicit username, such as GSS-Negotiate, will not + work. +-- http.proactiveAuth:: Attempt authentication without first making an unauthenticated attempt and
diff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc index 4682a6b..cb8f5e2 100644 --- a/Documentation/config/imap.adoc +++ b/Documentation/config/imap.adoc
@@ -1,44 +1,44 @@ -imap.folder:: +`imap.folder`:: The folder to drop the mails into, which is typically the Drafts folder. For example: `INBOX.Drafts`, `INBOX/Drafts` or `[Gmail]/Drafts`. The IMAP folder to interact with MUST be specified; the value of this configuration variable is used as the fallback default value when the `--folder` option is not given. -imap.tunnel:: +`imap.tunnel`:: Command used to set up a tunnel to the IMAP server through which commands will be piped instead of using a direct network connection - to the server. Required when imap.host is not set. + to the server. Required when `imap.host` is not set. -imap.host:: +`imap.host`:: A URL identifying the server. Use an `imap://` prefix for non-secure connections and an `imaps://` prefix for secure connections. - Ignored when imap.tunnel is set, but required otherwise. + Ignored when `imap.tunnel` is set, but required otherwise. -imap.user:: +`imap.user`:: The username to use when logging in to the server. -imap.pass:: +`imap.pass`:: The password to use when logging in to the server. -imap.port:: +`imap.port`:: An integer port number to connect to on the server. - Defaults to 143 for imap:// hosts and 993 for imaps:// hosts. - Ignored when imap.tunnel is set. + Defaults to 143 for `imap://` hosts and 993 for `imaps://` hosts. + Ignored when `imap.tunnel` is set. -imap.sslverify:: +`imap.sslverify`:: A boolean to enable/disable verification of the server certificate used by the SSL/TLS connection. Default is `true`. Ignored when - imap.tunnel is set. + `imap.tunnel` is set. -imap.preformattedHTML:: +`imap.preformattedHTML`:: A boolean to enable/disable the use of html encoding when sending - a patch. An html encoded patch will be bracketed with <pre> + a patch. An html encoded patch will be bracketed with `<pre>` and have a content type of text/html. Ironically, enabling this option causes Thunderbird to send the patch as a plain/text, format=fixed email. Default is `false`. -imap.authMethod:: +`imap.authMethod`:: Specify the authentication method for authenticating with the IMAP server. If Git was built with the NO_CURL option, or if your curl version is older than 7.34.0, or if you're running git-imap-send with the `--no-curl`
diff --git a/Documentation/config/mergetool.adoc b/Documentation/config/mergetool.adoc index 7064f5a..7afdcad 100644 --- a/Documentation/config/mergetool.adoc +++ b/Documentation/config/mergetool.adoc
@@ -52,13 +52,13 @@ if `merge.tool` is configured as _<variant>_), Git will consult `mergetool.<variant>.layout` to determine the tool's layout. If the variant-specific configuration is not available, `vimdiff` ' s is used as - fallback. If that too is not available, a default layout with 4 windows - will be used. To configure the layout, see the 'BACKEND SPECIFIC HINTS' + fallback. If that too is not available, a default layout with 4 windows + will be used. ifdef::git-mergetool[] - section. +To configure the layout, see the 'BACKEND SPECIFIC HINTS' section. endif::[] ifndef::git-mergetool[] - section in linkgit:git-mergetool[1]. +To configure the layout, see the 'BACKEND SPECIFIC HINTS' section in linkgit:git-mergetool[1]. endif::[] `mergetool.hideResolved`::
diff --git a/Documentation/config/promisor.adoc b/Documentation/config/promisor.adoc index b0fa43b..39af63d 100644 --- a/Documentation/config/promisor.adoc +++ b/Documentation/config/promisor.adoc
@@ -63,11 +63,13 @@ advertised promisor remote (e.g., "foo"), three conditions must be met for the check of this specific field to pass: + +-- 1. The corresponding local configuration (e.g., `remote.foo.token`) must be set. 2. The server must advertise the "token" field for remote "foo". 3. The value of the locally configured `remote.foo.token` must exactly match the value advertised by the server for the "token" field. +-- + If any of these conditions is not met for any field name listed in `promisor.checkFields`, the advertised remote "foo" is rejected.
diff --git a/Documentation/config/push.adoc b/Documentation/config/push.adoc index d9112b2..28132ee 100644 --- a/Documentation/config/push.adoc +++ b/Documentation/config/push.adoc
@@ -41,9 +41,10 @@ `simple`;; push the current branch with the same name on the remote. + -If you are working on a centralized workflow (pushing to the same repository you -pull from, which is typically `origin`), then you need to configure an upstream -branch with the same name. +This mode requires that the remote repository to be pushed to is +known. When pushing back to the same remote you pull from, the +current branch must also have an upstream tracking branch with the +same name. + This mode is the default since Git 2.0, and is the safest option suited for beginners.
diff --git a/Documentation/config/remote.adoc b/Documentation/config/remote.adoc index 91e46f6..eb9c8a3 100644 --- a/Documentation/config/remote.adoc +++ b/Documentation/config/remote.adoc
@@ -107,6 +107,55 @@ the values inherited from a lower priority configuration files (e.g. `$HOME/.gitconfig`). +remote.<name>.negotiationRestrict:: + When negotiating with this remote during `git fetch`, restrict the + commits advertised as "have" lines to only those reachable from refs + matching the given patterns. This multi-valued config option behaves + like `--negotiation-restrict` on the command line. ++ +Each value is either an exact ref name (e.g. `refs/heads/release`) or a +glob pattern (e.g. `refs/heads/release/*`). The pattern syntax is the +same as for `--negotiation-restrict`. ++ +These config values are used as defaults for the `--negotiation-restrict` +command-line option. If `--negotiation-restrict` (or its synonym +`--negotiation-tip`) is specified on the command line, then the config +values are not used. ++ +These values also influence negotiation during `git push` if +`push.negotiate` is enabled. ++ +Blank values signal to ignore all previous values, allowing a reset of +the list from broader config scenarios. + +remote.<name>.negotiationInclude:: + When negotiating with this remote during `git fetch`, the client + advertises a list of commits that exist locally. In repos with + many references, this list of "haves" can be truncated. Depending + on data shape, dropping certain references may be expensive. This + multi-valued config option specifies references, commit hashes, + or ref pattern globs whose tips should always be sent as "have" + commits during fetch negotiation with this remote. ++ +Each value is either an exact ref name (e.g. `refs/heads/release`), a +commit hash, or a glob pattern (e.g. `refs/heads/release/*`). The +pattern syntax is the same as for `--negotiation-include`. ++ +These config values are used as defaults for the `--negotiation-include` +command-line option. If `--negotiation-include` is specified on the +command line, then the config values are not used. ++ +This option is additive with the normal negotiation process: the +negotiation algorithm still runs and advertises its own selected commits, +but the refs matching `remote.<name>.negotiationInclude` are sent +unconditionally on top of those heuristically selected commits. ++ +These values also influence negotiation during `git push` if +`push.negotiate` is enabled. ++ +Blank values signal to ignore all previous values, allowing a reset of +the list from broader config scenarios. + remote.<name>.followRemoteHEAD:: How linkgit:git-fetch[1] should handle updates to `remotes/<name>/HEAD` when fetching using the configured refspecs of a remote.
diff --git a/Documentation/config/repack.adoc b/Documentation/config/repack.adoc index e9e78dc..4c22a49 100644 --- a/Documentation/config/repack.adoc +++ b/Documentation/config/repack.adoc
@@ -46,3 +46,21 @@ `--write-midx`. When false, cruft packs are only included in the MIDX when necessary (e.g., because they might be required to form a reachability closure with MIDX bitmaps). Defaults to true. + +repack.midxSplitFactor:: + The factor used in the geometric merging condition when + compacting incremental MIDX layers during `git repack` when + invoked with the `--write-midx=incremental` option. ++ +Adjacent layers are merged when the accumulated object count of the +newer layer exceeds `1/<N>` of the object count of the next deeper +layer. Must be at least 2. Defaults to 2. + +repack.midxNewLayerThreshold:: + The minimum number of packs in the tip MIDX layer before those + packs are considered as candidates for geometric repacking + during `git repack --write-midx=incremental`. ++ +When the tip layer has fewer packs than this threshold, those packs are +excluded from the geometric repack entirely, and are thus left +unmodified. Must be at least 1. Defaults to 8.
diff --git a/Documentation/config/safe.adoc b/Documentation/config/safe.adoc index 2d45c98..99fbfc0 100644 --- a/Documentation/config/safe.adoc +++ b/Documentation/config/safe.adoc
@@ -2,10 +2,14 @@ Specifies which bare repositories Git will work with. The currently supported values are: + -* `all`: Git works with all bare repositories. This is the default. +-- +* `all`: Git works with all bare repositories. This is the default in + Git 2.x. * `explicit`: Git only works with bare repositories specified via the top-level `--git-dir` command-line option, or the `GIT_DIR` - environment variable (see linkgit:git[1]). + environment variable (see linkgit:git[1]). This will be the default + in Git 3.0. +-- + If you do not use bare repositories in your workflow, then it may be beneficial to set `safe.bareRepository` to `explicit` in your global @@ -13,6 +17,10 @@ repository that contains a bare repository and running a Git command within that directory. + +If you use bare repositories regularly and want to preserve the current +behavior after upgrading to Git 3.0, set `safe.bareRepository` to `all` +in your global or system config. ++ This config setting is only respected in protected configuration (see <<SCOPES>>). This prevents untrusted repositories from tampering with this value.
diff --git a/Documentation/config/sideband.adoc b/Documentation/config/sideband.adoc new file mode 100644 index 0000000..f5b159e --- /dev/null +++ b/Documentation/config/sideband.adoc
@@ -0,0 +1,27 @@ +sideband.allowControlCharacters:: + By default, control characters that are delivered via the sideband + are masked, except ANSI color sequences. This prevents potentially + unwanted ANSI escape sequences from being sent to the terminal. Use + this config setting to override this behavior (the value can be + a comma-separated list of the following keywords): ++ +-- + `color`:: + Allow ANSI color sequences, line feeds and horizontal tabs, + but mask all other control characters. This is the default. + `cursor`:: + Allow control sequences that move the cursor. This is + disabled by default. + `erase`:: + Allow control sequences that erase characters. This is + disabled by default. + `false`:: + Mask all control characters other than line feeds and + horizontal tabs. + `true`:: + Allow all control characters to be sent to the terminal. +-- + +sideband.<url>.*:: + Apply the `sideband.*` option selectively to specific URLs. The + same URL matching logic applies as for `http.<url>.*` settings.
diff --git a/Documentation/date-formats.adoc b/Documentation/date-formats.adoc index e24517c..330424b 100644 --- a/Documentation/date-formats.adoc +++ b/Documentation/date-formats.adoc
@@ -9,6 +9,11 @@ `<unix-timestamp>` is the number of seconds since the UNIX epoch. `<time-zone-offset>` is a positive or negative offset from UTC. For example CET (which is 1 hour ahead of UTC) is `+0100`. ++ +It is safer to prepend the `<unix-timestamp>` with `@` (e.g., +`@0 +0000`), which forces Git to interpret it as a raw timestamp. This +is required for values less than 100,000,000 (which have fewer than 9 +digits) to avoid confusion with other date formats like `YYYYMMDD`. RFC 2822:: The standard date format as described by RFC 2822, for example
diff --git a/Documentation/diff-format.adoc b/Documentation/diff-format.adoc index 9f7e988..ef5df14 100644 --- a/Documentation/diff-format.adoc +++ b/Documentation/diff-format.adoc
@@ -19,9 +19,9 @@ `git-diff-files [<pattern>...]`:: compares the index and the files on the filesystem. -The `git-diff-tree` command begins its output by printing the hash of -what is being compared. After that, all the commands print one output -line per changed file. +All the commands print one output line per changed file, +except `git diff-files` in the case of an unmerged file, which prints +both an "unmerged" and an "in-place edit" line. An output line is formatted this way: @@ -37,13 +37,13 @@ That is, from the left to the right: . a colon. -. mode for "src"; 000000 if creation or unmerged. +. mode for "src"; 000000 if creation, or if "src" is from the index and is unmerged. . a space. -. mode for "dst"; 000000 if deletion or unmerged. +. mode for "dst"; 000000 if deletion, or if "dst" is from the index and is unmerged. . a space. -. sha1 for "src"; 0\{40\} if creation or unmerged. +. sha1 for "src"; 0\{40\} if creation, or if "src" is from the index and is unmerged. . a space. -. sha1 for "dst"; 0\{40\} if deletion, unmerged or "work tree out of sync with the index". +. sha1 for "dst"; 0\{40\} if deletion, if "dst" is from the index and is unmerged, or if "dst" is from the work tree and is out of sync with the index. . a space. . status, followed by optional "score" number. . a tab or a NUL when `-z` option is used.
diff --git a/Documentation/diff-options.adoc b/Documentation/diff-options.adoc index 8a63b5e..c8242e2 100644 --- a/Documentation/diff-options.adoc +++ b/Documentation/diff-options.adoc
@@ -457,6 +457,14 @@ + Note that despite the name of the first mode, color is used to highlight the changed parts in all modes if enabled. ++ +The `--word-diff` option operates by taking the same line-by-line +diff that is produced without the option and computing +word-by-word changes within each hunk. This may produce a +larger diff than a dedicated word-diff tool would. If Git +acquires a different implementation in the future, the output +may change. Note that this is similar to the `--diff-algorithm` +option, which may also change the output. `--word-diff-regex=<regex>`:: Use _<regex>_ to decide what a word is, instead of considering
diff --git a/Documentation/fetch-options.adoc b/Documentation/fetch-options.adoc index 81a9d7f..035f780 100644 --- a/Documentation/fetch-options.adoc +++ b/Documentation/fetch-options.adoc
@@ -1,6 +1,6 @@ `--all`:: `--no-all`:: - Fetch all remotes, except for the ones that has the + Fetch all remotes, except for the ones that have the `remote.<name>.skipFetchAll` configuration variable set. This overrides the configuration variable `fetch.all`. @@ -49,6 +49,7 @@ `.git/shallow`. This option updates `.git/shallow` and accepts such refs. +`--negotiation-restrict=(<commit>|<glob>)`:: `--negotiation-tip=(<commit>|<glob>)`:: By default, Git will report, to the server, commits reachable from all local refs to find common commits in an attempt to @@ -58,6 +59,9 @@ local ref is likely to have commits in common with the upstream ref being fetched. + +`--negotiation-restrict` is the preferred name for this option; +`--negotiation-tip` is accepted as a synonym. ++ This option may be specified more than once; if so, Git will report commits reachable from any of the given commits. + @@ -69,9 +73,32 @@ configuration variables documented in linkgit:git-config[1], and the `--negotiate-only` option below. +`--negotiation-include=(<commit>|<glob>)`:: + Ensure that the commits at the given tips are always sent as "have" + lines during fetch negotiation, regardless of what the negotiation + algorithm selects. This is useful to guarantee that common + history reachable from specific refs is always considered, even + when `--negotiation-restrict` restricts the set of tips or when + the negotiation algorithm would otherwise skip them. ++ +This option may be specified more than once; if so, each commit is sent +unconditionally. ++ +The argument may be an exact ref name (e.g. `refs/heads/release`), an +object hash, or a glob pattern (e.g. `refs/heads/release/{asterisk}`). +The pattern syntax is the same as for `--negotiation-restrict`. ++ +If `--negotiation-restrict` is used, the have set is first restricted by +that option and then increased to include the tips specified by +`--negotiation-include`. ++ +If this option is not specified on the command line, then any +`remote.<name>.negotiationInclude` config values for the current remote +are used instead. + `--negotiate-only`:: Do not fetch anything from the server, and instead print the - ancestors of the provided `--negotiation-tip=` arguments, + ancestors of the provided `--negotiation-restrict=` arguments, which we have in common with the server. + This is incompatible with `--recurse-submodules=(yes|on-demand)`.
diff --git a/Documentation/format-patch-caveats.adoc b/Documentation/format-patch-caveats.adoc index 807a65b..133e475 100644 --- a/Documentation/format-patch-caveats.adoc +++ b/Documentation/format-patch-caveats.adoc
@@ -28,6 +28,6 @@ email workflows. Given these limitations, one might be tempted to use a general-purpose -utility like patch(1) instead. However, patch(1) will not only look for +utility like `patch`(1) instead. However, `patch`(1) will not only look for unindented diffs (like linkgit:git-am[1]) but will try to apply indented diffs as well.
diff --git a/Documentation/format-patch-end-of-commit-message.adoc b/Documentation/format-patch-end-of-commit-message.adoc index ec1ef79..a1a624d 100644 --- a/Documentation/format-patch-end-of-commit-message.adoc +++ b/Documentation/format-patch-end-of-commit-message.adoc
@@ -1,8 +1,8 @@ Any line that is of the form: * three-dashes and end-of-line, or -* a line that begins with "diff -", or -* a line that begins with "Index: " +* a line that begins with `diff -`, or +* a line that begins with `Index: ` is taken as the beginning of a patch, and the commit log message is terminated before the first occurrence of such a line.
diff --git a/Documentation/git-am.adoc b/Documentation/git-am.adoc index ac65852..28adf4c 100644 --- a/Documentation/git-am.adoc +++ b/Documentation/git-am.adoc
@@ -8,17 +8,17 @@ SYNOPSIS -------- -[verse] -'git am' [--signoff] [--keep] [--[no-]keep-cr] [--[no-]utf8] [--[no-]verify] +[synopsis] +git am [--signoff] [--keep] [--[no-]keep-cr] [--[no-]utf8] [--[no-]verify] [--[no-]3way] [--interactive] [--committer-date-is-author-date] [--ignore-date] [--ignore-space-change | --ignore-whitespace] [--whitespace=<action>] [-C<n>] [-p<n>] [--directory=<dir>] [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet] - [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>] + [--[no-]scissors] [-S[<key-id>]] [--patch-format=<format>] [--quoted-cr=<action>] [--empty=(stop|drop|keep)] [(<mbox> | <Maildir>)...] -'git am' (--continue | --skip | --abort | --quit | --retry | --show-current-patch[=(diff|raw)] | --allow-empty) +git am (--continue | --skip | --abort | --quit | --retry | --show-current-patch[=(diff|raw)] | --allow-empty) DESCRIPTION ----------- @@ -30,45 +30,45 @@ OPTIONS ------- -(<mbox>|<Maildir>)...:: +`(<mbox>|<Maildir>)...`:: The list of mailbox files to read patches from. If you do not supply this argument, the command reads from the standard input. If you supply directories, they will be treated as Maildirs. --s:: ---signoff:: +`-s`:: +`--signoff`:: Add a `Signed-off-by` trailer to the commit message (see linkgit:git-interpret-trailers[1]), using the committer identity of yourself. See the signoff option in linkgit:git-commit[1] for more information. --k:: ---keep:: +`-k`:: +`--keep`:: Pass `-k` flag to linkgit:git-mailinfo[1]. ---keep-non-patch:: +`--keep-non-patch`:: Pass `-b` flag to linkgit:git-mailinfo[1]. ---keep-cr:: ---no-keep-cr:: +`--keep-cr`:: +`--no-keep-cr`:: With `--keep-cr`, call linkgit:git-mailsplit[1] with the same option, to prevent it from stripping CR at the end of lines. `am.keepcr` configuration variable can be used to specify the default behaviour. `--no-keep-cr` is useful to override `am.keepcr`. --c:: ---scissors:: +`-c`:: +`--scissors`:: Remove everything in body before a scissors line (see linkgit:git-mailinfo[1]). Can be activated by default using the `mailinfo.scissors` configuration variable. ---no-scissors:: +`--no-scissors`:: Ignore scissors lines (see linkgit:git-mailinfo[1]). ---quoted-cr=<action>:: +`--quoted-cr=<action>`:: This flag will be passed down to linkgit:git-mailinfo[1]. ---empty=(drop|keep|stop):: +`--empty=(drop|keep|stop)`:: How to handle an e-mail message lacking a patch: + -- @@ -82,23 +82,23 @@ session. This is the default behavior. -- --m:: ---message-id:: +`-m`:: +`--message-id`:: Pass the `-m` flag to linkgit:git-mailinfo[1], so that the `Message-ID` header is added to the commit message. The `am.messageid` configuration variable can be used to specify the default behaviour. ---no-message-id:: +`--no-message-id`:: Do not add the Message-ID header to the commit message. `--no-message-id` is useful to override `am.messageid`. --q:: ---quiet:: +`-q`:: +`--quiet`:: Be quiet. Only print error messages. --u:: ---utf8:: +`-u`:: +`--utf8`:: Pass `-u` flag to linkgit:git-mailinfo[1]. The proposed commit log message taken from the e-mail is re-coded into UTF-8 encoding (configuration variable @@ -108,57 +108,57 @@ This was optional in prior versions of git, but now it is the default. You can use `--no-utf8` to override this. ---no-utf8:: +`--no-utf8`:: Pass `-n` flag to linkgit:git-mailinfo[1]. --3:: ---3way:: ---no-3way:: +`-3`:: +`--3way`:: +`--no-3way`:: When the patch does not apply cleanly, fall back on 3-way merge if the patch records the identity of blobs it is supposed to apply to and we have those blobs available locally. `--no-3way` can be used to override - am.threeWay configuration variable. For more information, - see am.threeWay in linkgit:git-config[1]. + `am.threeWay` configuration variable. For more information, + see `am.threeWay` in linkgit:git-config[1]. include::rerere-options.adoc[] ---ignore-space-change:: ---ignore-whitespace:: ---whitespace=<action>:: --C<n>:: --p<n>:: ---directory=<dir>:: ---exclude=<path>:: ---include=<path>:: ---reject:: +`--ignore-space-change`:: +`--ignore-whitespace`:: +`--whitespace=<action>`:: +`-C<n>`:: +`-p<n>`:: +`--directory=<dir>`:: +`--exclude=<path>`:: +`--include=<path>`:: +`--reject`:: These flags are passed to the linkgit:git-apply[1] program that applies the patch. + -Valid <action> for the `--whitespace` option are: +Valid _<action>_ for the `--whitespace` option are: `nowarn`, `warn`, `fix`, `error`, and `error-all`. ---patch-format:: +`--patch-format`:: By default the command will try to detect the patch format automatically. This option allows the user to bypass the automatic detection and specify the patch format that the patch(es) should be interpreted as. Valid formats are mbox, mboxrd, stgit, stgit-series, and hg. --i:: ---interactive:: +`-i`:: +`--interactive`:: Run interactively. ---verify:: --n:: ---no-verify:: +`--verify`:: +`-n`:: +`--no-verify`:: Run the `pre-applypatch` and `applypatch-msg` hooks. This is the default. Skip these hooks with `-n` or `--no-verify`. See also linkgit:githooks[5]. + Note that `post-applypatch` cannot be skipped. ---committer-date-is-author-date:: +`--committer-date-is-author-date`:: By default the command records the date from the e-mail message as the commit author date, and uses the time of commit creation as the committer date. This allows the @@ -172,29 +172,29 @@ older (in terms of the commit date) than the oldest patch you are applying. ---ignore-date:: +`--ignore-date`:: By default the command records the date from the e-mail message as the commit author date, and uses the time of commit creation as the committer date. This allows the user to lie about the author date by using the same value as the committer date. ---skip:: +`--skip`:: Skip the current patch. This is only meaningful when restarting an aborted patch. --S[<keyid>]:: ---gpg-sign[=<keyid>]:: ---no-gpg-sign:: - GPG-sign commits. The `keyid` argument is optional and +`-S[<key-id>]`:: +`--gpg-sign[=<key-id>]`:: +`--no-gpg-sign`:: + GPG-sign commits. The _<key-id>_ is optional and defaults to the committer identity; if specified, it must be stuck to the option without a space. `--no-gpg-sign` is useful to countermand both `commit.gpgSign` configuration variable, and earlier `--gpg-sign`. ---continue:: --r:: ---resolved:: +`--continue`:: +`-r`:: +`--resolved`:: After a patch failure (e.g. attempting to apply conflicting patch), the user has applied it by hand and the index file stores the result of the application. @@ -202,36 +202,36 @@ extracted from the e-mail message and the current index file, and continue. ---resolvemsg=<msg>:: - When a patch failure occurs, <msg> will be printed +`--resolvemsg=<msg>`:: + When a patch failure occurs, _<msg>_ will be printed to the screen before exiting. This overrides the standard message informing you to use `--continue` or `--skip` to handle the failure. This is solely for internal use between linkgit:git-rebase[1] and linkgit:git-am[1]. ---abort:: +`--abort`:: Restore the original branch and abort the patching operation. Revert the contents of files involved in the am operation to their pre-am state. ---quit:: - Abort the patching operation but keep HEAD and the index +`--quit`:: + Abort the patching operation but keep `HEAD` and the index untouched. ---retry:: +`--retry`:: Try to apply the last conflicting patch again. This is generally only useful for passing extra options to the retry attempt (e.g., `--3way`), since otherwise you'll just see the same failure again. ---show-current-patch[=(diff|raw)]:: +`--show-current-patch[=(diff|raw)]`:: Show the message at which linkgit:git-am[1] has stopped due to conflicts. If `raw` is specified, show the raw contents of the e-mail message; if `diff`, show the diff portion only. Defaults to `raw`. ---allow-empty:: +`--allow-empty`:: After a patch failure on an input e-mail message lacking a patch, create an empty commit with the contents of the e-mail message as its log message. @@ -278,11 +278,11 @@ run `git am --abort` before running the command with mailbox names. -Before any patches are applied, ORIG_HEAD is set to the tip of the +Before any patches are applied, `ORIG_HEAD` is set to the tip of the current branch. This is useful if you have problems with multiple commits, like running linkgit:git-am[1] on the wrong branch or an error in the commits that is more easily fixed by changing the mailbox (e.g. -errors in the "From:" lines). +errors in the `From:` lines). [[caveats]] CAVEATS
diff --git a/Documentation/git-apply.adoc b/Documentation/git-apply.adoc index 6c71ee6..3f22dac 100644 --- a/Documentation/git-apply.adoc +++ b/Documentation/git-apply.adoc
@@ -8,8 +8,8 @@ SYNOPSIS -------- -[verse] -'git apply' [--stat] [--numstat] [--summary] [--check] +[synopsis] +git apply [--stat] [--numstat] [--summary] [--check] [--index | --intent-to-add] [--3way] [--ours | --theirs | --union] [--apply] [--no-add] [--build-fake-ancestor=<file>] [-R | --reverse] [--allow-binary-replacement | --binary] [--reject] [-z] @@ -35,33 +35,33 @@ OPTIONS ------- -<patch>...:: - The files to read the patch from. '-' can be used to read +`<patch>...`:: + The files to read the patch from. `-` can be used to read from the standard input. ---stat:: +`--stat`:: Instead of applying the patch, output diffstat for the input. Turns off "apply". ---numstat:: +`--numstat`:: Similar to `--stat`, but shows the number of added and deleted lines in decimal notation and the pathname without abbreviation, to make it more machine friendly. For binary files, outputs two `-` instead of saying `0 0`. Turns off "apply". ---summary:: +`--summary`:: Instead of applying the patch, output a condensed summary of information obtained from git diff extended headers, such as creations, renames, and mode changes. Turns off "apply". ---check:: +`--check`:: Instead of applying the patch, see if the patch is applicable to the current working tree and/or the index file and detects errors. Turns off "apply". ---index:: +`--index`:: Apply the patch to both the index and the working tree (or merely check that it would apply cleanly to both if `--check` is in effect). Note that `--index` expects index entries and @@ -70,13 +70,13 @@ raise an error if they are not, even if the patch would apply cleanly to both the index and the working tree in isolation. ---cached:: +`--cached`:: Apply the patch to just the index, without touching the working tree. If `--check` is in effect, merely check that it would apply cleanly to the index entry. --N:: ---intent-to-add:: +`-N`:: +`--intent-to-add`:: When applying the patch only to the working tree, mark new files to be added to the index later (see `--intent-to-add` option in linkgit:git-add[1]). This option is ignored if @@ -84,8 +84,8 @@ repository. Note that `--index` could be implied by other options such as `--3way`. --3:: ---3way:: +`-3`:: +`--3way`:: Attempt 3-way merge if the patch records the identity of blobs it is supposed to apply to and we have those blobs available locally, possibly leaving the conflict markers in the files in the working tree for the user to @@ -94,14 +94,14 @@ When used with the `--cached` option, any conflicts are left at higher stages in the cache. ---ours:: ---theirs:: ---union:: +`--ours`:: +`--theirs`:: +`--union`:: Instead of leaving conflicts in the file, resolve conflicts favouring - our (or their or both) side of the lines. Requires --3way. + our (or their or both) side of the lines. Requires `--3way`. ---build-fake-ancestor=<file>:: - Newer 'git diff' output has embedded 'index information' +`--build-fake-ancestor=<file>`:: + Newer `git diff` output has embedded 'index information' for each blob to help identify the original version that the patch applies to. When this flag is given, and if the original versions of the blobs are available locally, @@ -110,18 +110,18 @@ When a pure mode change is encountered (which has no index information), the information is read from the current index instead. --R:: ---reverse:: +`-R`:: +`--reverse`:: Apply the patch in reverse. ---reject:: - For atomicity, 'git apply' by default fails the whole patch and +`--reject`:: + For atomicity, `git apply` by default fails the whole patch and does not touch the working tree when some of the hunks do not apply. This option makes it apply the parts of the patch that are applicable, and leave the - rejected hunks in corresponding *.rej files. + rejected hunks in corresponding `*.rej` files. --z:: +`-z`:: When `--numstat` has been given, do not munge pathnames, but use a NUL-terminated machine-readable format. + @@ -129,20 +129,20 @@ explained for the configuration variable `core.quotePath` (see linkgit:git-config[1]). --p<n>:: - Remove <n> leading path components (separated by slashes) from +`-p<n>`:: + Remove _<n>_ leading path components (separated by slashes) from traditional diff paths. E.g., with `-p2`, a patch against `a/dir/file` will be applied directly to `file`. The default is 1. --C<n>:: - Ensure at least <n> lines of surrounding context match before +`-C<n>`:: + Ensure at least _<n>_ lines of surrounding context match before and after each change. When fewer lines of surrounding context exist they all must match. By default no context is ever ignored. ---unidiff-zero:: - By default, 'git apply' expects that the patch being +`--unidiff-zero`:: + By default, `git apply` expects that the patch being applied is a unified diff with at least one line of context. This provides good safety measures, but breaks down when applying a diff generated with `--unified=0`. To bypass these @@ -151,34 +151,34 @@ Note, for the reasons stated above, the usage of context-free patches is discouraged. ---apply:: +`--apply`:: If you use any of the options marked "Turns off - 'apply'" above, 'git apply' reads and outputs the + 'apply'" above, `git apply` reads and outputs the requested information without actually applying the patch. Give this flag after those flags to also apply the patch. ---no-add:: +`--no-add`:: When applying a patch, ignore additions made by the patch. This can be used to extract the common part between - two files by first running 'diff' on them and applying + two files by first running `diff` on them and applying the result with this option, which would apply the deletion part but not the addition part. ---allow-binary-replacement:: ---binary:: +`--allow-binary-replacement`:: +`--binary`:: Historically we did not allow binary patch application without an explicit permission from the user, and this flag was the way to do so. Currently, we always allow binary patch application, so this is a no-op. ---exclude=<path-pattern>:: - Don't apply changes to files matching the given path pattern. This can +`--exclude=<path-pattern>`:: + Don't apply changes to files matching _<path-pattern>_. This can be useful when importing patchsets, where you want to exclude certain files or directories. ---include=<path-pattern>:: - Apply changes to files matching the given path pattern. This can +`--include=<path-pattern>`:: + Apply changes to files matching the _<path-pattern>_. This can be useful when importing patchsets, where you want to include certain files or directories. + @@ -188,15 +188,15 @@ include/exclude pattern is used by default if there is no include pattern on the command line, and ignored if there is any include pattern. ---ignore-space-change:: ---ignore-whitespace:: +`--ignore-space-change`:: +`--ignore-whitespace`:: When applying a patch, ignore changes in whitespace in context lines if necessary. Context lines will preserve their whitespace, and they will not undergo whitespace fixing regardless of the value of the `--whitespace` option. New lines will still be fixed, though. ---whitespace=<action>:: +`--whitespace=<action>`:: When applying a patch, detect a new or modified line that has whitespace errors. What are considered whitespace errors is controlled by `core.whitespace` configuration. By default, @@ -209,7 +209,7 @@ When `git-apply` is used for statistics and not applying a patch, it defaults to `nowarn`. + -You can use different `<action>` values to control this +You can use different _<action>_ values to control this behavior: + * `nowarn` turns off the trailing whitespace warning. @@ -223,48 +223,48 @@ to apply the patch. * `error-all` is similar to `error` but shows all errors. ---inaccurate-eof:: - Under certain circumstances, some versions of 'diff' do not correctly +`--inaccurate-eof`:: + Under certain circumstances, some versions of `diff` do not correctly detect a missing new-line at the end of the file. As a result, patches - created by such 'diff' programs do not record incomplete lines + created by such `diff` programs do not record incomplete lines correctly. This option adds support for applying such patches by working around this bug. --v:: ---verbose:: +`-v`:: +`--verbose`:: Report progress to stderr. By default, only a message about the current patch being applied will be printed. This option will cause additional information to be reported. --q:: ---quiet:: +`-q`:: +`--quiet`:: Suppress stderr output. Messages about patch status and progress will not be printed. ---recount:: +`--recount`:: Do not trust the line counts in the hunk headers, but infer them by inspecting the patch (e.g. after editing the patch without adjusting the hunk headers appropriately). ---directory=<root>:: - Prepend <root> to all filenames. If a "-p" argument was also passed, +`--directory=<root>`:: + Prepend _<root>_ to all filenames. If a `-p` argument was also passed, it is applied before prepending the new root. + For example, a patch that talks about updating `a/git-gui.sh` to `b/git-gui.sh` can be applied to the file in the working tree `modules/git-gui/git-gui.sh` by running `git apply --directory=modules/git-gui`. ---unsafe-paths:: +`--unsafe-paths`:: By default, a patch that affects outside the working area (either a Git controlled working tree, or the current working - directory when "git apply" is used as a replacement of GNU - patch) is rejected as a mistake (or a mischief). + directory when `git apply` is used as a replacement of GNU + `patch`) is rejected as a mistake (or a mischief). + -When `git apply` is used as a "better GNU patch", the user can pass +When `git apply` is used as a "better GNU `patch`", the user can pass the `--unsafe-paths` option to override this safety check. This option has no effect when `--index` or `--cached` is in use. ---allow-empty:: +`--allow-empty`:: Don't return an error for patches containing no diff. This includes empty patches and patches with commit text only. @@ -273,11 +273,12 @@ include::includes/cmd-config-section-all.adoc[] +:git-apply: 1 include::config/apply.adoc[] SUBMODULES ---------- -If the patch contains any changes to submodules then 'git apply' +If the patch contains any changes to submodules then `git apply` treats these changes as follows. If `--index` is specified (explicitly or implicitly), then the submodule
diff --git a/Documentation/git-backfill.adoc b/Documentation/git-backfill.adoc index 246ab41..82d6a19 100644 --- a/Documentation/git-backfill.adoc +++ b/Documentation/git-backfill.adoc
@@ -9,7 +9,7 @@ SYNOPSIS -------- [synopsis] -git backfill [--min-batch-size=<n>] [--[no-]sparse] +git backfill [--min-batch-size=<n>] [--[no-]sparse] [--[no-]include-edges] [<revision-range>] DESCRIPTION ----------- @@ -43,7 +43,7 @@ time. By default, `git backfill` downloads all blobs reachable from the `HEAD` -commit. This set can be restricted or expanded using various options. +commit. This set can be restricted or expanded using various options below. THIS COMMAND IS EXPERIMENTAL. ITS BEHAVIOR MAY CHANGE IN THE FUTURE. @@ -63,7 +63,27 @@ current sparse-checkout. If the sparse-checkout feature is enabled, then `--sparse` is assumed and can be disabled with `--no-sparse`. -You may also specify the commit limiting options from linkgit:git-rev-list[1]. +`--include-edges`:: +`--no-include-edges`:: + Include blobs from boundary commits in the backfill. Useful in + preparation for commands like `git log -p A..B` or `git replay + --onto TARGET A..B`, where A..B normally excludes A but you need + the blobs from A as well. `--include-edges` is the default. + +`<revision-range>`:: + Backfill only blobs reachable from commits in the specified + revision range. When no _<revision-range>_ is specified, it + defaults to `HEAD` (i.e. the whole history leading to the + current commit). For a complete list of ways to spell + _<revision-range>_, see the "Specifying Ranges" section of + linkgit:gitrevisions[7]. ++ +You may also use commit-limiting options understood by +linkgit:git-rev-list[1] such as `--first-parent`, `--since`, or pathspecs. ++ +Most `--filter=<spec>` options don't work with the purpose of +`git backfill`, but the `sparse:<oid>` filter is integrated to provide a +focused set of paths to download, distinct from the `--sparse` option. SEE ALSO --------
diff --git a/Documentation/git-bisect.adoc b/Documentation/git-bisect.adoc index b0078dd..d2115b2 100644 --- a/Documentation/git-bisect.adoc +++ b/Documentation/git-bisect.adoc
@@ -8,20 +8,20 @@ SYNOPSIS -------- -[verse] -'git bisect' start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>] - [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...] -'git bisect' (bad|new|<term-new>) [<rev>] -'git bisect' (good|old|<term-old>) [<rev>...] -'git bisect' terms [--term-(good|old) | --term-(bad|new)] -'git bisect' skip [(<rev>|<range>)...] -'git bisect' next -'git bisect' reset [<commit>] -'git bisect' (visualize|view) -'git bisect' replay <logfile> -'git bisect' log -'git bisect' run <cmd> [<arg>...] -'git bisect' help +[synopsis] +git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>] + [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...] +git bisect (bad|new|<term-new>) [<rev>] +git bisect (good|old|<term-old>) [<rev>...] +git bisect terms [--term-(good|old) | --term-(bad|new)] +git bisect skip [(<rev>|<range>)...] +git bisect next +git bisect reset [<commit>] +git bisect (visualize|view) +git bisect replay <logfile> +git bisect log +git bisect run <cmd> [<arg>...] +git bisect help DESCRIPTION ----------- @@ -94,11 +94,10 @@ ~~~~~~~~~~~~ After a bisect session, to clean up the bisection state and return to -the original HEAD, issue the following command: +the original `HEAD`, issue the following command: ------------------------------------------------- -$ git bisect reset ------------------------------------------------- +[synopsis] +git bisect reset By default, this will return your tree to the commit that was checked out before `git bisect start`. (A new `git bisect start` will also do @@ -107,9 +106,9 @@ With an optional argument, you can return to a different commit instead: ------------------------------------------------- -$ git bisect reset <commit> ------------------------------------------------- +[synopsis] +git bisect reset <commit> + For example, `git bisect reset bisect/bad` will check out the first bad revision, while `git bisect reset HEAD` will leave you on the @@ -143,23 +142,20 @@ bisect start` without commits as argument and then run the following commands to add the commits: ------------------------------------------------- +[synopsis] git bisect old [<rev>] ------------------------------------------------- to indicate that a commit was before the sought change, or ------------------------------------------------- +[synopsis] git bisect new [<rev>...] ------------------------------------------------- to indicate that it was after. To get a reminder of the currently used terms, use ------------------------------------------------- +[synopsis] git bisect terms ------------------------------------------------- You can get just the old term with `git bisect terms --term-old` or `git bisect terms --term-good`; `git bisect terms --term-new` @@ -171,21 +167,20 @@ subcommands like `reset`, `start`, ...) by starting the bisection using ------------------------------------------------- +[synopsis] git bisect start --term-old <term-old> --term-new <term-new> ------------------------------------------------- For example, if you are looking for a commit that introduced a performance regression, you might use ------------------------------------------------ -git bisect start --term-old fast --term-new slow +$ git bisect start --term-old fast --term-new slow ------------------------------------------------ Or if you are looking for the commit that fixed a bug, you might use ------------------------------------------------ -git bisect start --term-new fixed --term-old broken +$ git bisect start --term-new fixed --term-old broken ------------------------------------------------ Then, use `git bisect <term-old>` and `git bisect <term-new>` instead @@ -194,7 +189,7 @@ Bisect visualize/view ~~~~~~~~~~~~~~~~~~~~~ -To see the currently remaining suspects in 'gitk', issue the following +To see the currently remaining suspects in `gitk`, issue the following command during the bisection process (the subcommand `view` can be used as an alternative to `visualize`): @@ -203,12 +198,13 @@ ------------ Git detects a graphical environment through various environment variables: -`DISPLAY`, which is set in X Window System environments on Unix systems. -`SESSIONNAME`, which is set under Cygwin in interactive desktop sessions. -`MSYSTEM`, which is set under Msys2 and Git for Windows. -`SECURITYSESSIONID`, which may be set on macOS in interactive desktop sessions. -If none of these environment variables is set, 'git log' is used instead. +`DISPLAY`:: which is set in X Window System environments on Unix systems. +`SESSIONNAME`:: which is set under Cygwin in interactive desktop sessions. +`MSYSTEM`:: which is set under Msys2 and Git for Windows. +`SECURITYSESSIONID`:: which may be set on macOS in interactive desktop sessions. + +If none of these environment variables is set, `git log` is used instead. You can also give command-line options such as `-p` and `--stat`. ------------ @@ -332,18 +328,17 @@ If you have a script that can tell if the current source code is good or bad, you can bisect by issuing the command: ------------- -$ git bisect run my_script arguments ------------- +[synopsis] +git bisect run <cmd> [<arg>...] -Note that the script (`my_script` in the above example) should exit +Note that _<cmd>_ run with _<arg>_ should exit with code 0 if the current source code is good/old, and exit with a code between 1 and 127 (inclusive), except 125, if the current source code is bad/new. Any other exit code will abort the bisect process. It should be noted -that a program that terminates via `exit(-1)` leaves $? = 255, (see the -exit(3) manual page), as the value is chopped with `& 0377`. +that a program that terminates via `exit(-1)` leaves `$?` = 255, (see the +`exit`(3) manual page), as the value is chopped with `& 0377`. The special exit code 125 should be used when the current source code cannot be tested. If the script exits with this code, the current @@ -355,12 +350,12 @@ `bisect run` is concerned). You may often find that during a bisect session you want to have -temporary modifications (e.g. s/#define DEBUG 0/#define DEBUG 1/ in a +temporary modifications (e.g. `s/#define DEBUG 0/#define DEBUG 1/` in a header file, or "revision that does not have this commit needs this patch applied to work around another problem this bisection is not interested in") applied to the revision being tested. -To cope with such a situation, after the inner 'git bisect' finds the +To cope with such a situation, after the inner `git bisect` finds the next revision to test, the script can apply the patch before compiling, run the real test, and afterwards decide if the revision (possibly with the needed patch) passed the test and then @@ -370,20 +365,18 @@ OPTIONS ------- ---no-checkout:: -+ -Do not checkout the new working tree at each iteration of the bisection -process. Instead just update the reference named `BISECT_HEAD` to make -it point to the commit that should be tested. +`--no-checkout`:: + Do not checkout the new working tree at each iteration of the bisection + process. Instead just update the reference named `BISECT_HEAD` to make + it point to the commit that should be tested. + This option may be useful when the test you would perform in each step does not require a checked out tree. + If the repository is bare, `--no-checkout` is assumed. ---first-parent:: -+ -Follow only the first parent commit upon seeing a merge commit. +`--first-parent`:: + Follow only the first parent commit upon seeing a merge commit. + In detecting regressions introduced through the merging of a branch, the merge commit will be identified as introduction of the bug and its ancestors will be @@ -395,7 +388,7 @@ EXAMPLES -------- -* Automatically bisect a broken build between v1.2 and HEAD: +* Automatically bisect a broken build between v1.2 and `HEAD`: + ------------ $ git bisect start HEAD v1.2 -- # HEAD is bad, v1.2 is good @@ -403,7 +396,7 @@ $ git bisect reset # quit the bisect session ------------ -* Automatically bisect a test failure between origin and HEAD: +* Automatically bisect a test failure between origin and `HEAD`: + ------------ $ git bisect start HEAD origin -- # HEAD is bad, origin is good @@ -430,7 +423,7 @@ + It is safer if both `test.sh` and `check_test_case.sh` are outside the repository to prevent interactions between the bisect, -make and test processes and the scripts. +`make` and test processes and the scripts. * Automatically bisect with temporary modifications (hot-fix): + @@ -491,9 +484,9 @@ $ git bisect reset # quit the bisect session ------------ + -In this case, when 'git bisect run' finishes, bisect/bad will refer to a commit that +In this case, when `git bisect run` finishes, `bisect/bad` will refer to a commit that has at least one parent whose reachable graph is fully traversable in the sense -required by 'git pack objects'. +required by `git pack-objects`. * Look for a fix instead of a regression in the code +
diff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc index c139f55..86b9181 100644 --- a/Documentation/git-cat-file.adoc +++ b/Documentation/git-cat-file.adoc
@@ -174,6 +174,11 @@ since the beginning or since the last flush was issued. When `--buffer` is used, no output will come until a `flush` is issued. When `--buffer` is not used, commands are flushed each time without issuing `flush`. + +`mailmap (<bool>)`:: + Enable or disable mailmap for subsequent commands. The `<bool>` + argument accepts the same boolean values as linkgit:git-config[1]. + The mailmap data is read upon the first use and only once. -- +
diff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc index 43ccf47..a8b3b8c 100644 --- a/Documentation/git-checkout.adoc +++ b/Documentation/git-checkout.adoc
@@ -251,20 +251,19 @@ are different between the current branch and the branch to which you are switching, the command refuses to switch branches in order to preserve your modifications in context. - However, with this option, a three-way merge between the current - branch, your working tree contents, and the new branch - is done, and you will be on the new branch. -+ -When a merge conflict happens, the index entries for conflicting -paths are left unmerged, and you need to resolve the conflicts -and mark the resolved paths with `git add` (or `git rm` if the merge -should result in deletion of the path). + With this option, the conflicting local changes are + automatically stashed before the switch and reapplied + afterwards. If the local changes do not overlap with the + differences between branches, the switch proceeds without + stashing. If reapplying the stash results in conflicts, the + entry is saved to the stash list. Resolve the conflicts + and run `git stash drop` when done, or clear the working + tree (e.g. with `git reset --hard`) before running `git stash + pop` later to re-apply your changes. + When checking out paths from the index, this option lets you recreate the conflicted merge in the specified paths. This option cannot be used when checking out paths from a tree-ish. -+ -When switching branches with `--merge`, staged changes may be lost. `--conflict=<style>`:: The same as `--merge` option above, but changes the way the @@ -578,38 +577,36 @@ error: You have local changes to 'frotz'; not switching branches. ------------ -You can give the `-m` flag to the command, which would try a -three-way merge: +You can give the `-m` flag to the command, which will carry your local +changes to the new branch: ------------ $ git checkout -m mytopic -Auto-merging frotz +Applied autostash. +Switched to branch 'mytopic' +The following paths have local changes: +M frotz ------------ -After this three-way merge, the local modifications are _not_ +After the switch, the local modifications are reapplied and are _not_ registered in your index file, so `git diff` would show you what changes you made since the tip of the new branch. === 3. Merge conflict -When a merge conflict happens during switching branches with -the `-m` option, you would see something like this: +When the `--merge` (`-m`) option is given and the local changes +overlap with the changes in the branch we're switching to, the +changes are stashed and reapplied after the switch. If this +process results in conflicts, the stash entry is saved and a +message is printed: ------------ $ git checkout -m mytopic -Auto-merging frotz -ERROR: Merge conflict in frotz -fatal: merge program failed ------------- - -At this point, `git diff` shows the changes cleanly merged as in -the previous example, as well as the changes in the conflicted -files. Edit and resolve the conflict and mark it resolved with -`git add` as usual: - ------------- -$ edit frotz -$ git add frotz +Your local changes are stashed, however applying them +resulted in conflicts. You can either resolve the conflicts +and then discard the stash with "git stash drop", or, if you +do not want to resolve them now, run "git reset --hard" and +apply the local changes later by running "git stash pop". ------------ CONFIGURATION
diff --git a/Documentation/git-commit-graph.adoc b/Documentation/git-commit-graph.adoc index 6d19026..f2a37e9 100644 --- a/Documentation/git-commit-graph.adoc +++ b/Documentation/git-commit-graph.adoc
@@ -146,6 +146,12 @@ $ git rev-parse HEAD | git commit-graph write --stdin-commits --append ------------------------------------------------ +CAVEATS +------- + +The existence of replace objects or commit grafts turns off reading or +writing to the commit-graph. See linkgit:git-replace[1]. + CONFIGURATION -------------
diff --git a/Documentation/git-config.adoc b/Documentation/git-config.adoc index 00545b2..57af010ad 100644 --- a/Documentation/git-config.adoc +++ b/Documentation/git-config.adoc
@@ -119,10 +119,10 @@ Append a comment at the end of new or modified lines. + If _<message>_ begins with one or more whitespaces followed -by "#", it is used as-is. If it begins with "#", a space is -prepended before it is used. Otherwise, a string " # " (a +by `#`, it is used as-is. If it begins with `#`, a space is +prepended before it is used. Otherwise, a string `" # "` (a space followed by a hash followed by a space) is prepended -to it. And the resulting string is placed immediately after +to it. The resulting string is placed immediately after the value defined for the variable. The _<message>_ must not contain linefeed characters (no multi-line comments are permitted). @@ -221,6 +221,7 @@ + Valid `<type>`'s include: + +-- - 'bool': canonicalize values `true`, `yes`, `on`, and positive numbers as "true", and values `false`, `no`, `off` and `0` as "false". @@ -239,6 +240,7 @@ escape sequence. When setting a value, a sanity-check is performed to ensure that the given value is canonicalize-able as an ANSI color, but it is written as-is. +-- + If the command is in `list` mode, then the `--type <type>` argument will apply to each listed config value. If the value does not successfully parse in that
diff --git a/Documentation/git-describe.adoc b/Documentation/git-describe.adoc index 08ff715..b2cb1e4 100644 --- a/Documentation/git-describe.adoc +++ b/Documentation/git-describe.adoc
@@ -7,10 +7,10 @@ SYNOPSIS -------- -[verse] -'git describe' [--all] [--tags] [--contains] [--abbrev=<n>] [<commit-ish>...] -'git describe' [--all] [--tags] [--contains] [--abbrev=<n>] --dirty[=<mark>] -'git describe' <blob> +[synopsis] +git describe [--all] [--tags] [--contains] [--abbrev=<n>] [<commit-ish>...] +git describe [--all] [--tags] [--contains] [--abbrev=<n>] --dirty[=<mark>] +git describe <blob> DESCRIPTION ----------- @@ -22,70 +22,70 @@ is a "human-readable" object name which can also be used to identify the commit to other git commands. -By default (without --all or --tags) `git describe` only shows +By default (without `--all` or `--tags`) `git describe` only shows annotated tags. For more information about creating annotated tags -see the -a and -s options to linkgit:git-tag[1]. +see the `-a` and `-s` options to linkgit:git-tag[1]. If the given object refers to a blob, it will be described as `<commit-ish>:<path>`, such that the blob can be found -at `<path>` in the `<commit-ish>`, which itself describes the +at _<path>_ in the _<commit-ish>_, which itself describes the first commit in which this blob occurs in a reverse revision walk -from HEAD. +from `HEAD`. OPTIONS ------- -<commit-ish>...:: - Commit-ish object names to describe. Defaults to HEAD if omitted. +`<commit-ish>...`:: + Commit-ish object names to describe. Defaults to `HEAD` if omitted. ---dirty[=<mark>]:: ---broken[=<mark>]:: +`--dirty[=<mark>]`:: +`--broken[=<mark>]`:: Describe the state of the working tree. When the working - tree matches HEAD, the output is the same as "git describe - HEAD". If the working tree has local modification "-dirty" + tree matches `HEAD`, the output is the same as `git describe HEAD`. + If the working tree has local modification, `-dirty` is appended to it. If a repository is corrupt and Git cannot determine if there is local modification, Git will - error out, unless `--broken' is given, which appends - the suffix "-broken" instead. + error out, unless `--broken` is given, which appends + the suffix `-broken` instead. ---all:: +`--all`:: Instead of using only the annotated tags, use any ref found in `refs/` namespace. This option enables matching any known branch, remote-tracking branch, or lightweight tag. ---tags:: +`--tags`:: Instead of using only the annotated tags, use any tag found in `refs/tags` namespace. This option enables matching a lightweight (non-annotated) tag. ---contains:: +`--contains`:: Instead of finding the tag that predates the commit, find the tag that comes after the commit, and thus contains it. - Automatically implies --tags. + Automatically implies `--tags`. ---abbrev=<n>:: +`--abbrev=<n>`:: Instead of using the default number of hexadecimal digits (which will vary according to the number of objects in the repository with - a default of 7) of the abbreviated object name, use <n> digits, or - as many digits as needed to form a unique object name. An <n> of 0 + a default of 7) of the abbreviated object name, use _<n>_ digits, or + as many digits as needed to form a unique object name. An _<n>_ of 0 will suppress long format, only showing the closest tag. ---candidates=<n>:: +`--candidates=<n>`:: Instead of considering only the 10 most recent tags as candidates to describe the input commit-ish consider - up to <n> candidates. Increasing <n> above 10 will take + up to _<n>_ candidates. Increasing _<n>_ above 10 will take slightly longer but may produce a more accurate result. - An <n> of 0 will cause only exact matches to be output. + An _<n>_ of 0 will cause only exact matches to be output. ---exact-match:: +`--exact-match`:: Only output exact matches (a tag directly references the - supplied commit). This is a synonym for --candidates=0. + supplied commit). This is a synonym for `--candidates=0`. ---debug:: +`--debug`:: Verbosely display information about the searching strategy being employed to standard error. The tag name will still be printed to standard out. ---long:: +`--long`:: Always output the long format (the tag, the number of commits and the abbreviated commit name) even when it matches a tag. This is useful when you want to see parts of the commit object name @@ -94,8 +94,8 @@ describe such a commit as v1.2-0-gdeadbee (0th commit since tag v1.2 that points at object deadbee....). ---match <pattern>:: - Only consider tags matching the given `glob(7)` pattern, +`--match <pattern>`:: + Only consider tags matching the given `glob`(7) pattern, excluding the "refs/tags/" prefix. If used with `--all`, it also considers local branches and remote-tracking references matching the pattern, excluding respectively "refs/heads/" and "refs/remotes/" @@ -104,22 +104,22 @@ matching any of the patterns will be considered. Use `--no-match` to clear and reset the list of patterns. ---exclude <pattern>:: - Do not consider tags matching the given `glob(7)` pattern, excluding +`--exclude <pattern>`:: + Do not consider tags matching the given `glob`(7) pattern, excluding the "refs/tags/" prefix. If used with `--all`, it also does not consider local branches and remote-tracking references matching the pattern, - excluding respectively "refs/heads/" and "refs/remotes/" prefix; + excluding respectively "`refs/heads/`" and "`refs/remotes/`" prefix; references of other types are never considered. If given multiple times, a list of patterns will be accumulated and tags matching any of the - patterns will be excluded. When combined with --match a tag will be - considered when it matches at least one --match pattern and does not - match any of the --exclude patterns. Use `--no-exclude` to clear and + patterns will be excluded. When combined with `--match` a tag will be + considered when it matches at least one `--match` pattern and does not + match any of the `--exclude` patterns. Use `--no-exclude` to clear and reset the list of patterns. ---always:: +`--always`:: Show uniquely abbreviated commit object as fallback. ---first-parent:: +`--first-parent`:: Follow only the first parent commit upon seeing a merge commit. This is useful when you wish to not match tags on branches merged in the history of the target commit. @@ -139,8 +139,8 @@ at the end. The number of additional commits is the number -of commits which would be displayed by "git log v1.0.4..parent". -The hash suffix is "-g" + an unambiguous abbreviation for the tip commit +of commits which would be displayed by `git log v1.0.4..parent`. +The hash suffix is "`-g`" + an unambiguous abbreviation for the tip commit of parent (which was `2414721b194453f058079d897d13c4e377f92dc6`). The length of the abbreviation scales as the repository grows, using the approximate number of objects in the repository and a bit of math @@ -149,12 +149,12 @@ a software depending on the SCM the software is managed with. This is useful in an environment where people may use different SCMs. -Doing a 'git describe' on a tag-name will just show the tag name: +Doing a `git describe` on a tag-name will just show the tag name: [torvalds@g5 git]$ git describe v1.0.4 v1.0.4 -With --all, the command can use branch heads as references, so +With `--all`, the command can use branch heads as references, so the output shows the reference path as well: [torvalds@g5 git]$ git describe --all --abbrev=4 v1.0.5^2 @@ -163,7 +163,7 @@ [torvalds@g5 git]$ git describe --all --abbrev=4 HEAD^ heads/lt/describe-7-g975b -With --abbrev set to 0, the command can be used to find the +With `--abbrev` set to 0, the command can be used to find the closest tagname without any suffix: [torvalds@g5 git]$ git describe --abbrev=0 v1.0.5^2 @@ -179,13 +179,13 @@ SEARCH STRATEGY --------------- -For each commit-ish supplied, 'git describe' will first look for +For each commit-ish supplied, `git describe` will first look for a tag which tags exactly that commit. Annotated tags will always be preferred over lightweight tags, and tags with newer dates will always be preferred over tags with older dates. If an exact match is found, its name will be output and searching will stop. -If an exact match was not found, 'git describe' will walk back +If an exact match was not found, `git describe` will walk back through the commit history to locate an ancestor commit which has been tagged. The ancestor's tag will be output along with an abbreviation of the input commit-ish's SHA-1. If `--first-parent` was @@ -203,7 +203,7 @@ Tree objects as well as tag objects not pointing at commits, cannot be described. When describing blobs, the lightweight tags pointing at blobs are ignored, -but the blob is still described as <commit-ish>:<path> despite the lightweight +but the blob is still described as `<commit-ish>:<path>` despite the lightweight tag being favorable. GIT
diff --git a/Documentation/git-difftool.adoc b/Documentation/git-difftool.adoc index 064bc68..dd7cacf 100644 --- a/Documentation/git-difftool.adoc +++ b/Documentation/git-difftool.adoc
@@ -7,64 +7,64 @@ SYNOPSIS -------- -[verse] -'git difftool' [<options>] [<commit> [<commit>]] [--] [<path>...] +[synopsis] +git difftool [<options>] [<commit> [<commit>]] [--] [<path>...] DESCRIPTION ----------- -'git difftool' is a Git command that allows you to compare and edit files -between revisions using common diff tools. 'git difftool' is a frontend -to 'git diff' and accepts the same options and arguments. See +`git difftool` is a Git command that allows you to compare and edit files +between revisions using common diff tools. `git difftool` is a frontend +to `git diff` and accepts the same options and arguments. See linkgit:git-diff[1]. OPTIONS ------- --d:: ---dir-diff:: +`-d`:: +`--dir-diff`:: Copy the modified files to a temporary location and perform a directory diff on them. This mode never prompts before launching the diff tool. --y:: ---no-prompt:: +`-y`:: +`--no-prompt`:: Do not prompt before launching a diff tool. ---prompt:: +`--prompt`:: Prompt before each invocation of the diff tool. This is the default behaviour; the option is provided to override any configuration settings. ---rotate-to=<file>:: - Start showing the diff for the given path, +`--rotate-to=<file>`:: + Start showing the diff for _<file>_, the paths before it will move to the end and output. ---skip-to=<file>:: - Start showing the diff for the given path, skipping all +`--skip-to=<file>`:: + Start showing the diff for _<file>_, skipping all the paths before it. --t <tool>:: ---tool=<tool>:: - Use the diff tool specified by <tool>. Valid values include +`-t <tool>`:: +`--tool=<tool>`:: + Use the diff tool specified by _<tool>_. Valid values include emerge, kompare, meld, and vimdiff. Run `git difftool --tool-help` - for the list of valid <tool> settings. + for the list of valid _<tool>_ settings. + -If a diff tool is not specified, 'git difftool' +If a diff tool is not specified, `git difftool` will use the configuration variable `diff.tool`. If the -configuration variable `diff.tool` is not set, 'git difftool' +configuration variable `diff.tool` is not set, `git difftool` will pick a suitable default. + You can explicitly provide a full path to the tool by setting the configuration variable `difftool.<tool>.path`. For example, you can configure the absolute path to kdiff3 by setting -`difftool.kdiff3.path`. Otherwise, 'git difftool' assumes the +`difftool.kdiff3.path`. Otherwise, `git difftool` assumes the tool is available in PATH. + Instead of running one of the known diff tools, -'git difftool' can be customized to run an alternative program +`git difftool` can be customized to run an alternative program by specifying the command line to invoke in a configuration variable `difftool.<tool>.cmd`. + -When 'git difftool' is invoked with this tool (either through the +When `git difftool` is invoked with this tool (either through the `-t` or `--tool` option or the `diff.tool` configuration variable) the configured command line will be invoked with the following variables available: `$LOCAL` is set to the name of the temporary @@ -74,30 +74,30 @@ being compared. `$BASE` is provided for compatibility with custom merge tool commands and has the same value as `$MERGED`. ---tool-help:: +`--tool-help`:: Print a list of diff tools that may be used with `--tool`. ---symlinks:: ---no-symlinks:: - 'git difftool''s default behavior is to create symlinks to the +`--symlinks`:: +`--no-symlinks`:: + `git difftool`'s default behavior is to create symlinks to the working tree when run in `--dir-diff` mode and the right-hand side of the comparison yields the same content as the file in the working tree. + -Specifying `--no-symlinks` instructs 'git difftool' to create copies +Specifying `--no-symlinks` instructs `git difftool` to create copies instead. `--no-symlinks` is the default on Windows. --x <command>:: ---extcmd=<command>:: +`-x <command>`:: +`--extcmd=<command>`:: Specify a custom command for viewing diffs. - 'git-difftool' ignores the configured defaults and runs + `git-difftool` ignores the configured defaults and runs `<command> $LOCAL $REMOTE` when this option is specified. Additionally, `$BASE` is set in the environment. --g:: ---gui:: ---no-gui:: - When 'git-difftool' is invoked with the `-g` or `--gui` option +`-g`:: +`--gui`:: +`--no-gui`:: + When `git-difftool` is invoked with the `-g` or `--gui` option the default diff tool will be read from the configured `diff.guitool` variable instead of `diff.tool`. This may be selected automatically using the configuration variable @@ -106,20 +106,20 @@ fallback in the order of `merge.guitool`, `diff.tool`, `merge.tool` until a tool is found. ---trust-exit-code:: ---no-trust-exit-code:: +`--trust-exit-code`:: +`--no-trust-exit-code`:: Errors reported by the diff tool are ignored by default. - Use `--trust-exit-code` to make 'git-difftool' exit when an + Use `--trust-exit-code` to make `git-difftool` exit when an invoked diff tool returns a non-zero exit code. + -'git-difftool' will forward the exit code of the invoked tool when +`git-difftool` will forward the exit code of the invoked tool when `--trust-exit-code` is used. See linkgit:git-diff[1] for the full list of supported options. CONFIGURATION ------------- -'git difftool' falls back to 'git mergetool' config variables when the +`git difftool` falls back to `git mergetool` config variables when the difftool equivalents have not been defined. include::includes/cmd-config-section-rest.adoc[]
diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc index 5662382..f7905c0 100644 --- a/Documentation/git-format-patch.adoc +++ b/Documentation/git-format-patch.adoc
@@ -221,10 +221,9 @@ for generating the cover letter. --subject-prefix=<subject-prefix>:: - Instead of the standard '[PATCH]' prefix in the subject - line, instead use '[<subject-prefix>]'. This can be used - to name a patch series, and can be combined with the - `--numbered` option. + Use '[<subject-prefix>]' instead of the standard '[PATCH]' + prefix in the subject line. This can be used to name a patch + series, and can be combined with the `--numbered` option. + The configuration variable `format.subjectPrefix` may also be used to configure a subject prefix to apply to a given repository for
diff --git a/Documentation/git-format-rev.adoc b/Documentation/git-format-rev.adoc new file mode 100644 index 0000000..505a52f --- /dev/null +++ b/Documentation/git-format-rev.adoc
@@ -0,0 +1,215 @@ +git-format-rev(1) +================= + +NAME +---- +git-format-rev - EXPERIMENTAL: Pretty format revisions on demand + + +SYNOPSIS +-------- +[synopsis] +(EXPERIMENTAL!) git format-rev --stdin-mode=<mode> --format=<pretty> [--[no-]notes=<ref>] [-z] [--[no-]null-output] [--[no-]null-input] + +DESCRIPTION +----------- + +Pretty format revisions from standard input. + +THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE. + +OPTIONS +------- + +`--stdin-mode=<mode>`:: + How to interpret standard input data: ++ +-- +`revs`;; Each line or record (see the <<io,INPUT AND OUTPUT FORMATS>> + section) is interpreted as a commit. Any kind of revision + expression can be used (see linkgit:gitrevisions[7]). Annotated + tags are peeled (see linkgit:gitglossary[7]). ++ +The argument `rev` is also accepted. + +`text`;; Formats all commit object names found in freeform text. These + must be full object names, i.e. abbreviated hexadecimal object + names will not be interpreted. ++ +Anything that is parsed as an object name but that is not found to be a +commit object name is left alone (echoed). +-- + +`--format=<pretty>`:: + Pretty format string. + +`--notes=<ref>`:: +`--no-notes`:: + Custom notes ref. Notes are displayed when using the `%N` + atom. See linkgit:git-notes[1]. + +`-z`:: +`--null`:: + Use _NUL_ character to terminate both input and output instead + of newline. This option cannot be negated. ++ +This is useful if both the input and output could contain newlines or if +the input to this command also uses _NUL_ character termination; see the +<<io,INPUT AND OUTPUT FORMATS>> section below. ++ +The mode `--stdin-mode=text` can have use for this option when it needs +to process input like for example `git last-modified -z`; see the +<<examples,EXAMPLES>> section below. + +`--null-output`:: +`--no-null-output`:: + Use _NUL_ character to terminate output instead of newline. The + default is `--no-null-output`. ++ +This is useful if the output could contain newlines, for example if the +`%n` (newline) atom is used. + +`--null-input`:: +`--no-null-input`:: + Use _NUL_ character to terminate input instead of newline. The + default is `--no-null-input`. ++ +This is useful if the input revision expressions could contain newlines. + +[[io]] +INPUT AND OUTPUT FORMAT +----------------------- + +The command uses newlines for both input and output termination by +default. See the `-z`, `--null-output`, and `--null-input` options for +using _NUL_ character as the terminator. + +The mode `--stdin-mode=revs` outputs one formatted commit followed by +the terminator. This could either be called a _line_ or a _record_ in +case "line" is too suggestive of newline termination. + +Note that this means that the terminator character (newline or _NUL_) +acts as a _terminator_, not a _separator_. In other words, the final +line or record is also terminated by the terminator character. + +The mode `--stdin-mode=text` replaces each object name with the +formatted commit, i.e. the format `%s` would transform some commit +object name to `<subject>` without any termination. Like this: + +---- +Did we not fix this in "<subject>"? +---- + +It is safe to interactively read and write from this command since each +record is immediately flushed. + +[[examples]] +EXAMPLES +-------- + +The command linkgit:git-last-modified[1] shows the commit that each file +was last modified in. + +---- +$ git last-modified -- README.md Makefile +7798034171030be0909c56377a4e0e10e6d2df93 Makefile +c50fbb2dd225e7e82abba4380423ae105089f4d7 README.md +---- + +We can pipe the result to this command in order to replace the object +name with the commit author. + +---- +$ git last-modified -- README.md Makefile | + git format-rev --stdin-mode=text --format=%an +Junio C Hamano Makefile +Todd Zullinger README.md +---- + +Another example is _formatting commits in commit messages_. Given this commit message: + +---- +Fix off-by-one error + +Fix off-by-one error introduced in +e83c5163316f89bfbde7d9ab23ca2e25604af290. + +We thought we fixed this in 5569bf9bbedd63a00780fc5c110e0cfab3aa97b9 but +that only covered 1/3 of the faulty cases. +---- + +We can format the commits and use par(1) to reflow the text, say in a +`commit-msg` hook: + +---- +$ git config set hook.reference-commits.event commit-msg +$ git config set hook.reference-commits.command reference-commits +$ cat $(which reference-commits) +#/bin/sh + +msg="$1" +rewritten=$(mktemp) +git format-rev --stdin-mode=text --format=reference <"$msg" | + par >"$rewritten" +mv "$rewritten" "$msg" +---- + +Which will produce something like this: + +---- +Fix off-by-one error + +Fix off-by-one error introduced in e83c5163316 (Implement better memory +allocator, 2005-04-07). + +We thought we fixed this in 5569bf9bbed (Fix memory allocator, +2005-06-22) but that only covered 1/3 of the faulty cases. +---- + +DISCUSSION +---------- + +This command lets you format any number of revisions in any order +through one command invocation. Consider the +linkgit:git-last-modified[1] case from the <<examples,EXAMPLES>> section +above: + +1. There might be hundreds of files +2. Commits can be repeated, i.e. two or more files were last modified in + the same commit + +Two widely-used commands which pretty formats commits are +linkgit:git-log[1] and linkgit:git-show[1]. It turns out that they are +not a good fit for the above use case. + +- The output of linkgit:git-last-modified[1] would have to be processed + in stages since you need to transform the first column separately and + then link the author to the filename. But this is surmountable. +- You can feed each commit to `git show` or `git log --no-walk -1`. But + that means that you need to create a process for each line. +- Let’s say that you want to use one process, not one per line. So you + want to feed all the commits to the command. Now you face the problem + that you have to feed all the commits to the commands before you get + any output (this is also the case for the `--stdin` modes). In other + words, you cannot loop through each line, get the author for the + commit, and output the author and the filename. You need to feed all + the commits, get back all the output, and match the output with the + filename. +- But the next problem is that commands will deduplicate the input and + only output one commit one single time only. Thus you cannot make the + output order match the input order, since a commit could have been + repeated in the original input. + +In short, it is straightforward to use these two commands if you use one +process per line. It is much more work if you just want to use one +process, but still doable. In contrast, this problem is solved with just +another shell pipeline with this command. + +SEE ALSO +-------- +linkgit:git-name-rev[1], +linkgit:git-log[1]. + +GIT +--- +Part of the linkgit:git[1] suite
diff --git a/Documentation/git-fsmonitor--daemon.adoc b/Documentation/git-fsmonitor--daemon.adoc index 8fe5241..12fa866 100644 --- a/Documentation/git-fsmonitor--daemon.adoc +++ b/Documentation/git-fsmonitor--daemon.adoc
@@ -76,9 +76,9 @@ correctly with all network-mounted repositories, so such use is considered experimental. -On Mac OS, the inter-process communication (IPC) between various Git +On Mac OS and Linux, the inter-process communication (IPC) between various Git commands and the fsmonitor daemon is done via a Unix domain socket (UDS) -- a -special type of file -- which is supported by native Mac OS filesystems, +special type of file -- which is supported by native Mac OS and Linux filesystems, but not on network-mounted filesystems, NTFS, or FAT32. Other filesystems may or may not have the needed support; the fsmonitor daemon is not guaranteed to work with these filesystems and such use is considered experimental. @@ -87,13 +87,33 @@ `.git` directory is on a network-mounted filesystem, it will instead be created at `$HOME/.git-fsmonitor-*` unless `$HOME` itself is on a network-mounted filesystem, in which case you must set the configuration -variable `fsmonitor.socketDir` to the path of a directory on a Mac OS native +variable `fsmonitor.socketDir` to the path of a directory on a native filesystem in which to create the socket file. If none of the above directories (`.git`, `$HOME`, or `fsmonitor.socketDir`) -is on a native Mac OS file filesystem the fsmonitor daemon will report an +is on a native filesystem the fsmonitor daemon will report an error that will cause the daemon and the currently running command to exit. +LINUX CAVEATS +~~~~~~~~~~~~~ + +On Linux, the fsmonitor daemon uses inotify to monitor filesystem events. +The inotify system has per-user limits on the number of watches that can +be created. The default limit is typically 8192 watches per user. + +For large repositories with many directories, you may need to increase +this limit. Check the current limit with: + + cat /proc/sys/fs/inotify/max_user_watches + +To temporarily increase the limit: + + sudo sysctl fs.inotify.max_user_watches=65536 + +To make the change permanent, add to `/etc/sysctl.conf`: + + fs.inotify.max_user_watches=65536 + CONFIGURATION -------------
diff --git a/Documentation/git-grep.adoc b/Documentation/git-grep.adoc index a548585..19b3ade 100644 --- a/Documentation/git-grep.adoc +++ b/Documentation/git-grep.adoc
@@ -8,8 +8,8 @@ SYNOPSIS -------- -[verse] -'git grep' [-a | --text] [-I] [--textconv] [-i | --ignore-case] [-w | --word-regexp] +[synopsis] +git grep [-a | --text] [-I] [--textconv] [-i | --ignore-case] [-w | --word-regexp] [-v | --invert-match] [-h|-H] [--full-name] [-E | --extended-regexp] [-G | --basic-regexp] [-P | --perl-regexp] @@ -41,139 +41,139 @@ OPTIONS ------- ---cached:: +`--cached`:: Instead of searching tracked files in the working tree, search blobs registered in the index file. ---untracked:: +`--untracked`:: In addition to searching in the tracked files in the working tree, search also in untracked files. ---no-index:: +`--no-index`:: Search files in the current directory that is not managed by Git, or by ignoring that the current directory is managed by Git. This - is rather similar to running the regular `grep(1)` utility with its + is rather similar to running the regular `grep`(1) utility with its `-r` option specified, but with some additional benefits, such as - using pathspec patterns to limit paths; see the 'pathspec' entry + using pathspec patterns to limit paths; see the `pathspec` entry in linkgit:gitglossary[7] for more information. + This option cannot be used together with `--cached` or `--untracked`. See also `grep.fallbackToNoIndex` in 'CONFIGURATION' below. ---no-exclude-standard:: +`--no-exclude-standard`:: Also search in ignored files by not honoring the `.gitignore` mechanism. Only useful with `--untracked`. ---exclude-standard:: +`--exclude-standard`:: Do not pay attention to ignored files specified via the `.gitignore` mechanism. Only useful when searching files in the current directory with `--no-index`. ---recurse-submodules:: +`--recurse-submodules`:: Recursively search in each submodule that is active and checked out in the repository. When used in combination with the _<tree>_ option the prefix of all submodule output will be the name of the parent project's _<tree>_ object. This option cannot be used together with `--untracked`, and it has no effect if `--no-index` is specified. --a:: ---text:: +`-a`:: +`--text`:: Process binary files as if they were text. ---textconv:: +`--textconv`:: Honor textconv filter settings. ---no-textconv:: +`--no-textconv`:: Do not honor textconv filter settings. This is the default. --i:: ---ignore-case:: +`-i`:: +`--ignore-case`:: Ignore case differences between the patterns and the files. --I:: +`-I`:: Don't match the pattern in binary files. ---max-depth <depth>:: - For each <pathspec> given on command line, descend at most <depth> +`--max-depth <depth>`:: + For each _<pathspec>_ given on command line, descend at most _<depth>_ levels of directories. A value of -1 means no limit. - This option is ignored if <pathspec> contains active wildcards. + This option is ignored if _<pathspec>_ contains active wildcards. In other words if "a*" matches a directory named "a*", - "*" is matched literally so --max-depth is still effective. + "*" is matched literally so `--max-depth` is still effective. --r:: ---recursive:: +`-r`:: +`--recursive`:: Same as `--max-depth=-1`; this is the default. ---no-recursive:: +`--no-recursive`:: Same as `--max-depth=0`. --w:: ---word-regexp:: +`-w`:: +`--word-regexp`:: Match the pattern only at word boundary (either begin at the beginning of a line, or preceded by a non-word character; end at the end of a line or followed by a non-word character). --v:: ---invert-match:: +`-v`:: +`--invert-match`:: Select non-matching lines. --h:: --H:: +`-h`:: +`-H`:: By default, the command shows the filename for each match. `-h` option is used to suppress this output. `-H` is there for completeness and does not do anything except it overrides `-h` given earlier on the command line. ---full-name:: +`--full-name`:: When run from a subdirectory, the command usually outputs paths relative to the current directory. This option forces paths to be output relative to the project top directory. --E:: ---extended-regexp:: --G:: ---basic-regexp:: +`-E`:: +`--extended-regexp`:: +`-G`:: +`--basic-regexp`:: Use POSIX extended/basic regexp for patterns. Default is to use basic regexp. --P:: ---perl-regexp:: +`-P`:: +`--perl-regexp`:: Use Perl-compatible regular expressions for patterns. + Support for these types of regular expressions is an optional compile-time dependency. If Git wasn't compiled with support for them providing this option will cause it to die. --F:: ---fixed-strings:: +`-F`:: +`--fixed-strings`:: Use fixed strings for patterns (don't interpret pattern as a regex). --n:: ---line-number:: +`-n`:: +`--line-number`:: Prefix the line number to matching lines. ---column:: +`--column`:: Prefix the 1-indexed byte-offset of the first match from the start of the matching line. --l:: ---files-with-matches:: ---name-only:: --L:: ---files-without-match:: +`-l`:: +`--files-with-matches`:: +`--name-only`:: +`-L`:: +`--files-without-match`:: Instead of showing every matched line, show only the names of files that contain (or do not contain) matches. - For better compatibility with 'git diff', `--name-only` is a + For better compatibility with `git diff`, `--name-only` is a synonym for `--files-with-matches`. --O[<pager>]:: ---open-files-in-pager[=<pager>]:: - Open the matching files in the pager (not the output of 'grep'). +`-O[<pager>]`:: +`--open-files-in-pager[=<pager>]`:: + Open the matching files in the pager (not the output of `grep`). If the pager happens to be "less" or "vi", and the user specified only one pattern, the first file is positioned at the first match automatically. The `pager` argument is @@ -181,65 +181,65 @@ without a space. If `pager` is unspecified, the default pager will be used (see `core.pager` in linkgit:git-config[1]). --z:: ---null:: +`-z`:: +`--null`:: Use \0 as the delimiter for pathnames in the output, and print them verbatim. Without this option, pathnames with "unusual" characters are quoted as explained for the configuration variable `core.quotePath` (see linkgit:git-config[1]). --o:: ---only-matching:: +`-o`:: +`--only-matching`:: Print only the matched (non-empty) parts of a matching line, with each such part on a separate output line. --c:: ---count:: +`-c`:: +`--count`:: Instead of showing every matched line, show the number of lines that match. ---color[=<when>]:: +`--color[=<when>]`:: Show colored matches. - The value must be always (the default), never, or auto. + The value must be `always` (the default), `never`, or `auto`. ---no-color:: +`--no-color`:: Turn off match highlighting, even when the configuration file gives the default to color output. Same as `--color=never`. ---break:: +`--break`:: Print an empty line between matches from different files. ---heading:: +`--heading`:: Show the filename above the matches in that file instead of at the start of each shown line. --p:: ---show-function:: +`-p`:: +`--show-function`:: Show the preceding line that contains the function name of the match, unless the matching line is a function name itself. The name is determined in the same way as `git diff` works out patch hunk headers (see 'Defining a custom hunk-header' in linkgit:gitattributes[5]). --<num>:: --C <num>:: ---context <num>:: - Show <num> leading and trailing lines, and place a line +`-<num>`:: +`-C <num>`:: +`--context <num>`:: + Show _<num>_ leading and trailing lines, and place a line containing `--` between contiguous groups of matches. --A <num>:: ---after-context <num>:: - Show <num> trailing lines, and place a line containing +`-A <num>`:: +`--after-context <num>`:: + Show _<num>_ trailing lines, and place a line containing `--` between contiguous groups of matches. --B <num>:: ---before-context <num>:: - Show <num> leading lines, and place a line containing +`-B <num>`:: +`--before-context <num>`:: + Show _<num>_ leading lines, and place a line containing `--` between contiguous groups of matches. --W:: ---function-context:: +`-W`:: +`--function-context`:: Show the surrounding text from the previous line containing a function name up to the one before the next function name, effectively showing the whole function in which the match was @@ -247,22 +247,22 @@ `git diff` works out patch hunk headers (see 'Defining a custom hunk-header' in linkgit:gitattributes[5]). --m <num>:: ---max-count <num>:: +`-m <num>`:: +`--max-count <num>`:: Limit the amount of matches per file. When using the `-v` or `--invert-match` option, the search stops after the specified number of non-matches. A value of -1 will return unlimited results (the default). A value of 0 will exit immediately with a non-zero status. ---threads <num>:: - Number of `grep` worker threads to use. See 'NOTES ON THREADS' +`--threads <num>`:: + Number of `grep` worker threads to use. See `NOTES ON THREADS` and `grep.threads` in 'CONFIGURATION' for more information. --f <file>:: - Read patterns from <file>, one per line. +`-f <file>`:: + Read patterns from _<file>_, one per line. + -Passing the pattern via <file> allows for providing a search pattern +Passing the pattern via _<file>_ allows for providing a search pattern containing a \0. + Not all pattern types support patterns containing \0. Git will error @@ -279,44 +279,44 @@ more search backends, until then we'll die when the pattern type in question doesn't support them. --e:: +`-e`:: The next parameter is the pattern. This option has to be used for patterns starting with `-` and should be used in scripts passing user input to grep. Multiple patterns are - combined by 'or'. + combined by `or`. ---and:: ---or:: ---not:: -( ... ):: +`--and`:: +`--or`:: +`--not`:: +`( ... )`:: Specify how multiple patterns are combined using Boolean expressions. `--or` is the default operator. `--and` has higher precedence than `--or`. `-e` has to be used for all patterns. ---all-match:: +`--all-match`:: When giving multiple pattern expressions combined with `--or`, this flag is specified to limit the match to files that have lines to match all of them. --q:: ---quiet:: +`-q`:: +`--quiet`:: Do not output matched lines; instead, exit with status 0 when there is a match and with non-zero status when there isn't. -<tree>...:: +`<tree>...`:: Instead of searching tracked files in the working tree, search blobs in the given trees. -\--:: +`--`:: Signals the end of options; the rest of the parameters - are <pathspec> limiters. + are _<pathspec>_ limiters. -<pathspec>...:: +`<pathspec>...`:: If given, limit the search to paths matching at least one pattern. - Both leading paths match and glob(7) patterns are supported. + Both leading paths match and `glob`(7) patterns are supported. + -For more details about the <pathspec> syntax, see the 'pathspec' entry +For more details about the _<pathspec>_ syntax, see the `pathspec` entry in linkgit:gitglossary[7]. EXAMPLES
diff --git a/Documentation/git-history.adoc b/Documentation/git-history.adoc index 24dc907..2ba8121 100644 --- a/Documentation/git-history.adoc +++ b/Documentation/git-history.adoc
@@ -8,6 +8,7 @@ SYNOPSIS -------- [synopsis] +git history fixup <commit> [--dry-run] [--update-refs=(branches|head)] [--reedit-message] [--empty=(drop|keep|abort)] git history reword <commit> [--dry-run] [--update-refs=(branches|head)] git history split <commit> [--dry-run] [--update-refs=(branches|head)] [--] [<pathspec>...] @@ -22,8 +23,9 @@ This command is related to linkgit:git-rebase[1] in that both commands can be used to rewrite history. There are a couple of major differences though: -* linkgit:git-history[1] can work in a bare repository as it does not need to - touch either the index or the worktree. +* Most subcommands of linkgit:git-history[1] can work in a bare repository as + they do not need to touch either the index or the worktree. The `fixup` + subcommand is an exception to this, as it reads staged changes from the index. * linkgit:git-history[1] does not execute any linkgit:githooks[5] at the current point in time. This may change in the future. * linkgit:git-history[1] by default updates all branches that are descendants @@ -48,11 +50,28 @@ be stateful operations. The limitation can be lifted once (if) Git learns about first-class conflicts. +When using `fixup` with `--empty=drop`, dropping the root commit is not yet +supported. + COMMANDS -------- The following commands are available to rewrite history in different ways: +`fixup <commit>`:: + Apply the currently staged changes to the specified commit. This is + similar in nature to `git commit --fixup=<commit>` followed by `git + rebase --autosquash <commit>~`. Changes are applied to the target + commit by performing a three-way merge between the HEAD commit, the + target commit and the tree generated from staged changes. ++ +The commit message and authorship of the target commit are preserved by +default, unless you specify `--reedit-message`. ++ +If applying the staged changes would result in a conflict, the command +aborts with an error. All branches that are descendants of the original +commit are updated to point to the rewritten history. + `reword <commit>`:: Rewrite the commit message of the specified commit. All the other details of this commit remain unchanged. This command will spawn an @@ -87,6 +106,31 @@ objects will be written into the repository, so applying these printed ref updates is generally safe. +`--reedit-message`:: + Open an editor to modify the target commit's message. + +`--empty=(drop|keep|abort)`:: + Control what happens when a commit becomes empty as a result of the + fixup. This can happen in two situations: ++ +-- +* The fixup target itself becomes empty because the staged changes exactly + cancel out all changes introduced by that commit. + +* A descendant commit becomes empty during replay because it introduced the + same change that was just fixed up into an ancestor. +-- ++ +With `drop` (the default), empty commits are removed from the rewritten +history. Descendants of a dropped target commit are replayed directly onto +the target's parent. Note that dropping the root commit is not supported; +see LIMITATIONS. ++ +With `keep`, empty commits are retained in the rewritten history as-is. ++ +With `abort`, the command stops with an error if any commit would become +empty. + `--update-refs=(branches|head)`:: Control which references will be updated by the command, if any. With `branches`, all local branches that point to commits which are @@ -96,6 +140,36 @@ EXAMPLES -------- +Fixup a commit +~~~~~~~~~~~~~~ + +---------- +$ git log --oneline --stat +abc1234 (HEAD -> main) third + third.txt | 1 + +def5678 second + second.txt | 1 + +ghi9012 first + first.txt | 1 + + +$ echo "change" >>unrelated.txt +$ git add unrelated.txt +$ git history fixup ghi9012 + +$ git log --oneline --stat +jkl3456 (HEAD -> main) third + third.txt | 1 + +mno7890 second + second.txt | 1 + +pqr1234 first + first.txt | 1 + + unrelated.txt | 1 + +---------- + +The staged addition of `unrelated.txt` has been incorporated into the `first` +commit. All descendant commits have been replayed on top of the rewritten +history. + Split a commit ~~~~~~~~~~~~~~
diff --git a/Documentation/git-hook.adoc b/Documentation/git-hook.adoc index 318c637..4868852 100644 --- a/Documentation/git-hook.adoc +++ b/Documentation/git-hook.adoc
@@ -3,19 +3,20 @@ NAME ---- -git-hook - Run git hooks +git-hook - Run Git hooks SYNOPSIS -------- [verse] -'git hook' run [--allow-unknown-hook-name] [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>] +'git hook' run [--allow-unknown-hook-name] [--ignore-missing] [--to-stdin=<path>] [(-j|--jobs) <n>] + <hook-name> [-- <hook-args>] 'git hook' list [--allow-unknown-hook-name] [-z] [--show-scope] <hook-name> DESCRIPTION ----------- -A command interface for running git hooks (see linkgit:githooks[5]), -for use by other scripted git commands. +A command interface for running Git hooks (see linkgit:githooks[5]), +for use by other scripted Git commands. This command parses the default configuration files for sets of configs like so: @@ -40,7 +41,7 @@ With this config, when you run 'git commit', first `~/bin/linter --cpp20` will have a chance to check your files to be committed (during the `pre-commit` hook -event`), and then `~/bin/spellchecker` will have a chance to check your commit +event), and then `~/bin/spellchecker` will have a chance to check your commit message (during the `commit-msg` hook event). Commands are run in the order Git encounters their associated @@ -147,6 +148,23 @@ mirroring the output style of `git config --show-scope`. Traditional hooks from the hookdir are unaffected. +-j:: +--jobs:: + Only valid for `run`. ++ +Specify how many hooks to run simultaneously. If this flag is not specified, +the value of the `hook.jobs` config is used, see linkgit:git-config[1]. If +neither is specified, defaults to 1 (serial execution). ++ +When greater than 1, it overrides the per-hook `hook.<friendly-name>.parallel` +setting, allowing all hooks for the event to run concurrently, even if they +are not individually marked as parallel. ++ +Some hooks always run sequentially regardless of this flag or the +`hook.jobs` config, because Git knows they cannot safely run in parallel: +`applypatch-msg`, `pre-commit`, `prepare-commit-msg`, `commit-msg`, +`post-commit`, `post-checkout`, and `push-to-checkout`. + WRAPPERS -------- @@ -169,7 +187,8 @@ git hook run --allow-unknown-hook-name mywrapper-start-tests \ # providing something to stdin --stdin some-tempfile-123 \ - # execute hooks in serial + # execute multiple hooks in parallel + --jobs 3 \ # plus some arguments of your own... -- \ --testname bar \ @@ -185,6 +204,7 @@ CONFIGURATION ------------- +:git-hook: 1 include::config/hook.adoc[] SEE ALSO
diff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc index 278e5cc..538b91a 100644 --- a/Documentation/git-imap-send.adoc +++ b/Documentation/git-imap-send.adoc
@@ -8,9 +8,9 @@ SYNOPSIS -------- -[verse] -'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] -'git imap-send' --list +[synopsis] +git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] +git imap-send --list DESCRIPTION @@ -32,30 +32,30 @@ OPTIONS ------- --v:: ---verbose:: +`-v`:: +`--verbose`:: Be verbose. --q:: ---quiet:: +`-q`:: +`--quiet`:: Be quiet. --f <folder>:: ---folder=<folder>:: +`-f <folder>`:: +`--folder=<folder>`:: Specify the folder in which the emails have to saved. For example: `--folder=[Gmail]/Drafts` or `-f INBOX/Drafts`. ---curl:: +`--curl`:: Use libcurl to communicate with the IMAP server, unless tunneling into it. Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND option set. ---no-curl:: +`--no-curl`:: Talk to the IMAP server using git's own IMAP routines instead of using libcurl. Ignored if Git was built with the NO_OPENSSL option set. ---list:: +`--list`:: Run the IMAP LIST command to output a list of all the folders present. CONFIGURATION
diff --git a/Documentation/git-log.adoc b/Documentation/git-log.adoc index e304739..fb3ac11 100644 --- a/Documentation/git-log.adoc +++ b/Documentation/git-log.adoc
@@ -36,14 +36,14 @@ Print out the ref names of any commits that are shown. Possible values are: + ----- +-- `short`;; the ref name prefixes `refs/heads/`, `refs/tags/` and `refs/remotes/` are not printed. `full`;; the full ref name (including prefix) is printed. -`auto`:: if the output is going to a terminal, the ref names +`auto`;; if the output is going to a terminal, the ref names are shown as if `short` were given, otherwise no ref names are shown. ----- +-- + The option `--decorate` is short-hand for `--decorate=short`. Default to configuration value of `log.decorate` if configured, otherwise, `auto`.
diff --git a/Documentation/git-ls-files.adoc b/Documentation/git-ls-files.adoc index 58c529a..2b17538 100644 --- a/Documentation/git-ls-files.adoc +++ b/Documentation/git-ls-files.adoc
@@ -331,7 +331,7 @@ 1. The file specified by the `core.excludesfile` configuration variable, if exists, or the `$XDG_CONFIG_HOME/git/ignore` file. - 2. The `$GIT_DIR/info/exclude` file. + 2. The `$GIT_COMMON_DIR/info/exclude` file. via the `--exclude-from=` option.
diff --git a/Documentation/git-multi-pack-index.adoc b/Documentation/git-multi-pack-index.adoc index 3a5aa22..c6d23ae 100644 --- a/Documentation/git-multi-pack-index.adoc +++ b/Documentation/git-multi-pack-index.adoc
@@ -11,9 +11,11 @@ [verse] 'git multi-pack-index' [<options>] write [--preferred-pack=<pack>] [--[no-]bitmap] [--[no-]incremental] [--[no-]stdin-packs] - [--refs-snapshot=<path>] + [--refs-snapshot=<path>] [--[no-]write-chain-file] + [--base=<checksum>] 'git multi-pack-index' [<options>] compact [--[no-]incremental] - [--[no-]bitmap] <from> <to> + [--[no-]bitmap] [--base=<checksum>] [--[no-]write-chain-file] + <from> <to> 'git multi-pack-index' [<options>] verify 'git multi-pack-index' [<options>] expire 'git multi-pack-index' [<options>] repack [--batch-size=<size>] @@ -83,6 +85,20 @@ and packs not present in an existing MIDX layer. Migrates non-incremental MIDXs to incremental ones when necessary. + + --[no-]write-chain-file:: + When used with `--incremental`, write a new MIDX layer + but do not update the multi-pack-index-chain file. + The checksum of the new layer is printed to standard + output, allowing the caller to assemble and write the + chain itself. Requires `--incremental`. + + --base=<checksum>:: + Specify the checksum of an existing MIDX layer to use + as the base when writing a new incremental layer. + The special value `none` indicates that the new layer + should have no base (i.e., it becomes a root layer). + Requires `--no-write-chain-file`. -- compact:: @@ -97,6 +113,18 @@ --[no-]bitmap:: Control whether or not a multi-pack bitmap is written. + + --[no-]write-chain-file:: + When used with `--incremental`, write a new compacted + MIDX layer but do not update the multi-pack-index-chain + file. The checksum of the new layer is printed to + standard output. Requires `--incremental`. + + --base=<checksum>:: + Specify the checksum of an existing MIDX layer to use + as the base for the compacted result, instead of using + the immediate parent of `<from>`. The special value + `none` indicates that the result should have no base. -- + Note that the compact command requires writing a version-2 midx that
diff --git a/Documentation/git-pack-objects.adoc b/Documentation/git-pack-objects.adoc index b78175f..8a27aa1 100644 --- a/Documentation/git-pack-objects.adoc +++ b/Documentation/git-pack-objects.adoc
@@ -402,9 +402,11 @@ of filenames that cause collisions in Git's default name-hash algorithm. + -Incompatible with `--delta-islands`, `--shallow`, or `--filter`. The -`--use-bitmap-index` option will be ignored in the presence of -`--path-walk.` +Incompatible with `--delta-islands`. The `--use-bitmap-index` option is +ignored in the presence of `--path-walk`. The `--path-walk` option +supports the `--filter=<spec>` forms `blob:none`, `blob:limit=<n>`, +`tree:0`, `object:type=<type>`, and `sparse:<oid>`. These supported filter +types can be combined with the `combine:<spec>+<spec>` form. DELTA ISLANDS
diff --git a/Documentation/git-push.adoc b/Documentation/git-push.adoc index e5ba3a6..aa221c3 100644 --- a/Documentation/git-push.adoc +++ b/Documentation/git-push.adoc
@@ -18,17 +18,28 @@ DESCRIPTION ----------- - -Updates one or more branches, tags, or other references in a remote -repository from your local repository, and sends all necessary data -that isn't already on the remote. +Updates one or more branches, tags, or other references in one or more +remote repositories from your local repository, and sends all necessary +data that isn't already on the remote. The simplest way to push is `git push <remote> <branch>`. `git push origin main` will push the local `main` branch to the `main` branch on the remote named `origin`. -The `<repository>` argument defaults to the upstream for the current branch, -or `origin` if there's no configured upstream. +You can also push to multiple remotes at once by using a remote group. +A remote group is a named list of remotes configured via `remotes.<name>` +in your git config: + + $ git config remotes.all-remotes "origin gitlab backup" + +Then `git push all-remotes` will push to `origin`, `gitlab`, and +`backup` in turn, as if you had run `git push` against each one +individually. Each remote is pushed independently using its own +push mapping configuration. There is a `remotes.<group>` entry in +the configuration file. (See linkgit:git-config[1]). + +The `<repository>` argument defaults to the upstream for the current +branch, or `origin` if there's no configured upstream. To decide which branches, tags, or other refs to push, Git uses (in order of precedence): @@ -55,8 +66,10 @@ _<repository>_:: The "remote" repository that is the destination of a push operation. This parameter can be either a URL - (see the section <<URLS,GIT URLS>> below) or the name - of a remote (see the section <<REMOTES,REMOTES>> below). + (see the section <<URLS,GIT URLS>> below), the name + of a remote (see the section <<REMOTES,REMOTES>> below), + or the name of a remote group + (see the section <<REMOTE-GROUPS,REMOTE GROUPS>> below). `<refspec>...`:: Specify what destination ref to update with what source object. @@ -430,6 +443,57 @@ include::urls-remotes.adoc[] +[[REMOTE-GROUPS]] +REMOTE GROUPS +------------- + +A remote group is a named list of remotes configured via `remotes.<name>` +in your git config: + + $ git config remotes.all-remotes "r1 r2 r3" + +When a group name is given as the `<repository>` argument, the push is +performed to each member remote in turn. The defining principle is: + + git push <options> all-remotes <args> + +is exactly equivalent to: + + git push <options> r1 <args> + git push <options> r2 <args> + ... + git push <options> rN <args> + +where r1, r2, ..., rN are the members of `all-remotes`. No special +behaviour is added or removed — the group is purely a shorthand for +running the same push command against each member remote individually. + +When pushing to a group of more than one remote, Git spawns a separate +`git push` subprocess for each member remote in sequence. Each subprocess +receives the same flags and refspecs as the original invocation. This +means that per-remote push mappings configured via `remote.<name>.push` +and mirror mode (`remote.<name>.mirror`) are evaluated independently for +each remote, and a mirror remote in the group cannot affect the push +behaviour of other non-mirror remotes in the same group. + +The `--atomic` option is not supported for group pushes, because atomicity +can only be guaranteed within a single transport connection to a single +remote. Git will refuse the invocation with an error if `--atomic` is +combined with a group name. + +If any member remote fails whether due to a push rejection (e.g. a +non-fast-forward update, a server-side hook refusing a ref) or a connection +error (e.g. the repository does not exist, authentication fails, or the +network is unreachable), Git reports the error and continues pushing to +the remaining remotes in the group. The overall exit code is non-zero if +any member push fails. + +This means the user is responsible for ensuring that the sequence of +individual pushes makes sense. If `git push r1`` would fail for a given +set of options and arguments, then `git push all-remotes` will fail in +the same way when it reaches r1. The group push does not do anything +special to make a failing individual push succeed. + OUTPUT ------
diff --git a/Documentation/git-range-diff.adoc b/Documentation/git-range-diff.adoc index b5e85d3..5cc5e2e 100644 --- a/Documentation/git-range-diff.adoc +++ b/Documentation/git-range-diff.adoc
@@ -7,11 +7,11 @@ SYNOPSIS -------- -[verse] -'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>] +[synopsis] +git range-diff [--color=[<when>]] [--no-color] [<diff-options>] [--no-dual-color] [--creation-factor=<factor>] [--left-only | --right-only] [--diff-merges=<format>] - [--remerge-diff] + [--remerge-diff] [--no-notes | --notes[=<ref>]] ( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> ) [[--] <path>...] @@ -21,14 +21,14 @@ This command shows the differences between two versions of a patch series, or more generally, two commit ranges (ignoring merge commits). -In the presence of `<path>` arguments, these commit ranges are limited +In the presence of _<path>_ arguments, these commit ranges are limited accordingly. To that end, it first finds pairs of commits from both commit ranges that correspond with each other. Two commits are said to correspond when the diff between their patches (i.e. the author information, the commit message and the commit diff) is reasonably small compared to the -patches' size. See ``Algorithm`` below for details. +patches' size. See 'Algorithm' below for details. Finally, the list of matching commits is shown in the order of the second commit range, with unmatched commits being inserted just after @@ -37,7 +37,7 @@ There are three ways to specify the commit ranges: - `<range1> <range2>`: Either commit range can be of the form - `<base>..<rev>`, `<rev>^!` or `<rev>^-<n>`. See `SPECIFYING RANGES` + `<base>..<rev>`, `<rev>^!` or `<rev>^-<n>`. See 'SPECIFYING RANGES' in linkgit:gitrevisions[7] for more details. - `<rev1>...<rev2>`. This is equivalent to @@ -48,7 +48,7 @@ OPTIONS ------- ---no-dual-color:: +`--no-dual-color`:: When the commit diffs differ, `git range-diff` recreates the original diffs' coloring, and adds outer -/+ diff markers with the *background* being red/green to make it easier to see e.g. @@ -56,33 +56,33 @@ + Additionally, the commit diff lines that are only present in the first commit range are shown "dimmed" (this can be overridden using the `color.diff.<slot>` -config setting where `<slot>` is one of `contextDimmed`, `oldDimmed` and +config setting where _<slot>_ is one of `contextDimmed`, `oldDimmed` and `newDimmed`), and the commit diff lines that are only present in the second commit range are shown in bold (which can be overridden using the config -settings `color.diff.<slot>` with `<slot>` being one of `contextBold`, +settings `color.diff.<slot>` with _<slot>_ being one of `contextBold`, `oldBold` or `newBold`). + This is known to `range-diff` as "dual coloring". Use `--no-dual-color` to revert to color all lines according to the outer diff markers (and completely ignore the inner diff when it comes to color). ---creation-factor=<percent>:: - Set the creation/deletion cost fudge factor to `<percent>`. +`--creation-factor=<percent>`:: + Set the creation/deletion cost fudge factor to _<percent>_. Defaults to 60. Try a larger value if `git range-diff` erroneously considers a large change a total rewrite (deletion of one commit and addition of another), and a smaller one in the reverse case. - See the ``Algorithm`` section below for an explanation of why this is + See the 'Algorithm' section below for an explanation of why this is needed. ---left-only:: +`--left-only`:: Suppress commits that are missing from the first specified range - (or the "left range" when using the `<rev1>...<rev2>` format). + (or the "left range" when using the `<rev1>...<rev2>` form). ---right-only:: +`--right-only`:: Suppress commits that are missing from the second specified range - (or the "right range" when using the `<rev1>...<rev2>` format). + (or the "right range" when using the `<rev1>...<rev2>` form). ---diff-merges=<format>:: +`--diff-merges=<format>`:: Instead of ignoring merge commits, generate diffs for them using the corresponding `--diff-merges=<format>` option of linkgit:git-log[1], and include them in the comparison. @@ -93,30 +93,30 @@ non-conflicting `git merge`, the `remerge` mode will represent it with an empty diff. ---remerge-diff:: +`--remerge-diff`:: Convenience option, equivalent to `--diff-merges=remerge`. ---notes[=<ref>]:: ---no-notes:: +`--notes[=<ref>]`:: +`--no-notes`:: This flag is passed to the `git log` program (see linkgit:git-log[1]) that generates the patches. -<range1> <range2>:: +`<range1> <range2>`:: Compare the commits specified by the two ranges, where - `<range1>` is considered an older version of `<range2>`. + _<range1>_ is considered an older version of _<range2>_. -<rev1>...<rev2>:: +`<rev1>...<rev2>`:: Equivalent to passing `<rev2>..<rev1>` and `<rev1>..<rev2>`. -<base> <rev1> <rev2>:: +`<base> <rev1> <rev2>`:: Equivalent to passing `<base>..<rev1>` and `<base>..<rev2>`. - Note that `<base>` does not need to be the exact branch point + Note that _<base>_ does not need to be the exact branch point of the branches. Example: after rebasing a branch `my-topic`, `git range-diff my-topic@{u} my-topic@{1} my-topic` would show the differences introduced by the rebase. `git range-diff` also accepts the regular diff options (see -linkgit:git-diff[1]), most notably the `--color=[<when>]` and +linkgit:git-diff[1]), most notably the `--color[=<when>]` and `--no-color` options. These options are used when generating the "diff between patches", i.e. to compare the author, commit message and diff of corresponding old/new commits. There is currently no means to tweak most of the
diff --git a/Documentation/git-repack.adoc b/Documentation/git-repack.adoc index 673ce91..72c4201 100644 --- a/Documentation/git-repack.adoc +++ b/Documentation/git-repack.adoc
@@ -11,7 +11,7 @@ [verse] 'git repack' [-a] [-A] [-d] [-f] [-F] [-l] [-n] [-q] [-b] [-m] [--window=<n>] [--depth=<n>] [--threads=<n>] [--keep-pack=<pack-name>] - [--write-midx] [--name-hash-version=<n>] [--path-walk] + [--write-midx[=<mode>]] [--name-hash-version=<n>] [--path-walk] DESCRIPTION ----------- @@ -250,9 +250,47 @@ linkgit:git-multi-pack-index[1]). -m:: ---write-midx:: +--write-midx[=<mode>]:: Write a multi-pack index (see linkgit:git-multi-pack-index[1]) - containing the non-redundant packs. + containing the non-redundant packs. The following modes are + available: ++ +-- + `default`;; + Write a single MIDX covering all packs. This is the + default when `--write-midx` is given without an + explicit mode. + + `incremental`;; + Write an incremental MIDX chain instead of a single + flat MIDX. ++ +Without `--geometric`, a new MIDX layer is appended to the existing +chain (or a new chain is started) containing whatever packs were written +by the repack. Existing layers are preserved as-is. ++ +When combined with `--geometric`, the incremental mode maintains a chain +of MIDX layers that is compacted over time using a geometric merging +strategy. Each repack creates a new tip layer containing the newly +written pack(s). Adjacent layers are then merged whenever the newer +layer's object count exceeds `1/repack.midxSplitFactor` of the next +deeper layer's count. Layers that do not meet this condition are +retained as-is. ++ +The result is that newer (tip) layers tend to contain many small packs +with relatively few objects, while older (deeper) layers contain fewer, +larger packs covering more objects. Because compaction is driven by the +tip of the chain, newer layers are also rewritten more frequently than +older ones, which are only touched when enough objects have accumulated +to justify merging into them. This keeps the total number of layers +logarithmic relative to the total number of objects. ++ +Only packs in the tip MIDX layer are considered as candidates for the +geometric repack; packs in deeper layers are left untouched. If the tip +layer contains fewer packs than `repack.midxNewLayerThreshold`, those +packs are excluded from the geometry entirely, and a new layer is +created for any new pack(s) without disturbing the existing chain. +-- --name-hash-version=<n>:: Provide this argument to the underlying `git pack-objects` process.
diff --git a/Documentation/git-replace.adoc b/Documentation/git-replace.adoc index 0a65460..436a0e5 100644 --- a/Documentation/git-replace.adoc +++ b/Documentation/git-replace.adoc
@@ -145,6 +145,13 @@ There may be other problems when using 'git rev-list' related to pending objects. +CAVEATS +------- + +The existence of replace objects or commit grafts turns off reading or +writing to the commit-graph, which can cause performance issues. See +linkgit:git-commit-graph[1]. + SEE ALSO -------- linkgit:git-hash-object[1]
diff --git a/Documentation/git-restore.adoc b/Documentation/git-restore.adoc index 961eef0..7d19423 100644 --- a/Documentation/git-restore.adoc +++ b/Documentation/git-restore.adoc
@@ -41,7 +41,7 @@ + As a special case, you may use `"<rev-A>...<rev-B>"` as a shortcut for the merge base of _<rev-A>_ and _<rev-B>_ if there is exactly one merge base. You can -leave out at most one of _<rev-A>__ and _<rev-B>_, in which case it defaults to `HEAD`. +leave out at most one of _<rev-A>_ and _<rev-B>_, in which case it defaults to `HEAD`. `-p`:: `--patch`::
diff --git a/Documentation/git-shortlog.adoc b/Documentation/git-shortlog.adoc index a11b57c..e067d39 100644 --- a/Documentation/git-shortlog.adoc +++ b/Documentation/git-shortlog.adoc
@@ -3,63 +3,63 @@ NAME ---- -git-shortlog - Summarize 'git log' output +git-shortlog - Summarize `git log` output SYNOPSIS -------- -[verse] -'git shortlog' [<options>] [<revision-range>] [[--] <path>...] -git log --pretty=short | 'git shortlog' [<options>] +[synopsis] +git shortlog [<options>] [<revision-range>] [[--] <path>...] +git log --pretty=short | git shortlog [<options>] DESCRIPTION ----------- -Summarizes 'git log' output in a format suitable for inclusion +Summarizes `git log` output in a format suitable for inclusion in release announcements. Each commit will be grouped by author and title. Additionally, "[PATCH]" will be stripped from the commit description. If no revisions are passed on the command line and either standard input -is not a terminal or there is no current branch, 'git shortlog' will +is not a terminal or there is no current branch, `git shortlog` will output a summary of the log read from standard input, without reference to the current repository. OPTIONS ------- --n:: ---numbered:: +`-n`:: +`--numbered`:: Sort output according to the number of commits per author instead of author alphabetic order. --s:: ---summary:: +`-s`:: +`--summary`:: Suppress commit description and provide a commit count summary only. --e:: ---email:: +`-e`:: +`--email`:: Show the email address of each author. ---format[=<format>]:: +`--format[=<format>]`:: Instead of the commit subject, use some other information to - describe each commit. '<format>' can be any string accepted - by the `--format` option of 'git log', such as '* [%h] %s'. - (See the "PRETTY FORMATS" section of linkgit:git-log[1].) + describe each commit. _<format>_ can be any string accepted + by the `--format` option of `git log`, such as '* [%h] %s'. + (See the 'PRETTY FORMATS' section of linkgit:git-log[1].) + Each pretty-printed commit will be rewrapped before it is shown. ---date=<format>:: +`--date=<format>`:: Show dates formatted according to the given date string. (See - the `--date` option in the "Commit Formatting" section of + the `--date` option in the 'Commit Formatting' section of linkgit:git-log[1]). Useful with `--group=format:<format>`. ---group=<type>:: - Group commits based on `<type>`. If no `--group` option is - specified, the default is `author`. `<type>` is one of: +`--group=<type>`:: + Group commits based on _<type>_. If no `--group` option is + specified, the default is `author`. _<type>_ is one of: + -- - `author`, commits are grouped by author - `committer`, commits are grouped by committer (the same as `-c`) - - `trailer:<field>`, the `<field>` is interpreted as a case-insensitive + - `trailer:<field>`, the _<field>_ is interpreted as a case-insensitive commit message trailer (see linkgit:git-interpret-trailers[1]). For example, if your project uses `Reviewed-by` trailers, you might want to see who has been reviewing with @@ -76,7 +76,7 @@ as an identity, it will be taken literally and completely. - `format:<format>`, any string accepted by the `--format` option of - 'git log'. (See the "PRETTY FORMATS" section of + `git log`. (See the 'PRETTY FORMATS' section of linkgit:git-log[1].) -- + @@ -85,11 +85,11 @@ example, `git shortlog --group=author --group=trailer:co-authored-by` counts both authors and co-authors. --c:: ---committer:: +`-c`:: +`--committer`:: This is an alias for `--group=committer`. --w[<width>[,<indent1>[,<indent2>]]]:: +`-w[<width>[,<indent1>[,<indent2>]]]`:: Linewrap the output by wrapping each line at `width`. The first line of each entry is indented by `indent1` spaces, and the second and subsequent lines are indented by `indent2` spaces. `width`, @@ -98,16 +98,16 @@ If width is `0` (zero) then indent the lines of the output without wrapping them. -<revision-range>:: +`<revision-range>`:: Show only commits in the specified revision range. When no - <revision-range> is specified, it defaults to `HEAD` (i.e. the + _<revision-range>_ is specified, it defaults to `HEAD` (i.e. the whole history leading to the current commit). `origin..HEAD` specifies all the commits reachable from the current commit (i.e. `HEAD`), but not from `origin`. For a complete list of - ways to spell <revision-range>, see the "Specifying Ranges" + ways to spell _<revision-range>_, see the 'Specifying Ranges' section of linkgit:gitrevisions[7]. -[--] <path>...:: +`[--] <path>...`:: Consider only commits that are enough to explain how the files that match the specified paths came to be. +
diff --git a/Documentation/git-sparse-checkout.adoc b/Documentation/git-sparse-checkout.adoc index 0d1618f..e286584 100644 --- a/Documentation/git-sparse-checkout.adoc +++ b/Documentation/git-sparse-checkout.adoc
@@ -134,7 +134,7 @@ + The `--dry-run` option will list the directories that would be removed without deleting them. Running in this mode can be helpful to predict the -behavior of the clean comand or to determine which kinds of files are left +behavior of the clean command or to determine which kinds of files are left in the sparse directories. + The `--verbose` option will list every file within the directories that
diff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc index b05c990..50bb89f 100644 --- a/Documentation/git-stash.adoc +++ b/Documentation/git-stash.adoc
@@ -12,7 +12,7 @@ git stash show [-u | --include-untracked | --only-untracked] [<diff-options>] [<stash>] git stash drop [-q | --quiet] [<stash>] git stash pop [--index] [-q | --quiet] [<stash>] -git stash apply [--index] [-q | --quiet] [<stash>] +git stash apply [--index] [-q | --quiet] [--label-ours=<label>] [--label-theirs=<label>] [--label-base=<label>] [<stash>] git stash branch <branchname> [<stash>] git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet] [-u | --include-untracked] [-a | --all] [(-m | --message) <message>] @@ -195,6 +195,15 @@ (which are stored in the index, where you therefore can no longer apply the changes as they were originally). +`--label-ours=<label>`:: +`--label-theirs=<label>`:: +`--label-base=<label>`:: + These options are only valid for the `apply` command. ++ +Use the given labels in conflict markers instead of the default +"Updated upstream", "Stashed changes", and "Stash base". +`--label-base` only has an effect with merge.conflictStyle=diff3. + `-k`:: `--keep-index`:: `--no-keep-index`::
diff --git a/Documentation/git-svn.adoc b/Documentation/git-svn.adoc index c26c12b..2a7fa60 100644 --- a/Documentation/git-svn.adoc +++ b/Documentation/git-svn.adoc
@@ -439,7 +439,7 @@ 'show-ignore':: Recursively finds and lists the svn:ignore and svn:global-ignores properties on directories. The output is suitable for appending to - the $GIT_DIR/info/exclude file. + the $GIT_COMMON_DIR/info/exclude file. 'mkdirs':: Attempts to recreate empty directories that core Git cannot track
diff --git a/Documentation/git-switch.adoc b/Documentation/git-switch.adoc index 87707e9..d6c4f22 100644 --- a/Documentation/git-switch.adoc +++ b/Documentation/git-switch.adoc
@@ -123,18 +123,19 @@ `-m`:: `--merge`:: - If you have local modifications to one or more files that are - different between the current branch and the branch to which - you are switching, the command refuses to switch branches in - order to preserve your modifications in context. However, - with this option, a three-way merge between the current - branch, your working tree contents, and the new branch is - done, and you will be on the new branch. -+ -When a merge conflict happens, the index entries for conflicting -paths are left unmerged, and you need to resolve the conflicts -and mark the resolved paths with `git add` (or `git rm` if the merge -should result in deletion of the path). + If you have local modifications to one or more files that + are different between the current branch and the branch to + which you are switching, the command normally refuses to + switch branches in order to preserve your modifications in + context. However, with this option, the conflicting local + changes are automatically stashed before the switch and + reapplied afterwards. If the local changes do not overlap + with the differences between branches, the switch proceeds + without stashing. If reapplying the stash results in + conflicts, the entry is saved to the stash list. Resolve + the conflicts and run `git stash drop` when done, or clear + the working tree (e.g. with `git reset --hard`) before + running `git stash pop` later to re-apply your changes. `--conflict=<style>`:: The same as `--merge` option above, but changes the way the @@ -217,15 +218,18 @@ error: You have local changes to 'frotz'; not switching branches. ------------ -You can give the `-m` flag to the command, which would try a three-way -merge: +You can give the `-m` flag to the command, which will carry your local +changes to the new branch: ------------ $ git switch -m mytopic -Auto-merging frotz +Applied autostash. +Switched to branch 'mytopic' +The following paths have local changes: +M frotz ------------ -After this three-way merge, the local modifications are _not_ +After the switch, the local modifications are reapplied and are _not_ registered in your index file, so `git diff` would show you what changes you made since the tip of the new branch.
diff --git a/Documentation/git-url-parse.adoc b/Documentation/git-url-parse.adoc new file mode 100644 index 0000000..9d0d93d --- /dev/null +++ b/Documentation/git-url-parse.adoc
@@ -0,0 +1,80 @@ +git-url-parse(1) +================ + +NAME +---- +git-url-parse - Parse and extract git URL components + +SYNOPSIS +-------- +[synopsis] +git url-parse [-c <component>] [--] <url>... + +DESCRIPTION +----------- + +Git supports many ways to specify URLs, some of them non-standard. +For example, git supports the scp style [user@]host:[path] format. +This command eases interoperability with git URLs by enabling the +parsing and extraction of the components of all git URLs. + +Any syntactically valid URL is parsed, even if the scheme is not one +git supports for fetching or pushing. + +OPTIONS +------- + +`-c <component>`:: +`--component <component>`:: + Extract the _<component>_ component from the given Git URLs. + _<component>_ can be one of: + `scheme`, `user`, `password`, `host`, `port`, `path`. + +OUTPUT +------ + +When `--component` is given, the requested component of each URL +is printed on its own line, in the order the URLs were given. If +the URL has no such component (for example, a port in a URL that +does not specify one), an empty line is printed in its place. + +When `--component` is not given, no output is produced. The exit +status is zero if every URL parses successfully and non-zero +otherwise, allowing the command to be used purely as a validator. + +EXAMPLES +-------- + +* Print the host name: ++ +------------ +$ git url-parse --component host https://example.com/user/repo +example.com +------------ + +* Print the path: ++ +------------ +$ git url-parse --component path https://example.com/user/repo +/user/repo +$ git url-parse --component path example.com:~user/repo +~user/repo +$ git url-parse --component path example.com:user/repo +/user/repo +------------ + +* Validate URLs without outputting anything: ++ +------------ +$ git url-parse https://example.com/user/repo example.com:~user/repo +------------ + +SEE ALSO +-------- +linkgit:git-clone[1], +linkgit:git-fetch[1], +linkgit:git-config[1] + +GIT +--- +Part of the linkgit:git[1] suite
diff --git a/Documentation/gitattributes.adoc b/Documentation/gitattributes.adoc index f20041a..bd76167 100644 --- a/Documentation/gitattributes.adoc +++ b/Documentation/gitattributes.adoc
@@ -911,7 +911,8 @@ - `rust` suitable for source code in the Rust language. -- `scheme` suitable for source code in the Scheme language. +- `scheme` suitable for source code in most Lisp dialects, + including Scheme, Emacs Lisp, Common Lisp, and Clojure. - `tex` suitable for source code for LaTeX documents.
diff --git a/Documentation/gitformat-index.adoc b/Documentation/gitformat-index.adoc index 145cace..f6a427c 100644 --- a/Documentation/gitformat-index.adoc +++ b/Documentation/gitformat-index.adoc
@@ -291,14 +291,14 @@ sequence in variable width encoding. Each string describes the environment where the cache can be used. - - Stat data of $GIT_DIR/info/exclude. See "Index entry" section from + - Stat data of $GIT_COMMON_DIR/info/exclude. See "Index entry" section from ctime field until "file size". - Stat data of core.excludesFile - 32-bit dir_flags (see struct dir_struct) - - Hash of $GIT_DIR/info/exclude. A null hash means the file + - Hash of $GIT_COMMON_DIR/info/exclude. A null hash means the file does not exist. - Hash of core.excludesFile. A null hash means the file does
diff --git a/Documentation/gitignore.adoc b/Documentation/gitignore.adoc index a3d24e5..7979e50 100644 --- a/Documentation/gitignore.adoc +++ b/Documentation/gitignore.adoc
@@ -7,7 +7,7 @@ SYNOPSIS -------- -$XDG_CONFIG_HOME/git/ignore, $GIT_DIR/info/exclude, .gitignore +$XDG_CONFIG_HOME/git/ignore, $GIT_COMMON_DIR/info/exclude, .gitignore DESCRIPTION ----------- @@ -34,7 +34,7 @@ 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`. + * Patterns read from `$GIT_COMMON_DIR/info/exclude`. * Patterns read from the file specified by the configuration variable `core.excludesFile`. @@ -50,7 +50,7 @@ specific to a particular repository but which do not need to be shared with other related repositories (e.g., auxiliary files that live inside the repository but are specific to one user's workflow) should go into - the `$GIT_DIR/info/exclude` file. + the `$GIT_COMMON_DIR/info/exclude` file. * Patterns which a user wants Git to ignore in all situations (e.g., backup or temporary files generated by @@ -97,7 +97,7 @@ match at any level below the `.gitignore` level. - Patterns read from exclude sources that are outside the working tree, - such as $GIT_DIR/info/exclude and core.excludesFile, are treated as if + such as $GIT_COMMON_DIR/info/exclude and core.excludesFile, are treated as if they are specified at the root of the working tree, i.e. a leading "/" in such patterns anchors the match at the root of the repository. @@ -146,8 +146,8 @@ The optional configuration variable `core.excludesFile` indicates a path to a file containing patterns of file names to exclude, similar to -`$GIT_DIR/info/exclude`. Patterns in the exclude file are used in addition to -those in `$GIT_DIR/info/exclude`. +`$GIT_COMMON_DIR/info/exclude`. Patterns in the exclude file are used in +addition to those in `$GIT_COMMON_DIR/info/exclude`. NOTES -----
diff --git a/Documentation/gitpacking.adoc b/Documentation/gitpacking.adoc index a56596e..e6de6ec 100644 --- a/Documentation/gitpacking.adoc +++ b/Documentation/gitpacking.adoc
@@ -150,7 +150,7 @@ pattern = "refs/" threshold = now stableThreshold = never - sampleRate = 100 + sampleRate = 1 maxMerges = 64 ---- @@ -177,7 +177,7 @@ pattern = "refs/virtual/([0-9]+)/(heads|tags)/" threshold = now stableThreshold = never - sampleRate = 100 + sampleRate = 1 maxMerges = 64 ----
diff --git a/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc index ac5102b..befa697 100644 --- a/Documentation/gitprotocol-v2.adoc +++ b/Documentation/gitprotocol-v2.adoc
@@ -848,6 +848,10 @@ where `pr-name` is the urlencoded name of a promisor remote the server advertised and the client accepts. +The promisor remotes that the client accepted will be tried before the +other configured promisor remotes when the client attempts to fetch +missing objects. + Note that, everywhere in this document, the ';' and ',' characters MUST be encoded if they appear in `pr-name` or `field-value`.
diff --git a/Documentation/line-range-options.adoc b/Documentation/line-range-options.adoc index ecb2c79..72f639b 100644 --- a/Documentation/line-range-options.adoc +++ b/Documentation/line-range-options.adoc
@@ -8,12 +8,14 @@ give zero or one positive revision arguments, and _<start>_ and _<end>_ (or _<funcname>_) must exist in the starting revision. You can specify this option more than once. Implies `--patch`. - Patch output can be suppressed using `--no-patch`, but other diff formats - (namely `--raw`, `--numstat`, `--shortstat`, `--dirstat`, `--summary`, - `--name-only`, `--name-status`, `--check`) are not currently implemented. + Patch output can be suppressed using `--no-patch`. + Non-patch diff formats `--raw`, `--name-only`, `--name-status`, + and `--summary` are supported. Diff stat formats + (`--stat`, `--numstat`, `--shortstat`, `--dirstat`) are not + currently implemented. + Patch formatting options such as `--word-diff`, `--color-moved`, `--no-prefix`, and whitespace options (`-w`, `-b`) are supported, -as are pickaxe options (`-S`, `-G`). +as are pickaxe options (`-S`, `-G`) and `--diff-filter`. + include::line-range-format.adoc[]
diff --git a/Documentation/meson.build b/Documentation/meson.build index d6365b8..f4854f8 100644 --- a/Documentation/meson.build +++ b/Documentation/meson.build
@@ -55,6 +55,7 @@ 'git-for-each-ref.adoc' : 1, 'git-for-each-repo.adoc' : 1, 'git-format-patch.adoc' : 1, + 'git-format-rev.adoc' : 1, 'git-fsck-objects.adoc' : 1, 'git-fsck.adoc' : 1, 'git-fsmonitor--daemon.adoc' : 1, @@ -155,6 +156,7 @@ 'git-update-server-info.adoc' : 1, 'git-upload-archive.adoc' : 1, 'git-upload-pack.adoc' : 1, + 'git-url-parse.adoc' : 1, 'git-var.adoc' : 1, 'git-verify-commit.adoc' : 1, 'git-verify-pack.adoc' : 1,
diff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc index 2d195a1..eaee6ee 100644 --- a/Documentation/rev-list-options.adoc +++ b/Documentation/rev-list-options.adoc
@@ -16,14 +16,18 @@ `-<number>`:: `-n <number>`:: `--max-count=<number>`:: - Limit the output to _<number>_ commits. + Limit the output to the first _<number>_ commits that would be shown. + +`--max-count-oldest=<number>`:: + Limit the output to the last _<number>_ commits that would be shown. `--skip=<number>`:: Skip _<number>_ commits before starting to show the commit output. `--since=<date>`:: `--after=<date>`:: - Show commits more recent than _<date>_. + Show commits more recent than _<date>_. As a special case, + 'today' means the last midnight. `--since-as-filter=<date>`:: Show all commits more recent than _<date>_. This visits @@ -1259,6 +1263,12 @@ in between them in that case. If _<barrier>_ is specified, it is the string that will be shown instead of the default one. +`--graph-lane-limit=<n>`:: + When `--graph` is used, limit the number of graph lanes to be shown. + Lanes over the limit are replaced with a truncation mark '~'. + By default it is set to 0 (no limit), zero and negative values + are ignored and treated as no limit. + ifdef::git-rev-list[] `--count`:: Print a number stating how many commits would have been
diff --git a/Documentation/technical/api-path-walk.adoc b/Documentation/technical/api-path-walk.adoc index a67de1b..6e17b13 100644 --- a/Documentation/technical/api-path-walk.adoc +++ b/Documentation/technical/api-path-walk.adoc
@@ -48,6 +48,13 @@ applications could disable some options to make it simpler to walk the objects or to have fewer calls to `path_fn`. + +Note that objects directly requested as pending objects (such as targets +of lightweight tags or other ref tips) are always emitted to `path_fn`, +even when the corresponding type flag is disabled. Only objects +discovered during the tree walk are subject to these type filters. This +ensures that objects specifically requested through the revision input +are never silently dropped. ++ While it is possible to walk only commits in this way, consumers would be better off using the revision walk API instead.
diff --git a/Documentation/technical/build-systems.adoc b/Documentation/technical/build-systems.adoc index 3c5237b..ca5b5d9 100644 --- a/Documentation/technical/build-systems.adoc +++ b/Documentation/technical/build-systems.adoc
@@ -47,7 +47,7 @@ - Check for the existence of headers. - Check for the existence of libraries. - - Check for the existence of exectuables. + - Check for the existence of executables. - Check for the runtime behavior of specific functions. - Check for specific link order requirements when multiple libraries are involved. @@ -106,7 +106,7 @@ - C: the primary compiled language used by Git, must be supported. Relevant toolchains are GCC, Clang and MSVC. - - Rust: candidate as a second compiled lanugage, should be supported. Relevant + - Rust: candidate as a second compiled language, should be supported. Relevant toolchains is the LLVM-based rustc. Built-in support for the respective languages is preferred over support that @@ -142,7 +142,7 @@ === GNU Make -- Platform support: ubitquitous on all platforms, but not well-integrated into Windows. +- Platform support: ubiquitous on all platforms, but not well-integrated into Windows. - Auto-detection: no built-in support for auto-detection of features. - Ease of use: easy to use, but discovering available options is hard. Makefile rules can quickly get out of hand once reaching a certain scope.
diff --git a/Documentation/technical/hash-function-transition.adoc b/Documentation/technical/hash-function-transition.adoc index 2359d7d..241d2f7 100644 --- a/Documentation/technical/hash-function-transition.adoc +++ b/Documentation/technical/hash-function-transition.adoc
@@ -545,7 +545,7 @@ ~~~~~~~~~~ For the same reason, a SHA-256 repository cannot borrow objects from a SHA-1 repository using objects/info/alternates or -$GIT_ALTERNATE_OBJECT_REPOSITORIES. +$GIT_ALTERNATE_OBJECT_DIRECTORIES. git notes ~~~~~~~~~
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index fd6979b..9bcfdb5 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN
@@ -1,6 +1,6 @@ #!/bin/sh -DEF_VER=v2.54.0 +DEF_VER=v2.55.0-rc1 LF=' '
diff --git a/Makefile b/Makefile index 85405cb..1cec251 100644 --- a/Makefile +++ b/Makefile
@@ -283,13 +283,9 @@ # Define SKIP_DASHED_BUILT_INS if you do not need the dashed versions of the # built-ins to be linked/copied at all. # -# Define USE_NED_ALLOCATOR if you want to replace the platforms default -# memory allocators with the nedmalloc allocator written by Niall Douglas. -# # Define OVERRIDE_STRDUP to override the libc version of strdup(3). # This is necessary when using a custom allocator in order to avoid # crashes due to allocation and free working on different 'heaps'. -# It's defined automatically if USE_NED_ALLOCATOR is set. # # Define NO_REGEX if your C library lacks regex support with REG_STARTEND # feature. @@ -420,7 +416,7 @@ # If your platform has OS-specific ways to tell if a repo is incompatible with # fsmonitor (whether the hook or IPC daemon version), set FSMONITOR_OS_SETTINGS # to the "<name>" of the corresponding `compat/fsmonitor/fsm-settings-<name>.c` -# that implements the `fsm_os_settings__*()` routines. +# and `compat/fsmonitor/fsm-ipc-<name>.c` files. # # Define LINK_FUZZ_PROGRAMS if you want `make all` to also build the fuzz test # programs in oss-fuzz/. @@ -498,9 +494,9 @@ # # == Optional Rust support == # -# Define WITH_RUST if you want to include features and subsystems written in -# Rust into Git. For now, Rust is still an optional feature of the build -# process. With Git 3.0 though, Rust will always be enabled. +# Define NO_RUST if you want to disable features and subsystems written in Rust +# from being compiled into Git. For now, Rust is still an optional feature of +# the build process. With Git 3.0 though, Rust will always be enabled. # # Building Rust code requires Cargo. # @@ -896,6 +892,7 @@ BUILT_INS += git-cherry$X BUILT_INS += git-cherry-pick$X BUILT_INS += git-format-patch$X +BUILT_INS += git-format-rev$X BUILT_INS += git-fsck-objects$X BUILT_INS += git-init$X BUILT_INS += git-maintenance$X @@ -1219,7 +1216,10 @@ LIB_OBJS += odb.o LIB_OBJS += odb/source.o LIB_OBJS += odb/source-files.o +LIB_OBJS += odb/source-inmemory.o +LIB_OBJS += odb/source-loose.o LIB_OBJS += odb/streaming.o +LIB_OBJS += odb/transaction.o LIB_OBJS += oid-array.o LIB_OBJS += oidmap.o LIB_OBJS += oidset.o @@ -1352,7 +1352,7 @@ LIB_OBJS += usage.o LIB_OBJS += userdiff.o LIB_OBJS += utf8.o -ifndef WITH_RUST +ifdef NO_RUST LIB_OBJS += varint.o endif LIB_OBJS += version.o @@ -1498,6 +1498,7 @@ BUILTIN_OBJS += builtin/update-server-info.o BUILTIN_OBJS += builtin/upload-archive.o BUILTIN_OBJS += builtin/upload-pack.o +BUILTIN_OBJS += builtin/url-parse.o BUILTIN_OBJS += builtin/var.o BUILTIN_OBJS += builtin/verify-commit.o BUILTIN_OBJS += builtin/verify-pack.o @@ -1512,7 +1513,6 @@ # upstream unnecessarily (making merging in future changes easier). THIRD_PARTY_SOURCES += compat/inet_ntop.c THIRD_PARTY_SOURCES += compat/inet_pton.c -THIRD_PARTY_SOURCES += compat/nedmalloc/% THIRD_PARTY_SOURCES += compat/obstack.% THIRD_PARTY_SOURCES += compat/poll/% THIRD_PARTY_SOURCES += compat/regex/% @@ -1528,6 +1528,7 @@ CLAR_TEST_SUITES += u-hashmap CLAR_TEST_SUITES += u-list-objects-filter-options CLAR_TEST_SUITES += u-mem-pool +CLAR_TEST_SUITES += u-odb-inmemory CLAR_TEST_SUITES += u-oid-array CLAR_TEST_SUITES += u-oidmap CLAR_TEST_SUITES += u-oidtree @@ -1591,7 +1592,7 @@ ALL_CFLAGS = $(DEVELOPER_CFLAGS) $(CPPFLAGS) $(CFLAGS) $(CFLAGS_APPEND) ALL_LDFLAGS = $(LDFLAGS) $(LDFLAGS_APPEND) -ifdef WITH_RUST +ifndef NO_RUST BASIC_CFLAGS += -DWITH_RUST GITLIBS += $(RUST_LIB) ifeq ($(uname_S),Windows) @@ -2268,12 +2269,6 @@ BASIC_CFLAGS += -DNATIVE_CRLF endif -ifdef USE_NED_ALLOCATOR - COMPAT_CFLAGS += -Icompat/nedmalloc - COMPAT_OBJS += compat/nedmalloc/nedmalloc.o - OVERRIDE_STRDUP = YesPlease -endif - ifdef OVERRIDE_STRDUP COMPAT_CFLAGS += -DOVERRIDE_STRDUP COMPAT_OBJS += compat/strdup.o @@ -2380,13 +2375,13 @@ COMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND COMPAT_OBJS += compat/fsmonitor/fsm-listen-$(FSMONITOR_DAEMON_BACKEND).o COMPAT_OBJS += compat/fsmonitor/fsm-health-$(FSMONITOR_DAEMON_BACKEND).o - COMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_DAEMON_BACKEND).o endif ifdef FSMONITOR_OS_SETTINGS COMPAT_CFLAGS += -DHAVE_FSMONITOR_OS_SETTINGS + COMPAT_OBJS += compat/fsmonitor/fsm-ipc-$(FSMONITOR_OS_SETTINGS).o COMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_OS_SETTINGS).o - COMPAT_OBJS += compat/fsmonitor/fsm-path-utils-$(FSMONITOR_OS_SETTINGS).o + COMPAT_OBJS += compat/fsmonitor/fsm-path-utils-$(FSMONITOR_DAEMON_BACKEND).o endif ifdef WITH_BREAKING_CHANGES @@ -2672,7 +2667,7 @@ help.sp help.s help.o: command-list.h builtin/bugreport.sp builtin/bugreport.s builtin/bugreport.o: hook-list.h -builtin/hook.sp builtin/hook.s builtin/hook.o: hook-list.h +hook.sp hook.s hook.o: hook-list.h builtin/help.sp builtin/help.s builtin/help.o: config-list.h GIT-PREFIX builtin/help.sp builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \ @@ -2984,12 +2979,6 @@ -DGAWK -DNO_MBSUPPORT endif -ifdef USE_NED_ALLOCATOR -compat/nedmalloc/nedmalloc.sp compat/nedmalloc/nedmalloc.o: EXTRA_CPPFLAGS = \ - -DNDEBUG -DREPLACE_SYSTEM_ALLOCATOR -compat/nedmalloc/nedmalloc.sp: SP_EXTRA_FLAGS += -Wno-non-pointer-null -endif - headless-git.o: compat/win32/headless.c GIT-CFLAGS $(QUIET_CC)$(CC) $(ALL_CFLAGS) $(COMPAT_CFLAGS) \ -fno-stack-protector -o $@ -c -Wall -Wwrite-strings $< @@ -4085,7 +4074,7 @@ contrib/credential/osxkeychain/git-credential-osxkeychain: contrib/credential/osxkeychain/git-credential-osxkeychain.o $(LIB_FILE) GIT-LDFLAGS $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \ - $(filter %.o,$^) $(LIB_FILE) $(EXTLIBS) -framework Security -framework CoreFoundation + $(filter %.o,$^) $(LIBS) -framework Security -framework CoreFoundation contrib/credential/osxkeychain/git-credential-osxkeychain.o: contrib/credential/osxkeychain/git-credential-osxkeychain.c GIT-CFLAGS $(QUIET_LINK)$(CC) -o $@ -c $(dep_args) $(compdb_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $<
diff --git a/RelNotes b/RelNotes index 84ef738..159e44a 120000 --- a/RelNotes +++ b/RelNotes
@@ -1 +1 @@ -Documentation/RelNotes/2.54.0.adoc \ No newline at end of file +Documentation/RelNotes/2.55.0.adoc \ No newline at end of file
diff --git a/alias.c b/alias.c index ec9833d..e737c49 100644 --- a/alias.c +++ b/alias.c
@@ -34,8 +34,20 @@ static int config_alias_cb(const char *var, const char *value, if (subsection && !subsection_len) subsection = NULL; - if (subsection && strcmp(key, "command")) - return 0; + if (subsection && strcmp(key, "command")) { + /* + * We have historically supported the "alias.name" form when + * "name" happens to contain dots (e.g., alias.foo.bar to allow + * "git foo.bar". But our parsing above would split that into + * subsection "foo". + * + * If we do not understand the final key in a subsection-style + * variable, fall back to treating it as a two-level alias. + */ + key = var + strlen("alias."); + subsection = NULL; + subsection_len = 0; + } if (data->alias) { int match;
diff --git a/apply.c b/apply.c index 4aa1694..249248d 100644 --- a/apply.c +++ b/apply.c
@@ -4881,8 +4881,10 @@ static int apply_patch(struct apply_state *state, state->patch_input_file = filename; state->linenr = 1; - if (read_patch_file(&buf, fd) < 0) - return -128; + if (read_patch_file(&buf, fd) < 0) { + res = -128; + goto end; + } offset = 0; while (offset < buf.len) { struct patch *patch;
diff --git a/archive.c b/archive.c index fcd474c..5122910 100644 --- a/archive.c +++ b/archive.c
@@ -786,7 +786,7 @@ int write_archive(int argc, const char **argv, const char *prefix, * die ourselves; but its error message will be more specific * than what we could write here. */ - setup_git_directory(); + setup_git_directory(the_repository); } parse_treeish_arg(argv, &args, remote);
diff --git a/bisect.c b/bisect.c index ef17a44..e29d1cb 100644 --- a/bisect.c +++ b/bisect.c
@@ -512,7 +512,7 @@ static char *join_oid_array_hex(struct oid_array *array, char delim) int i; for (i = 0; i < array->nr; i++) { - strbuf_addstr(&joined_hexs, oid_to_hex(array->oid + i)); + strbuf_add_oid_hex(&joined_hexs, array->oid + i); if (i + 1 < array->nr) strbuf_addch(&joined_hexs, delim); } @@ -711,7 +711,7 @@ static enum bisect_error error_if_skipped_commits(struct commit_list *tried, return BISECT_OK; printf("There are only 'skip'ped commits left to test.\n" - "The first %s commit could be any of:\n", term_bad); + "The first '%s' commit could be any of:\n", term_bad); for ( ; tried; tried = tried->next) printf("%s\n", oid_to_hex(&tried->item->object.oid)); @@ -810,7 +810,7 @@ static enum bisect_error handle_bad_merge_base(void) "between %s and [%s].\n"), bad_hex, bad_hex, good_hex); } else { - fprintf(stderr, _("The merge base %s is %s.\n" + fprintf(stderr, _("The merge base %s is '%s'.\n" "This means the first '%s' commit is " "between %s and [%s].\n"), bad_hex, term_bad, term_good, bad_hex, good_hex); @@ -820,9 +820,9 @@ static enum bisect_error handle_bad_merge_base(void) return BISECT_MERGE_BASE_CHECK; } - fprintf(stderr, _("Some %s revs are not ancestors of the %s rev.\n" + fprintf(stderr, _("Some '%s' revs are not ancestors of the '%s' rev.\n" "git bisect cannot work properly in this case.\n" - "Maybe you mistook %s and %s revs?\n"), + "Maybe you mistook '%s' and '%s' revs?\n"), term_good, term_bad, term_good, term_bad); return BISECT_FAILED; } @@ -835,7 +835,7 @@ static void handle_skipped_merge_base(const struct object_id *mb) warning(_("the merge base between %s and [%s] " "must be skipped.\n" - "So we cannot be sure the first %s commit is " + "So we cannot be sure the first '%s' commit is " "between %s and %s.\n" "We continue anyway."), bad_hex, good_hex, term_bad, mb_hex, bad_hex); @@ -928,7 +928,7 @@ static enum bisect_error check_good_are_ancestors_of_bad(struct repository *r, struct commit **rev; if (!current_bad_oid) - return error(_("a %s revision is needed"), term_bad); + return error(_("a '%s' revision is needed"), term_bad); filename = repo_git_path(the_repository, "BISECT_ANCESTORS_OK"); @@ -1090,7 +1090,7 @@ enum bisect_error bisect_next_all(struct repository *r, const char *prefix) res = error_if_skipped_commits(tried, NULL); if (res < 0) goto cleanup; - printf(_("%s was both %s and %s\n"), + printf(_("%s was both '%s' and '%s'\n"), oid_to_hex(current_bad_oid), term_good, term_bad); @@ -1113,7 +1113,7 @@ enum bisect_error bisect_next_all(struct repository *r, const char *prefix) res = error_if_skipped_commits(tried, current_bad_oid); if (res) goto cleanup; - printf("%s is the first %s commit\n", oid_to_hex(bisect_rev), + printf("%s is the first '%s' commit\n", oid_to_hex(bisect_rev), term_bad); show_commit(revs.commits->item);
diff --git a/blame.c b/blame.c index a3c49d1..977cbb7 100644 --- a/blame.c +++ b/blame.c
@@ -2813,7 +2813,7 @@ void setup_scoreboard(struct blame_scoreboard *sb, } if (!sb->contents_from) - setup_work_tree(); + setup_work_tree(the_repository); sb->final = fake_working_tree_commit(sb->repo, &sb->revs->diffopt,
diff --git a/builtin.h b/builtin.h index 235c51f..4e47a4e 100644 --- a/builtin.h +++ b/builtin.h
@@ -189,6 +189,7 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix, struct re int cmd_for_each_ref(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_for_each_repo(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_format_patch(int argc, const char **argv, const char *prefix, struct repository *repo); +int cmd_format_rev(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_fsck(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_gc(int argc, const char **argv, const char *prefix, struct repository *repo); @@ -271,6 +272,7 @@ int cmd_update_server_info(int argc, const char **argv, const char *prefix, stru int cmd_upload_archive(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_upload_pack(int argc, const char **argv, const char *prefix, struct repository *repo); +int cmd_url_parse(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_var(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_verify_commit(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_verify_tag(int argc, const char **argv, const char *prefix, struct repository *repo);
diff --git a/builtin/add.c b/builtin/add.c index 7737ab8..c859f66 100644 --- a/builtin/add.c +++ b/builtin/add.c
@@ -16,6 +16,7 @@ #include "run-command.h" #include "object-file.h" #include "odb.h" +#include "odb/transaction.h" #include "parse-options.h" #include "path.h" #include "preload-index.h"
diff --git a/builtin/am.c b/builtin/am.c index fe6e087..e9623b8 100644 --- a/builtin/am.c +++ b/builtin/am.c
@@ -490,9 +490,11 @@ static int run_applypatch_msg_hook(struct am_state *state) assert(state->msg); - if (!state->no_verify) - ret = run_hooks_l(the_repository, "applypatch-msg", - am_path(state, "final-commit"), NULL); + if (!state->no_verify) { + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_FORCE_SERIAL; + strvec_push(&opt.args, am_path(state, "final-commit")); + ret = run_hooks_opt(the_repository, "applypatch-msg", &opt); + } if (!ret) { FREE_AND_NULL(state->msg);
diff --git a/builtin/archive.c b/builtin/archive.c index 13ea730..3c1288a 100644 --- a/builtin/archive.c +++ b/builtin/archive.c
@@ -31,7 +31,7 @@ static int run_remote_archiver(int argc, const char **argv, _remote = remote_get(remote); transport = transport_get(_remote, _remote->url.v[0]); - transport_connect(transport, "git-upload-archive", exec, fd); + transport_connect(transport, GIT_CONNECT_UPLOAD_ARCHIVE, exec, fd); /* * Inject a fake --format field at the beginning of the
diff --git a/builtin/backfill.c b/builtin/backfill.c index d794dd8..e71e0f4 100644 --- a/builtin/backfill.c +++ b/builtin/backfill.c
@@ -26,7 +26,7 @@ #include "path-walk.h" static const char * const builtin_backfill_usage[] = { - N_("git backfill [--min-batch-size=<n>] [--[no-]sparse]"), + N_("git backfill [--min-batch-size=<n>] [--[no-]sparse] [--[no-]include-edges] [<revision-range>]"), NULL }; @@ -35,6 +35,7 @@ struct backfill_context { struct oid_array current_batch; size_t min_batch_size; int sparse; + int include_edges; struct rev_info revs; }; @@ -78,6 +79,29 @@ static int fill_missing_blobs(const char *path UNUSED, return 0; } +static void reject_unsupported_rev_list_options(struct rev_info *revs) +{ + if (revs->diffopt.pickaxe) + die(_("'%s' cannot be used with 'git backfill'"), + (revs->diffopt.pickaxe_opts & DIFF_PICKAXE_REGEX) ? "-G" : "-S"); + if (revs->diffopt.filter || revs->diffopt.filter_not) + die(_("'%s' cannot be used with 'git backfill'"), + "--diff-filter"); + if (revs->diffopt.flags.follow_renames) + die(_("'%s' cannot be used with 'git backfill'"), + "--follow"); + if (revs->line_level_traverse) + die(_("'%s' cannot be used with 'git backfill'"), + "-L"); + if (revs->explicit_diff_merges) + die(_("'%s' cannot be used with 'git backfill'"), + "--diff-merges"); + if (!path_walk_filter_compatible(&revs->filter)) + die(_("cannot backfill with these filter options")); + if (revs->filter.blob_limit_value) + die(_("cannot backfill with blob size limits")); +} + static int do_backfill(struct backfill_context *ctx) { struct path_walk_info info = PATH_WALK_INFO_INIT; @@ -85,6 +109,7 @@ static int do_backfill(struct backfill_context *ctx) if (ctx->sparse) { CALLOC_ARRAY(info.pl, 1); + info.pl_sparse_trees = 1; if (get_sparse_checkout_patterns(info.pl)) { path_walk_info_clear(&info); return error(_("problem loading sparse-checkout")); @@ -94,6 +119,8 @@ static int do_backfill(struct backfill_context *ctx) /* Walk from HEAD if otherwise unspecified. */ if (!ctx->revs.pending.nr) add_head_to_pending(&ctx->revs); + if (ctx->include_edges) + ctx->revs.edge_hint = 1; info.blobs = 1; info.tags = info.commits = info.trees = 0; @@ -121,12 +148,15 @@ int cmd_backfill(int argc, const char **argv, const char *prefix, struct reposit .min_batch_size = 50000, .sparse = -1, .revs = REV_INFO_INIT, + .include_edges = 1, }; struct option options[] = { OPT_UNSIGNED(0, "min-batch-size", &ctx.min_batch_size, N_("Minimum number of objects to request at a time")), OPT_BOOL(0, "sparse", &ctx.sparse, N_("Restrict the missing objects to the current sparse-checkout")), + OPT_BOOL(0, "include-edges", &ctx.include_edges, + N_("Include blobs from boundary commits in the backfill")), OPT_END(), }; struct repo_config_values *cfg = repo_config_values(the_repository); @@ -144,6 +174,7 @@ int cmd_backfill(int argc, const char **argv, const char *prefix, struct reposit if (argc > 1) die(_("unrecognized argument: %s"), argv[1]); + reject_unsupported_rev_list_options(&ctx.revs); repo_config(repo, git_default_config, NULL);
diff --git a/builtin/bisect.c b/builtin/bisect.c index 4520e58..e7c2d2f 100644 --- a/builtin/bisect.c +++ b/builtin/bisect.c
@@ -465,13 +465,16 @@ static void bisect_print_status(const struct bisect_terms *terms) return; if (!state.nr_good && !state.nr_bad) - bisect_log_printf(_("status: waiting for both good and bad commits\n")); + bisect_log_printf(_("status: waiting for both '%s' and '%s' commits\n"), + terms->term_good, terms->term_bad); else if (state.nr_good) - bisect_log_printf(Q_("status: waiting for bad commit, %d good commit known\n", - "status: waiting for bad commit, %d good commits known\n", - state.nr_good), state.nr_good); + bisect_log_printf(Q_("status: waiting for '%s' commit, %d '%s' commit known\n", + "status: waiting for '%s' commit, %d '%s' commits known\n", + state.nr_good), + terms->term_bad, state.nr_good, terms->term_good); else - bisect_log_printf(_("status: waiting for good commit(s), bad commit known\n")); + bisect_log_printf(_("status: waiting for '%s' commit(s), '%s' commit known\n"), + terms->term_good, terms->term_bad); } static int bisect_next_check(const struct bisect_terms *terms, @@ -513,8 +516,8 @@ static int bisect_terms(struct bisect_terms *terms, const char *option) return error(_("no terms defined")); if (!option) { - printf(_("Your current terms are %s for the old state\n" - "and %s for the new state.\n"), + printf(_("Your current terms are '%s' for the old state\n" + "and '%s' for the new state.\n"), terms->term_good, terms->term_bad); return 0; } @@ -632,7 +635,7 @@ static int bisect_skipped_commits(struct bisect_terms *terms) strbuf_reset(&commit_name); repo_format_commit_message(the_repository, commit, "%s", &commit_name, &pp); - fprintf(fp, "# possible first %s commit: [%s] %s\n", + fprintf(fp, "# possible first '%s' commit: [%s] %s\n", terms->term_bad, oid_to_hex(&commit->object.oid), commit_name.buf); } @@ -663,7 +666,7 @@ static int bisect_successful(struct bisect_terms *terms) repo_format_commit_message(the_repository, commit, "%s", &commit_name, &pp); - res = append_to_file(git_path_bisect_log(), "# first %s commit: [%s] %s\n", + res = append_to_file(git_path_bisect_log(), "# first '%s' commit: [%s] %s\n", terms->term_bad, oid_to_hex(&commit->object.oid), commit_name.buf); @@ -833,7 +836,7 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc, if (!repo_get_oid(the_repository, head, &head_oid) && !starts_with(head, "refs/heads/")) { strbuf_reset(&start_head); - strbuf_addstr(&start_head, oid_to_hex(&head_oid)); + strbuf_add_oid_hex(&start_head, &head_oid); } else if (!repo_get_oid(the_repository, head, &head_oid) && skip_prefix(head, "refs/heads/", &head)) { strbuf_addstr(&start_head, head); @@ -1262,14 +1265,14 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv) int rc = verify_good(terms, command.buf); is_first_run = 0; if (rc < 0 || 128 <= rc) { - error(_("unable to verify %s on good" - " revision"), command.buf); + error(_("unable to verify %s on '%s' revision"), + command.buf, terms->term_good); res = BISECT_FAILED; break; } if (rc == res) { - error(_("bogus exit code %d for good revision"), - rc); + error(_("bogus exit code %d for '%s' revision"), + rc, terms->term_good); res = BISECT_FAILED; break; } @@ -1314,7 +1317,7 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv) puts(_("bisect run success")); res = BISECT_OK; } else if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) { - puts(_("bisect found first bad commit")); + printf(_("bisect found first '%s' commit\n"), terms->term_bad); res = BISECT_OK; } else if (res) { error(_("bisect run failed: 'git bisect %s'"
diff --git a/builtin/blame.c b/builtin/blame.c index f3a11ef..ffbd3ce 100644 --- a/builtin/blame.c +++ b/builtin/blame.c
@@ -708,7 +708,7 @@ static unsigned parse_score(const char *arg) static char *add_prefix(const char *prefix, const char *path) { - return prefix_path(prefix, prefix ? strlen(prefix) : 0, path); + return prefix_path(the_repository, prefix, prefix ? strlen(prefix) : 0, path); } static int git_blame_config(const char *var, const char *value,
diff --git a/builtin/cat-file.c b/builtin/cat-file.c index d9fbad5..446d649 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c
@@ -57,6 +57,20 @@ static int use_mailmap; static char *replace_idents_using_mailmap(char *, size_t *); +/* + * The mailmap is initialized with .strdup_strings set to 0, + * but read_mailmap() sets the bit to 1 (this is true even when + * not a single mailmap entry is read), so it can be used for + * lazy loading. + */ +static void load_mailmap(void) +{ + if (mailmap.strdup_strings) + return; + + read_mailmap(the_repository, &mailmap); +} + static char *replace_idents_using_mailmap(char *object_buf, size_t *size) { struct strbuf sb = STRBUF_INIT; @@ -320,7 +334,7 @@ static int expand_atom(struct strbuf *sb, const char *atom, int len, { if (is_atom("objectname", atom, len)) { if (!data->mark_query) - strbuf_addstr(sb, oid_to_hex(&data->oid)); + strbuf_add_oid_hex(sb, &data->oid); } else if (is_atom("objecttype", atom, len)) { if (data->mark_query) data->info.typep = &data->type; @@ -330,12 +344,12 @@ static int expand_atom(struct strbuf *sb, const char *atom, int len, if (data->mark_query) data->info.sizep = &data->size; else - strbuf_addf(sb, "%"PRIuMAX , (uintmax_t)data->size); + strbuf_add_uint(sb, data->size); } else if (is_atom("objectsize:disk", atom, len)) { if (data->mark_query) data->info.disk_sizep = &data->disk_size; else - strbuf_addf(sb, "%"PRIuMAX, (uintmax_t)data->disk_size); + strbuf_add_uint(sb, data->disk_size); } else if (is_atom("rest", atom, len)) { if (data->mark_query) data->split_on_whitespace = 1; @@ -345,8 +359,7 @@ static int expand_atom(struct strbuf *sb, const char *atom, int len, if (data->mark_query) data->info.delta_base_oid = &data->delta_base_oid; else - strbuf_addstr(sb, - oid_to_hex(&data->delta_base_oid)); + strbuf_add_oid_hex(sb, &data->delta_base_oid); } else if (is_atom("objectmode", atom, len)) { if (!data->mark_query && !(S_IFINVALID == data->mode)) strbuf_addf(sb, "%06o", data->mode); @@ -692,6 +705,20 @@ static void parse_cmd_info(struct batch_options *opt, batch_one_object(line, output, opt, data); } +static void parse_cmd_mailmap(struct batch_options *opt UNUSED, + const char *line, + struct strbuf *output UNUSED, + struct expand_data *data UNUSED) +{ + use_mailmap = git_parse_maybe_bool(line); + + if (use_mailmap < 0) + die(_("mailmap: invalid boolean '%s'"), line); + + if (use_mailmap) + load_mailmap(); +} + static void dispatch_calls(struct batch_options *opt, struct strbuf *output, struct expand_data *data, @@ -725,9 +752,10 @@ static const struct parse_cmd { parse_cmd_fn_t fn; unsigned takes_args; } commands[] = { - { "contents", parse_cmd_contents, 1}, - { "info", parse_cmd_info, 1}, - { "flush", NULL, 0}, + { "contents", parse_cmd_contents, 1 }, + { "info", parse_cmd_info, 1 }, + { "flush", NULL, 0 }, + { "mailmap", parse_cmd_mailmap, 1 }, }; static void batch_objects_command(struct batch_options *opt, @@ -862,8 +890,9 @@ static void batch_each_object(struct batch_options *opt, */ odb_prepare_alternates(the_repository->objects); for (source = the_repository->objects->sources; source; source = source->next) { - int ret = odb_source_loose_for_each_object(source, NULL, batch_one_object_oi, - &payload, &opts); + struct odb_source_files *files = odb_source_files_downcast(source); + int ret = odb_source_for_each_object(&files->loose->base, NULL, batch_one_object_oi, + &payload, &opts); if (ret) break; } @@ -901,6 +930,7 @@ static int batch_objects(struct batch_options *opt) struct strbuf input = STRBUF_INIT; struct strbuf output = STRBUF_INIT; struct expand_data data = EXPAND_DATA_INIT; + struct repo_config_values *cfg = repo_config_values(the_repository); int save_warning; int retval = 0; @@ -973,8 +1003,8 @@ static int batch_objects(struct batch_options *opt) * warn) ends up dwarfing the actual cost of the object lookups * themselves. We can work around it by just turning off the warning. */ - save_warning = warn_on_object_refname_ambiguity; - warn_on_object_refname_ambiguity = 0; + save_warning = cfg->warn_on_object_refname_ambiguity; + cfg->warn_on_object_refname_ambiguity = 0; if (opt->batch_mode == BATCH_MODE_QUEUE_AND_DISPATCH) { batch_objects_command(opt, &output, &data); @@ -1002,7 +1032,7 @@ static int batch_objects(struct batch_options *opt) cleanup: strbuf_release(&input); strbuf_release(&output); - warn_on_object_refname_ambiguity = save_warning; + cfg->warn_on_object_refname_ambiguity = save_warning; return retval; } @@ -1131,7 +1161,7 @@ int cmd_cat_file(int argc, opt_epts = (opt == 'e' || opt == 'p' || opt == 't' || opt == 's'); if (use_mailmap) - read_mailmap(the_repository, &mailmap); + load_mailmap(); switch (batch.objects_filter.choice) { case LOFC_DISABLED:
diff --git a/builtin/check-attr.c b/builtin/check-attr.c index 51ed48c..98f64d5 100644 --- a/builtin/check-attr.c +++ b/builtin/check-attr.c
@@ -67,7 +67,7 @@ static void check_attr(const char *prefix, struct attr_check *check, { char *full_path = - prefix_path(prefix, prefix ? strlen(prefix) : 0, file); + prefix_path(the_repository, prefix, prefix ? strlen(prefix) : 0, file); if (collect_all) { git_all_attrs(the_repository->index, full_path, check); @@ -117,7 +117,7 @@ int cmd_check_attr(int argc, int cnt, i, doubledash, filei; if (!is_bare_repository()) - setup_work_tree(); + setup_work_tree(the_repository); repo_config(the_repository, git_default_config, NULL);
diff --git a/builtin/check-ref-format.c b/builtin/check-ref-format.c index 5d80afe..e42b044 100644 --- a/builtin/check-ref-format.c +++ b/builtin/check-ref-format.c
@@ -1,6 +1,9 @@ /* * GIT - The information manager from hell */ + +#define USE_THE_REPOSITORY_VARIABLE + #include "builtin.h" #include "refs.h" #include "setup.h" @@ -41,7 +44,7 @@ static int check_ref_format_branch(const char *arg) const char *name; int nongit; - setup_git_directory_gently(&nongit); + setup_git_directory_gently(the_repository, &nongit); if (check_branch_ref(&sb, arg) || !skip_prefix(sb.buf, "refs/heads/", &name)) die("'%s' is not a valid branch name", arg);
diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index 188128a..311b94f 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c
@@ -303,7 +303,7 @@ int cmd_checkout_index(int argc, die("git checkout-index: don't mix '--all' and explicit filenames"); if (read_from_stdin) die("git checkout-index: don't mix '--stdin' and explicit filenames"); - p = prefix_path(prefix, prefix_length, arg); + p = prefix_path(repo, prefix, prefix_length, arg); err |= checkout_file(repo->index, p, prefix); free(p); } @@ -325,7 +325,7 @@ int cmd_checkout_index(int argc, die("line is badly quoted"); strbuf_swap(&buf, &unquoted); } - p = prefix_path(prefix, prefix_length, buf.buf); + p = prefix_path(repo, prefix, prefix_length, buf.buf); err |= checkout_file(repo->index, p, prefix); free(p); }
diff --git a/builtin/checkout.c b/builtin/checkout.c index e031e61..b78b3a1 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c
@@ -17,7 +17,6 @@ #include "merge-ll.h" #include "lockfile.h" #include "mem-pool.h" -#include "merge-ort-wrappers.h" #include "object-file.h" #include "object-name.h" #include "odb.h" @@ -30,7 +29,10 @@ #include "repo-settings.h" #include "resolve-undo.h" #include "revision.h" +#include "sequencer.h" #include "setup.h" +#include "sparse-index.h" +#include "strvec.h" #include "submodule.h" #include "symlinks.h" #include "trace2.h" @@ -99,6 +101,8 @@ struct checkout_opts { .auto_advance = 1, \ } +#define MERGE_WORKING_TREE_UNPACK_FAILED (-2) + struct branch_info { char *name; /* The short name used */ char *path; /* The full name of a real branch */ @@ -123,24 +127,80 @@ static void branch_info_release(struct branch_info *info) static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit, int changed) { - return run_hooks_l(the_repository, "post-checkout", - oid_to_hex(old_commit ? &old_commit->object.oid : null_oid(the_hash_algo)), - oid_to_hex(new_commit ? &new_commit->object.oid : null_oid(the_hash_algo)), - changed ? "1" : "0", NULL); - /* "new_commit" can be NULL when checking out from the index before - a commit exists. */ + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_FORCE_SERIAL; + /* + * "new_commit" can be NULL when checking out from the index before + * a commit exists. + */ + strvec_pushl(&opt.args, + oid_to_hex(old_commit ? &old_commit->object.oid : null_oid(the_hash_algo)), + oid_to_hex(new_commit ? &new_commit->object.oid : null_oid(the_hash_algo)), + changed ? "1" : "0", + NULL); + + return run_hooks_opt(the_repository, "post-checkout", &opt); +} + +/* + * Handle a tree object and determine if we need to recurse into the + * tree (READ_TREE_RECURSIVE) or skip it (0). + */ +static int try_update_sparse_directory(const struct object_id *oid, + struct strbuf *base, + const char *pathname, + int overlay_mode) +{ + struct strbuf dirpath = STRBUF_INIT; + struct cache_entry *old; + int pos, result = READ_TREE_RECURSIVE; + + if (!the_repository->index->sparse_index) + return result; + + strbuf_addbuf(&dirpath, base); + strbuf_addstr(&dirpath, pathname); + strbuf_addch(&dirpath, '/'); + + pos = index_name_pos_sparse(the_repository->index, + dirpath.buf, dirpath.len); + if (pos < 0) + goto cleanup; + + old = the_repository->index->cache[pos]; + if (!S_ISSPARSEDIR(old->ce_mode)) + goto cleanup; + + if (oideq(oid, &old->oid)) { + /* Tree content already matches; no need to descend. */ + result = 0; + } else if (!overlay_mode) { + /* + * In non-overlay mode (e.g., restore --staged), replace the + * sparse directory OID directly since files not present in + * the source tree should be removed anyway. + */ + oidcpy(&old->oid, oid); + old->ce_flags |= CE_UPDATE; + result = 0; + } + +cleanup: + strbuf_release(&dirpath); + return result; } static int update_some(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, void *context UNUSED) + const char *pathname, unsigned mode, void *context) { int len; struct cache_entry *ce; int pos; + int overlay_mode = context ? *((int *)context) : 1; if (S_ISDIR(mode)) - return READ_TREE_RECURSIVE; + return try_update_sparse_directory(oid, base, pathname, + overlay_mode); len = base->len + strlen(pathname); ce = make_empty_cache_entry(the_repository->index, len); @@ -156,7 +216,7 @@ static int update_some(const struct object_id *oid, struct strbuf *base, * entry in place. Whether it is UPTODATE or not, checkout_entry will * do the right thing. */ - pos = index_name_pos(the_repository->index, ce->name, ce->ce_namelen); + pos = index_name_pos_sparse(the_repository->index, ce->name, ce->ce_namelen); if (pos >= 0) { struct cache_entry *old = the_repository->index->cache[pos]; if (ce->ce_mode == old->ce_mode && @@ -173,10 +233,11 @@ static int update_some(const struct object_id *oid, struct strbuf *base, return 0; } -static int read_tree_some(struct tree *tree, const struct pathspec *pathspec) +static int read_tree_some(struct tree *tree, const struct pathspec *pathspec, + int overlay_mode) { read_tree(the_repository, tree, - pathspec, update_some, NULL); + pathspec, update_some, &overlay_mode); /* update the index with the given tree's info * for all args, expanding wildcards, and exit @@ -571,7 +632,8 @@ static int checkout_paths(const struct checkout_opts *opts, return error(_("index file corrupt")); if (opts->source_tree) - read_tree_some(opts->source_tree, &opts->pathspec); + read_tree_some(opts->source_tree, &opts->pathspec, + opts->overlay_mode); if (opts->merge) unmerge_index(the_repository->index, &opts->pathspec, CE_MATCHED); @@ -753,9 +815,9 @@ static void setup_branch_path(struct branch_info *branch) branch->path = strbuf_detach(&buf, NULL); } -static void init_topts(struct unpack_trees_options *topts, int merge, +static void init_topts(struct unpack_trees_options *topts, int show_progress, int overwrite_ignore, - struct commit *old_commit) + bool quiet) { memset(topts, 0, sizeof(*topts)); topts->head_idx = -1; @@ -767,7 +829,7 @@ static void init_topts(struct unpack_trees_options *topts, int merge, topts->initial_checkout = is_index_unborn(the_repository->index); topts->update = 1; topts->merge = 1; - topts->quiet = merge && old_commit; + topts->quiet = quiet; topts->verbose_update = show_progress; topts->fn = twoway_merge; topts->preserve_ignored = !overwrite_ignore; @@ -776,6 +838,7 @@ static void init_topts(struct unpack_trees_options *topts, int merge, static int merge_working_tree(const struct checkout_opts *opts, struct branch_info *old_branch_info, struct branch_info *new_branch_info, + bool quiet, int *writeout_error) { int ret; @@ -783,8 +846,10 @@ static int merge_working_tree(const struct checkout_opts *opts, struct tree *new_tree; repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR); - if (repo_read_index_preload(the_repository, NULL, 0) < 0) + if (repo_read_index_preload(the_repository, NULL, 0) < 0) { + rollback_lock_file(&lock_file); return error(_("index file corrupt")); + } resolve_undo_clear_index(the_repository->index); if (opts->new_orphan_branch && opts->orphan_from_empty_tree) { @@ -797,14 +862,18 @@ static int merge_working_tree(const struct checkout_opts *opts, } else { new_tree = repo_get_commit_tree(the_repository, new_branch_info->commit); - if (!new_tree) + if (!new_tree) { + rollback_lock_file(&lock_file); return error(_("unable to read tree (%s)"), oid_to_hex(&new_branch_info->commit->object.oid)); + } } if (opts->discard_changes) { ret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info); - if (ret) + if (ret) { + rollback_lock_file(&lock_file); return ret; + } } else { struct tree_desc trees[2]; struct tree *tree; @@ -814,13 +883,14 @@ static int merge_working_tree(const struct checkout_opts *opts, refresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL); if (unmerged_index(the_repository->index)) { + rollback_lock_file(&lock_file); error(_("you need to resolve your current index first")); return 1; } /* 2-way merge to the new branch */ - init_topts(&topts, opts->merge, opts->show_progress, - opts->overwrite_ignore, old_branch_info->commit); + init_topts(&topts, opts->show_progress, + opts->overwrite_ignore, quiet); init_checkout_metadata(&topts.meta, new_branch_info->refname, new_branch_info->commit ? &new_branch_info->commit->object.oid : @@ -846,82 +916,8 @@ static int merge_working_tree(const struct checkout_opts *opts, ret = unpack_trees(2, trees, &topts); clear_unpack_trees_porcelain(&topts); if (ret == -1) { - /* - * Unpack couldn't do a trivial merge; either - * give up or do a real merge, depending on - * whether the merge flag was used. - */ - struct tree *work; - struct tree *old_tree; - struct merge_options o; - struct strbuf sb = STRBUF_INIT; - struct strbuf old_commit_shortname = STRBUF_INIT; - - if (!opts->merge) - return 1; - - /* - * Without old_branch_info->commit, the below is the same as - * the two-tree unpack we already tried and failed. - */ - if (!old_branch_info->commit) - return 1; - old_tree = repo_get_commit_tree(the_repository, - old_branch_info->commit); - - if (repo_index_has_changes(the_repository, old_tree, &sb)) - die(_("cannot continue with staged changes in " - "the following files:\n%s"), sb.buf); - strbuf_release(&sb); - - /* Do more real merge */ - - /* - * We update the index fully, then write the - * tree from the index, then merge the new - * branch with the current tree, with the old - * branch as the base. Then we reset the index - * (but not the working tree) to the new - * branch, leaving the working tree as the - * merged version, but skipping unmerged - * entries in the index. - */ - - add_files_to_cache(the_repository, NULL, NULL, NULL, 0, - 0, 0); - init_ui_merge_options(&o, the_repository); - o.verbosity = 0; - work = write_in_core_index_as_tree(the_repository, - the_repository->index); - - ret = reset_tree(new_tree, - opts, 1, - writeout_error, new_branch_info); - if (ret) - return ret; - o.ancestor = old_branch_info->name; - if (!old_branch_info->name) { - strbuf_add_unique_abbrev(&old_commit_shortname, - &old_branch_info->commit->object.oid, - DEFAULT_ABBREV); - o.ancestor = old_commit_shortname.buf; - } - o.branch1 = new_branch_info->name; - o.branch2 = "local"; - o.conflict_style = opts->conflict_style; - ret = merge_ort_nonrecursive(&o, - new_tree, - work, - old_tree); - if (ret < 0) - die(NULL); - ret = reset_tree(new_tree, - opts, 0, - writeout_error, new_branch_info); - strbuf_release(&o.obuf); - strbuf_release(&old_commit_shortname); - if (ret) - return ret; + rollback_lock_file(&lock_file); + return MERGE_WORKING_TREE_UNPACK_FAILED; } } @@ -1166,6 +1162,10 @@ static int switch_branches(const struct checkout_opts *opts, struct object_id rev; int flag, writeout_error = 0; int do_merge = 1; + int created_autostash = 0; + struct strbuf old_commit_shortname = STRBUF_INIT; + struct strbuf autostash_msg = STRBUF_INIT; + const char *stash_label_base = NULL; trace2_cmd_mode("branch"); @@ -1203,11 +1203,49 @@ static int switch_branches(const struct checkout_opts *opts, do_merge = 0; } + if (old_branch_info.name) { + stash_label_base = old_branch_info.name; + } else if (old_branch_info.commit) { + strbuf_add_unique_abbrev(&old_commit_shortname, + &old_branch_info.commit->object.oid, + DEFAULT_ABBREV); + stash_label_base = old_commit_shortname.buf; + } + if (do_merge) { - ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error); + ret = merge_working_tree(opts, &old_branch_info, new_branch_info, + opts->merge, &writeout_error); + if (ret == MERGE_WORKING_TREE_UNPACK_FAILED && opts->merge) { + strbuf_addf(&autostash_msg, + "autostash while switching to '%s'", + new_branch_info->name); + create_autostash_ref(the_repository, + "CHECKOUT_AUTOSTASH_HEAD", + autostash_msg.buf, true); + created_autostash = 1; + ret = merge_working_tree(opts, &old_branch_info, new_branch_info, + false, &writeout_error); + } + if (created_autostash) { + if (opts->conflict_style >= 0) { + struct strbuf cfg = STRBUF_INIT; + strbuf_addf(&cfg, "merge.conflictStyle=%s", + conflict_style_name(opts->conflict_style)); + git_config_push_parameter(cfg.buf); + strbuf_release(&cfg); + } + apply_autostash_ref(the_repository, + "CHECKOUT_AUTOSTASH_HEAD", + new_branch_info->name, + "local", + stash_label_base, + autostash_msg.buf); + } if (ret) { branch_info_release(&old_branch_info); - return ret; + strbuf_release(&old_commit_shortname); + strbuf_release(&autostash_msg); + return ret < 0 ? 1 : ret; } } @@ -1216,8 +1254,22 @@ static int switch_branches(const struct checkout_opts *opts, update_refs_for_switch(opts, &old_branch_info, new_branch_info); + if (created_autostash) { + discard_index(the_repository->index); + if (repo_read_index(the_repository) < 0) + die(_("index file corrupt")); + + if (!opts->quiet && new_branch_info->commit) { + printf(_("The following paths have local changes:\n")); + show_local_changes(&new_branch_info->commit->object, + &opts->diff_options); + } + } + ret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1); branch_info_release(&old_branch_info); + strbuf_release(&old_commit_shortname); + strbuf_release(&autostash_msg); return ret || writeout_error; } @@ -1485,7 +1537,7 @@ static int parse_branchname_arg(int argc, const char **argv, * it would be extremely annoying. */ if (argc) - verify_non_filename(opts->prefix, arg); + verify_non_filename(the_repository, opts->prefix, arg); } else if (opts->accept_pathspec) { argcount++; argv++;
diff --git a/builtin/clone.c b/builtin/clone.c index fba3c9c..d60d1b6 100644 --- a/builtin/clone.c +++ b/builtin/clone.c
@@ -647,6 +647,7 @@ static int checkout(int submodule_progress, struct tree *tree; struct tree_desc t; int err = 0; + struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT_FORCE_SERIAL; if (option_no_checkout) return 0; @@ -668,7 +669,7 @@ static int checkout(int submodule_progress, } /* We need to be in the new work tree for the checkout */ - setup_work_tree(); + setup_work_tree(the_repository); repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR); @@ -697,8 +698,9 @@ static int checkout(int submodule_progress, if (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); - err |= run_hooks_l(the_repository, "post-checkout", oid_to_hex(null_oid(the_hash_algo)), - oid_to_hex(&oid), "1", NULL); + strvec_pushl(&hook_opt.args, oid_to_hex(null_oid(the_hash_algo)), + oid_to_hex(&oid), "1", NULL); + err |= run_hooks_opt(the_repository, "post-checkout", &hook_opt); if (!err && (option_recurse_submodules.nr > 0)) { struct child_process cmd = CHILD_PROCESS_INIT; @@ -1114,7 +1116,7 @@ int cmd_clone(int argc, die_errno(_("could not create work tree dir '%s'"), work_tree); junk_work_tree = work_tree; - set_git_work_tree(work_tree); + set_git_work_tree(the_repository, work_tree); } if (real_git_dir) { @@ -1184,7 +1186,7 @@ int cmd_clone(int argc, * repository, and reference backends may persist that information into * their on-disk data structures. */ - init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, + init_db(the_repository, git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, ref_storage_format, NULL, do_not_override_repo_unix_permissions, INIT_DB_QUIET | INIT_DB_SKIP_REFDB); @@ -1227,7 +1229,7 @@ int cmd_clone(int argc, * * This is sufficient for Git commands to discover the Git directory. */ - initialize_repository_version(GIT_HASH_UNKNOWN, + initialize_repository_version(the_repository, GIT_HASH_UNKNOWN, the_repository->ref_storage_format, 1); refs_create_refdir_stubs(the_repository, git_dir, NULL); @@ -1440,9 +1442,9 @@ int cmd_clone(int argc, * ours to the same thing. */ hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport)); - initialize_repository_version(hash_algo, the_repository->ref_storage_format, 1); + initialize_repository_version(the_repository, hash_algo, the_repository->ref_storage_format, 1); repo_set_hash_algo(the_repository, hash_algo); - create_reference_database(NULL, 1); + create_reference_database(the_repository, NULL, 1); /* * Before fetching from the remote, download and install bundle
diff --git a/builtin/commit.c b/builtin/commit.c index a3e52ac..28f6174 100644 --- a/builtin/commit.c +++ b/builtin/commit.c
@@ -1979,7 +1979,8 @@ int cmd_commit(int argc, &oid, flags); } - apply_autostash_ref(the_repository, "MERGE_AUTOSTASH"); + apply_autostash_ref(the_repository, "MERGE_AUTOSTASH", + NULL, NULL, NULL, NULL); cleanup: free_commit_extra_headers(extra);
diff --git a/builtin/config.c b/builtin/config.c index cf4ba0f..8d8ec0b 100644 --- a/builtin/config.c +++ b/builtin/config.c
@@ -1,6 +1,7 @@ #define USE_THE_REPOSITORY_VARIABLE #include "builtin.h" #include "abspath.h" +#include "advice.h" #include "config.h" #include "color.h" #include "date.h" @@ -210,6 +211,26 @@ static void check_argc(int argc, int min, int max) exit(129); } +static NORETURN void die_missing_set_value(const char *arg) +{ + const char *last_dot = strrchr(arg, '.'); + const char *eq = last_dot ? strchr(last_dot + 1, '=') : NULL; + char *prefix = eq ? xstrndup(arg, eq - arg) : NULL; + + if (prefix && git_config_key_is_valid(prefix)) { + error(_("missing value to set to the variable '%s'"), arg); + advise(_("did you mean \"git config set %s %s\"?"), + prefix, eq + 1); + } else if (git_config_key_is_valid(arg)) { + error(_("missing value to set to the variable '%s'"), arg); + } else { + error(_("missing value to set to a variable with an invalid name '%s'"), + arg); + } + free(prefix); + exit(129); +} + static void show_config_origin(const struct config_display_options *opts, const struct key_value_info *kvi, struct strbuf *buf) @@ -1133,6 +1154,8 @@ static int cmd_config_set(int argc, const char **argv, const char *prefix, argc = parse_options(argc, argv, prefix, opts, builtin_config_set_usage, PARSE_OPT_STOP_AT_NON_OPTION); + if (argc == 1) + die_missing_set_value(argv[0]); check_argc(argc, 2, 2); if ((flags & CONFIG_FLAGS_FIXED_VALUE) && !value_pattern) @@ -1371,6 +1394,7 @@ static int cmd_config_actions(int argc, const char **argv, const char *prefix) }; char *value = NULL, *comment = NULL; int ret = 0; + int actions_implicit; struct key_value_info default_kvi = KVI_INIT; argc = parse_options(argc, argv, prefix, opts, @@ -1385,7 +1409,8 @@ static int cmd_config_actions(int argc, const char **argv, const char *prefix) exit(129); } - if (actions == 0) + actions_implicit = (actions == 0); + if (actions_implicit) switch (argc) { case 1: actions = ACTION_GET; break; case 2: actions = ACTION_SET; break; @@ -1394,6 +1419,11 @@ static int cmd_config_actions(int argc, const char **argv, const char *prefix) error(_("no action specified")); exit(129); } + if (actions_implicit && argc == 1) { + const char *last_dot = strrchr(argv[0], '.'); + if (last_dot && strchr(last_dot + 1, '=')) + die_missing_set_value(argv[0]); + } if (display_opts.omit_values && !(actions == ACTION_LIST || actions == ACTION_GET_REGEXP)) { error(_("--name-only is only applicable to --list or --get-regexp"));
diff --git a/builtin/describe.c b/builtin/describe.c index bffeed1..a94dad9 100644 --- a/builtin/describe.c +++ b/builtin/describe.c
@@ -712,13 +712,25 @@ int cmd_describe(int argc, NULL); if (always) strvec_push(&args, "--always"); - if (!all) { + if (!all) strvec_push(&args, "--tags"); + + for_each_string_list_item(item, &patterns) + strvec_pushf(&args, "--refs=refs/tags/%s", item->string); + for_each_string_list_item(item, &exclude_patterns) + strvec_pushf(&args, "--exclude=refs/tags/%s", item->string); + + if (all) { for_each_string_list_item(item, &patterns) - strvec_pushf(&args, "--refs=refs/tags/%s", item->string); + strvec_pushf(&args, "--refs=refs/heads/%s", item->string); for_each_string_list_item(item, &exclude_patterns) - strvec_pushf(&args, "--exclude=refs/tags/%s", item->string); + strvec_pushf(&args, "--exclude=refs/heads/%s", item->string); + for_each_string_list_item(item, &patterns) + strvec_pushf(&args, "--refs=refs/remotes/%s", item->string); + for_each_string_list_item(item, &exclude_patterns) + strvec_pushf(&args, "--exclude=refs/remotes/%s", item->string); } + if (argc) strvec_pushv(&args, argv); else @@ -740,6 +752,9 @@ int cmd_describe(int argc, return ret; } + if (!all) + for_each_ref_opts.prefix = "refs/tags/"; + hashmap_init(&names, commit_name_neq, NULL, 0); refs_for_each_ref_ext(get_main_ref_store(the_repository), get_name, NULL, &for_each_ref_opts); @@ -781,7 +796,7 @@ int cmd_describe(int argc, struct rev_info revs; int fd; - setup_work_tree(); + setup_work_tree(the_repository); prepare_repo_settings(the_repository); the_repository->settings.command_requires_full_index = 0; repo_read_index(the_repository);
diff --git a/builtin/diff-index.c b/builtin/diff-index.c index 522dacf..3db7cff 100644 --- a/builtin/diff-index.c +++ b/builtin/diff-index.c
@@ -69,7 +69,7 @@ int cmd_diff_index(int argc, rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1) usage(diff_cache_usage); if (!(option & DIFF_INDEX_CACHED)) { - setup_work_tree(); + setup_work_tree(the_repository); if (repo_read_index_preload(the_repository, &rev.diffopt.pathspec, 0) < 0) { perror("repo_read_index_preload"); return -1;
diff --git a/builtin/diff.c b/builtin/diff.c index 0b23c41..4b46e39 100644 --- a/builtin/diff.c +++ b/builtin/diff.c
@@ -159,7 +159,7 @@ static void builtin_diff_index(struct rev_info *revs, revs->max_age != -1) usage(builtin_diff_usage); if (!(option & DIFF_INDEX_CACHED)) { - setup_work_tree(); + setup_work_tree(the_repository); if (repo_read_index_preload(the_repository, &revs->diffopt.pathspec, 0) < 0) { die_errno("repo_read_index_preload"); @@ -281,7 +281,7 @@ static void builtin_diff_files(struct rev_info *revs, int argc, const char **arg (revs->diffopt.output_format & DIFF_FORMAT_PATCH)) diff_merges_set_dense_combined_if_unset(revs); - setup_work_tree(); + setup_work_tree(the_repository); if (repo_read_index_preload(the_repository, &revs->diffopt.pathspec, 0) < 0) { die_errno("repo_read_index_preload"); @@ -455,7 +455,7 @@ int cmd_diff(int argc, break; } - prefix = setup_git_directory_gently(&nongit); + prefix = setup_git_directory_gently(the_repository, &nongit); if (!nongit) { prepare_repo_settings(the_repository); @@ -471,8 +471,8 @@ int cmd_diff(int argc, * as a colourful "diff" replacement. */ if (nongit || ((argc == i + 2) && - (!path_inside_repo(prefix, argv[i]) || - !path_inside_repo(prefix, argv[i + 1])))) + (!path_inside_repo(the_repository, prefix, argv[i]) || + !path_inside_repo(the_repository, prefix, argv[i + 1])))) no_index = DIFF_NO_INDEX_IMPLICIT; }
diff --git a/builtin/difftool.c b/builtin/difftool.c index e4bc1f8..2a21005 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c
@@ -767,7 +767,7 @@ int cmd_difftool(int argc, die(_("difftool requires worktree or --no-index")); if (!no_index){ - setup_work_tree(); + setup_work_tree(repo); setenv(GIT_DIR_ENVIRONMENT, absolute_path(repo_get_git_dir(repo)), 1); setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(repo_get_work_tree(repo)), 1); } else if (dir_diff)
diff --git a/builtin/fast-import.c b/builtin/fast-import.c index 82bc6dc..070a5af 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c
@@ -965,6 +965,7 @@ static int store_object( unsigned long hdrlen, deltalen; struct git_hash_ctx c; git_zstream s; + struct repo_config_values *cfg = repo_config_values(the_repository); hdrlen = format_object_header((char *)hdr, sizeof(hdr), type, dat->len); @@ -1005,7 +1006,7 @@ static int store_object( } else delta = NULL; - git_deflate_init(&s, pack_compression_level); + git_deflate_init(&s, cfg->pack_compression_level); if (delta) { s.next_in = delta; s.avail_in = deltalen; @@ -1032,7 +1033,7 @@ static int store_object( if (delta) { FREE_AND_NULL(delta); - git_deflate_init(&s, pack_compression_level); + git_deflate_init(&s, cfg->pack_compression_level); s.next_in = (void *)dat->buf; s.avail_in = dat->len; s.avail_out = git_deflate_bound(&s, s.avail_in); @@ -1115,6 +1116,7 @@ static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark) struct git_hash_ctx c; git_zstream s; struct hashfile_checkpoint checkpoint; + struct repo_config_values *cfg = repo_config_values(the_repository); int status = Z_OK; /* Determine if we should auto-checkpoint. */ @@ -1134,7 +1136,7 @@ static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark) crc32_begin(pack_file); - git_deflate_init(&s, pack_compression_level); + git_deflate_init(&s, cfg->pack_compression_level); hdrlen = encode_in_pack_object_header(out_buf, out_sz, OBJ_BLOB, len);
diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index d9e42ba..316badd 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c
@@ -223,7 +223,7 @@ int cmd_fetch_pack(int argc, int flags = args.verbose ? CONNECT_VERBOSE : 0; if (args.diag_url) flags |= CONNECT_DIAG_URL; - conn = git_connect(fd, dest, "git-upload-pack", + conn = git_connect(fd, dest, GIT_CONNECT_UPLOAD_PACK, args.uploadpack, flags); if (!conn) return args.diag_url ? 0 : 1;
diff --git a/builtin/fetch.c b/builtin/fetch.c index a22c319..c1d7c67 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c
@@ -98,7 +98,8 @@ static struct transport *gtransport; static struct transport *gsecondary; static struct refspec refmap = REFSPEC_INIT_FETCH; static struct string_list server_options = STRING_LIST_INIT_DUP; -static struct string_list negotiation_tip = STRING_LIST_INIT_NODUP; +static struct string_list negotiation_restrict = STRING_LIST_INIT_NODUP; +static struct string_list negotiation_include = STRING_LIST_INIT_NODUP; struct fetch_config { enum display_format display_format; @@ -1534,23 +1535,29 @@ static int add_oid(const struct reference *ref, void *cb_data) return 0; } -static void add_negotiation_tips(struct git_transport_options *smart_options) +static void add_negotiation_tips(struct string_list *input_list, + struct oid_array **output_list, + const char *argname) { struct oid_array *oids = xcalloc(1, sizeof(*oids)); int i; - for (i = 0; i < negotiation_tip.nr; i++) { - const char *s = negotiation_tip.items[i].string; + for (i = 0; i < input_list->nr; i++) { + const char *s = input_list->items[i].string; struct refs_for_each_ref_options opts = { .pattern = s, }; int old_nr; if (!has_glob_specials(s)) { struct object_id oid; + + /* Ignore missing reference. */ if (repo_get_oid(the_repository, s, &oid)) - die(_("%s is not a valid object"), s); + continue; + /* Fail on missing object pointed by ref. */ if (!odb_has_object(the_repository->objects, &oid, 0)) die(_("the object %s does not exist"), s); + oid_array_append(oids, &oid); continue; } @@ -1558,10 +1565,10 @@ static void add_negotiation_tips(struct git_transport_options *smart_options) refs_for_each_ref_ext(get_main_ref_store(the_repository), add_oid, oids, &opts); if (old_nr == oids->nr) - warning("ignoring --negotiation-tip=%s because it does not match any refs", - s); + warning(_("ignoring %s=%s because it does not match any refs"), + argname, s); } - smart_options->negotiation_tips = oids; + *output_list = oids; } static struct transport *prepare_transport(struct remote *remote, int deepen, @@ -1595,11 +1602,50 @@ static struct transport *prepare_transport(struct remote *remote, int deepen, set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER, spec); set_option(transport, TRANS_OPT_FROM_PROMISOR, "1"); } - if (negotiation_tip.nr) { + if (negotiation_restrict.nr) { if (transport->smart_options) - add_negotiation_tips(transport->smart_options); + add_negotiation_tips(&negotiation_restrict, + &transport->smart_options->negotiation_restrict_tips, + "--negotiation-restrict"); else - warning("ignoring --negotiation-tip because the protocol does not support it"); + warning(_("ignoring %s because the protocol does not support it"), + "--negotiation-restrict"); + } else if (remote->negotiation_restrict.nr) { + struct string_list_item *item; + for_each_string_list_item(item, &remote->negotiation_restrict) + string_list_append(&negotiation_restrict, item->string); + if (transport->smart_options) + add_negotiation_tips(&negotiation_restrict, + &transport->smart_options->negotiation_restrict_tips, + "--negotiation-restrict"); + else { + struct strbuf config_name = STRBUF_INIT; + strbuf_addf(&config_name, "remote.%s.negotiationRestrict", remote->name); + warning(_("ignoring %s because the protocol does not support it"), + config_name.buf); + strbuf_release(&config_name); + } + } + if (negotiation_include.nr) { + if (transport->smart_options) + add_negotiation_tips(&negotiation_include, + &transport->smart_options->negotiation_include_tips, + "--negotiation-include"); + else + warning(_("ignoring %s because the protocol does not support it"), + "--negotiation-include"); + } else if (remote->negotiation_include.nr) { + if (transport->smart_options) { + add_negotiation_tips(&remote->negotiation_include, + &transport->smart_options->negotiation_include_tips, + "--negotiation-include"); + } else { + struct strbuf config_name = STRBUF_INIT; + strbuf_addf(&config_name, "remote.%s.negotiationInclude", remote->name); + warning(_("ignoring %s because the protocol does not support it"), + config_name.buf); + strbuf_release(&config_name); + } } return transport; } @@ -2138,48 +2184,6 @@ static int get_one_remote_for_fetch(struct remote *remote, void *priv) return 0; } -struct remote_group_data { - const char *name; - struct string_list *list; -}; - -static int get_remote_group(const char *key, const char *value, - const struct config_context *ctx UNUSED, - void *priv) -{ - struct remote_group_data *g = priv; - - if (skip_prefix(key, "remotes.", &key) && !strcmp(key, g->name)) { - /* split list by white space */ - while (*value) { - size_t wordlen = strcspn(value, " \t\n"); - - if (wordlen >= 1) - string_list_append_nodup(g->list, - xstrndup(value, wordlen)); - value += wordlen + (value[wordlen] != '\0'); - } - } - - return 0; -} - -static int add_remote_or_group(const char *name, struct string_list *list) -{ - int prev_nr = list->nr; - struct remote_group_data g; - g.name = name; g.list = list; - - repo_config(the_repository, get_remote_group, &g); - if (list->nr == prev_nr) { - struct remote *remote = remote_get(name); - if (!remote_is_configured(remote, 0)) - return 0; - string_list_append(list, remote->name); - } - return 1; -} - static void add_options_to_argv(struct strvec *argv, const struct fetch_config *config) { @@ -2565,8 +2569,11 @@ int cmd_fetch(int argc, N_("specify fetch refmap"), PARSE_OPT_NONEG, parse_refmap_arg), OPT_STRING_LIST('o', "server-option", &server_options, N_("server-specific"), N_("option to transmit")), OPT_IPVERSION(&family), - OPT_STRING_LIST(0, "negotiation-tip", &negotiation_tip, N_("revision"), + OPT_STRING_LIST(0, "negotiation-restrict", &negotiation_restrict, N_("revision"), N_("report that we have only objects reachable from this object")), + OPT_ALIAS(0, "negotiation-tip", "negotiation-restrict"), + OPT_STRING_LIST(0, "negotiation-include", &negotiation_include, N_("revision"), + N_("ensure this ref is always sent as a negotiation have")), OPT_BOOL(0, "negotiate-only", &negotiate_only, N_("do not fetch a packfile; instead, print ancestors of negotiation tips")), OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), @@ -2656,9 +2663,6 @@ int cmd_fetch(int argc, config.display_format = DISPLAY_FORMAT_PORCELAIN; } - if (negotiate_only && !negotiation_tip.nr) - die(_("--negotiate-only needs one or more --negotiation-tip=*")); - if (deepen_relative) { if (deepen_relative < 0) die(_("negative depth in --deepen is not supported")); @@ -2746,14 +2750,19 @@ int cmd_fetch(int argc, if (!remote) die(_("must supply remote when using --negotiate-only")); gtransport = prepare_transport(remote, 1, &filter_options); - if (gtransport->smart_options) { - gtransport->smart_options->acked_commits = &acked_commits; - } else { + + if (!gtransport->smart_options) { warning(_("protocol does not support --negotiate-only, exiting")); result = 1; trace2_region_leave("fetch", "negotiate-only", the_repository); goto cleanup; } + if (!gtransport->smart_options->negotiation_restrict_tips) + die(_("%s needs one or more %s"), "--negotiate-only", + "--negotiation-restrict=*"); + + gtransport->smart_options->acked_commits = &acked_commits; + if (server_options.nr) gtransport->server_options = &server_options; result = transport_fetch_refs(gtransport, NULL);
diff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c index 242c594..f920cf3 100644 --- a/builtin/fsmonitor--daemon.c +++ b/builtin/fsmonitor--daemon.c
@@ -16,7 +16,7 @@ #include "fsmonitor--daemon.h" #include "simple-ipc.h" -#include "khash.h" +#include "strmap.h" #include "run-command.h" #include "trace.h" #include "trace2.h" @@ -86,6 +86,8 @@ static int do_as_client__send_stop(void) { struct strbuf answer = STRBUF_INIT; int ret; + int max_wait_ms = 30000; + int elapsed_ms = 0; ret = fsmonitor_ipc__send_command("quit", &answer); @@ -96,8 +98,16 @@ static int do_as_client__send_stop(void) return ret; trace2_region_enter("fsm_client", "polling-for-daemon-exit", NULL); - while (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING) + while (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING) { + if (elapsed_ms >= max_wait_ms) { + trace2_region_leave("fsm_client", + "polling-for-daemon-exit", NULL); + return error(_("daemon did not stop within %d seconds"), + max_wait_ms / 1000); + } sleep_millisec(50); + elapsed_ms += 50; + } trace2_region_leave("fsm_client", "polling-for-daemon-exit", NULL); return 0; @@ -197,20 +207,31 @@ static enum fsmonitor_cookie_item_result with_lock__wait_for_cookie( unlink(cookie_pathname.buf); /* - * Technically, this is an infinite wait (well, unless another - * thread sends us an abort). I'd like to change this to - * use `pthread_cond_timedwait()` and return an error/timeout - * and let the caller do the trivial response thing, but we - * don't have that routine in our thread-utils. - * - * After extensive beta testing I'm not really worried about - * this. Also note that the above open() and unlink() calls - * will cause at least two FS events on that path, so the odds - * of getting stuck are pretty slim. + * Wait for the listener thread to observe the cookie file. + * Time out after a short interval so that the client + * does not hang forever if the filesystem does not deliver + * events (e.g., on certain container/overlay filesystems + * where inotify watches succeed but events never arrive). */ - while (cookie->result == FCIR_INIT) - pthread_cond_wait(&state->cookies_cond, - &state->main_lock); + { + struct timeval now; + struct timespec ts; + int err = 0; + + gettimeofday(&now, NULL); + ts.tv_sec = now.tv_sec + 1; + ts.tv_nsec = now.tv_usec * 1000; + + while (cookie->result == FCIR_INIT && !err) + err = pthread_cond_timedwait(&state->cookies_cond, + &state->main_lock, + &ts); + if (err == ETIMEDOUT && cookie->result == FCIR_INIT) { + trace_printf_key(&trace_fsmonitor, + "cookie_wait timed out"); + cookie->result = FCIR_ERROR; + } + } done: hashmap_remove(&state->cookies, &cookie->entry, NULL); @@ -653,8 +674,6 @@ static int fsmonitor_parse_client_token(const char *buf_token, return 0; } -KHASH_INIT(str, const char *, int, 0, kh_str_hash_func, kh_str_hash_equal) - static int do_handle_client(struct fsmonitor_daemon_state *state, const char *command, ipc_server_reply_cb *reply, @@ -671,8 +690,7 @@ static int do_handle_client(struct fsmonitor_daemon_state *state, const struct fsmonitor_batch *batch; struct fsmonitor_batch *remainder = NULL; intmax_t count = 0, duplicates = 0; - kh_str_t *shown; - int hash_ret; + struct strset shown = STRSET_INIT; int do_trivial = 0; int do_flush = 0; int do_cookie = 0; @@ -861,14 +879,14 @@ static int do_handle_client(struct fsmonitor_daemon_state *state, * so walk the batch list backwards from the current head back * to the batch (sequence number) they named. * - * We use khash to de-dup the list of pathnames. + * We use a strset to de-dup the list of pathnames. * * NEEDSWORK: each batch contains a list of interned strings, * so we only need to do pointer comparisons here to build the * hash table. Currently, we're still comparing the string * values. */ - shown = kh_init_str(); + strset_init_with_options(&shown, NULL, 0); for (batch = batch_head; batch && batch->batch_seq_nr > requested_oldest_seq_nr; batch = batch->next) { @@ -878,11 +896,9 @@ static int do_handle_client(struct fsmonitor_daemon_state *state, const char *s = batch->interned_paths[k]; size_t s_len; - if (kh_get_str(shown, s) != kh_end(shown)) + if (!strset_add(&shown, s)) duplicates++; else { - kh_put_str(shown, s, &hash_ret); - trace_printf_key(&trace_fsmonitor, "send[%"PRIuMAX"]: %s", count, s); @@ -909,8 +925,6 @@ static int do_handle_client(struct fsmonitor_daemon_state *state, total_response_len += payload.len; } - kh_release_str(shown); - pthread_mutex_lock(&state->main_lock); if (token_data->client_ref_count > 0) @@ -954,6 +968,7 @@ static int do_handle_client(struct fsmonitor_daemon_state *state, trace2_data_intmax("fsmonitor", the_repository, "response/count/duplicates", duplicates); cleanup: + strset_clear(&shown); strbuf_release(&response_token); strbuf_release(&requested_token_id); strbuf_release(&payload); @@ -1405,6 +1420,15 @@ static int fsmonitor_run_daemon(void) done: pthread_cond_destroy(&state.cookies_cond); pthread_mutex_destroy(&state.main_lock); + { + struct hashmap_iter iter; + struct fsmonitor_cookie_item *cookie; + + hashmap_for_each_entry(&state.cookies, &iter, cookie, entry) + free(cookie->name); + hashmap_clear_and_free(&state.cookies, + struct fsmonitor_cookie_item, entry); + } fsm_listen__dtor(&state); fsm_health__dtor(&state); @@ -1420,7 +1444,7 @@ static int fsmonitor_run_daemon(void) return err; } -static int try_to_run_foreground_daemon(int detach_console MAYBE_UNUSED) +static int try_to_run_foreground_daemon(int detach_console) { /* * Technically, we don't need to probe for an existing daemon @@ -1440,10 +1464,21 @@ static int try_to_run_foreground_daemon(int detach_console MAYBE_UNUSED) fflush(stderr); } + if (detach_console) { #ifdef GIT_WINDOWS_NATIVE - if (detach_console) FreeConsole(); +#else + /* + * Create a new session so that the daemon is detached + * from the parent's process group. This prevents + * shells with job control (e.g. bash with "set -m") + * from waiting on the daemon when they wait for a + * foreground command that implicitly spawned it. + */ + if (setsid() == -1) + warning_errno(_("setsid failed")); #endif + } return !!fsmonitor_run_daemon(); } @@ -1506,6 +1541,7 @@ static int try_to_start_background_daemon(void) cp.no_stdin = 1; cp.no_stdout = 1; cp.no_stderr = 1; + cp.close_fd_above_stderr = 1; sbgr = start_bg_command(&cp, bg_wait_cb, NULL, fsmonitor__start_timeout_sec);
diff --git a/builtin/gc.c b/builtin/gc.c index 3a71e31..c26c93e 100644 --- a/builtin/gc.c +++ b/builtin/gc.c
@@ -466,6 +466,7 @@ static int rerere_gc_condition(struct gc_config *cfg UNUSED) static int too_many_loose_objects(int limit) { + struct odb_source_files *files = odb_source_files_downcast(the_repository->objects->sources); /* * This is weird, but stems from legacy behaviour: the GC auto * threshold was always essentially interpreted as if it was rounded up @@ -474,9 +475,8 @@ static int too_many_loose_objects(int limit) int auto_threshold = DIV_ROUND_UP(limit, 256) * 256; unsigned long loose_count; - if (odb_source_loose_count_objects(the_repository->objects->sources, - ODB_COUNT_OBJECTS_APPROXIMATE, - &loose_count) < 0) + if (odb_source_count_objects(&files->loose->base, ODB_COUNT_OBJECTS_APPROXIMATE, + &loose_count) < 0) return 0; return loose_count > auto_threshold; @@ -1590,6 +1590,7 @@ static int maintenance_task_geometric_repack(struct maintenance_run_opts *opts, pack_geometry_split(&geometry); child.git_cmd = 1; + child.odb_to_close = the_repository->objects; strvec_pushl(&child.args, "repack", "-d", "-l", NULL); if (geometry.split < geometry.pack_nr)
diff --git a/builtin/grep.c b/builtin/grep.c index e33285e..6a09571 100644 --- a/builtin/grep.c +++ b/builtin/grep.c
@@ -28,9 +28,12 @@ #include "object-file.h" #include "object-name.h" #include "odb.h" +#include "oid-array.h" +#include "oidset.h" #include "packfile.h" #include "pager.h" #include "path.h" +#include "promisor-remote.h" #include "read-cache-ll.h" #include "write-or-die.h" @@ -692,6 +695,144 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, return hit; } +static void collect_blob_oids_for_tree(struct repository *repo, + const struct pathspec *pathspec, + struct tree_desc *tree, + struct strbuf *base, + int tn_len, + struct oidset *blob_oids) +{ + struct name_entry entry; + int old_baselen = base->len; + struct strbuf name = STRBUF_INIT; + enum interesting match = entry_not_interesting; + + while (tree_entry(tree, &entry)) { + if (match != all_entries_interesting) { + strbuf_addstr(&name, base->buf + tn_len); + match = tree_entry_interesting(repo->index, + &entry, &name, + pathspec); + strbuf_reset(&name); + + if (match == all_entries_not_interesting) + break; + if (match == entry_not_interesting) + continue; + } + + strbuf_add(base, entry.path, tree_entry_len(&entry)); + + if (S_ISREG(entry.mode)) { + if (!odb_has_object(repo->objects, &entry.oid, 0)) + oidset_insert(blob_oids, &entry.oid); + } else if (S_ISDIR(entry.mode)) { + enum object_type type; + struct tree_desc sub_tree; + void *data; + unsigned long size; + + data = odb_read_object(repo->objects, &entry.oid, + &type, &size); + if (!data) + die(_("unable to read tree (%s)"), + oid_to_hex(&entry.oid)); + + strbuf_addch(base, '/'); + init_tree_desc(&sub_tree, &entry.oid, data, size); + collect_blob_oids_for_tree(repo, pathspec, &sub_tree, + base, tn_len, blob_oids); + free(data); + } + /* + * ...no else clause for S_ISGITLINK: submodules have their + * own promisor configuration and would need separate fetches + * anyway. + */ + + strbuf_setlen(base, old_baselen); + } + + strbuf_release(&name); +} + +static void collect_blob_oids_for_treeish(struct grep_opt *opt, + const struct pathspec *pathspec, + const struct object_id *tree_ish_oid, + const char *name, + struct oidset *blob_oids) +{ + struct tree_desc tree; + void *data; + unsigned long size; + struct strbuf base = STRBUF_INIT; + int len; + + data = odb_read_object_peeled(opt->repo->objects, tree_ish_oid, + OBJ_TREE, &size, NULL); + + if (!data) + return; + + len = name ? strlen(name) : 0; + if (len) { + strbuf_add(&base, name, len); + strbuf_addch(&base, ':'); + } + init_tree_desc(&tree, tree_ish_oid, data, size); + + collect_blob_oids_for_tree(opt->repo, pathspec, &tree, + &base, base.len, blob_oids); + + strbuf_release(&base); + free(data); +} + +static void prefetch_grep_blobs(struct grep_opt *opt, + const struct pathspec *pathspec, + const struct object_array *list) +{ + struct oidset blob_oids = OIDSET_INIT; + + /* Exit if we're not in a partial clone */ + if (!repo_has_promisor_remote(opt->repo)) + return; + + /* For each tree, gather the blobs in it */ + for (int i = 0; i < list->nr; i++) { + struct object *real_obj; + + obj_read_lock(); + real_obj = deref_tag(opt->repo, list->objects[i].item, + NULL, 0); + obj_read_unlock(); + + if (real_obj && + (real_obj->type == OBJ_COMMIT || + real_obj->type == OBJ_TREE)) + collect_blob_oids_for_treeish(opt, pathspec, + &real_obj->oid, + list->objects[i].name, + &blob_oids); + } + + /* Prefetch the blobs we found */ + if (oidset_size(&blob_oids)) { + struct oid_array to_fetch = OID_ARRAY_INIT; + struct oidset_iter iter; + const struct object_id *oid; + + oidset_iter_init(&blob_oids, &iter); + while ((oid = oidset_iter_next(&iter))) + oid_array_append(&to_fetch, oid); + + promisor_remote_get_direct(opt->repo, to_fetch.oid, to_fetch.nr); + + oid_array_clear(&to_fetch); + } + oidset_clear(&blob_oids); +} + static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, struct object *obj, const char *name, const char *path) { @@ -732,6 +873,8 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec, int hit = 0; const unsigned int nr = list->nr; + prefetch_grep_blobs(opt, pathspec, list); + for (i = 0; i < nr; i++) { struct object *real_obj; @@ -1064,7 +1207,7 @@ int cmd_grep(int argc, use_index = 0; else /* die the same way as if we did it at the beginning */ - setup_git_directory(); + setup_git_directory(the_repository); } /* Ignore --recurse-submodules if --no-index is given or implied */ if (!use_index) @@ -1151,7 +1294,7 @@ int cmd_grep(int argc, object = parse_object_or_die(the_repository, &oid, arg); if (!seen_dashdash) - verify_non_filename(prefix, arg); + verify_non_filename(the_repository, prefix, arg); add_object_array_with_path(object, arg, &list, oc.mode, oc.path); object_context_release(&oc); } @@ -1163,7 +1306,7 @@ int cmd_grep(int argc, if (!seen_dashdash) { int j; for (j = i; j < argc; j++) - verify_filename(prefix, argv[j], j == i && allow_revs); + verify_filename(the_repository, prefix, argv[j], j == i && allow_revs); } parse_pathspec(&pathspec, 0, @@ -1272,7 +1415,7 @@ int cmd_grep(int argc, die(_("--[no-]exclude-standard cannot be used for tracked contents")); } else if (!list.nr) { if (!cached) - setup_work_tree(); + setup_work_tree(the_repository); hit = grep_cache(&opt, &pathspec, cached); } else {
diff --git a/builtin/hash-object.c b/builtin/hash-object.c index 5d900a6..f306b06 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c
@@ -100,9 +100,9 @@ int cmd_hash_object(int argc, hash_object_usage, 0); if (flags & INDEX_WRITE_OBJECT) - prefix = setup_git_directory(); + prefix = setup_git_directory(the_repository); else - prefix = setup_git_directory_gently(&nongit); + prefix = setup_git_directory_gently(the_repository, &nongit); if (nongit && !the_hash_algo) repo_set_hash_algo(the_repository, GIT_HASH_DEFAULT);
diff --git a/builtin/help.c b/builtin/help.c index c0aece4..a140339 100644 --- a/builtin/help.c +++ b/builtin/help.c
@@ -740,7 +740,7 @@ int cmd_help(int argc, return 0; } - setup_git_directory_gently(&nongit); + setup_git_directory_gently(the_repository, &nongit); repo_config(the_repository, git_help_config, NULL); if (parsed_help_format != HELP_FORMAT_NONE)
diff --git a/builtin/history.c b/builtin/history.c index 9526938..091465a 100644 --- a/builtin/history.c +++ b/builtin/history.c
@@ -10,6 +10,7 @@ #include "gettext.h" #include "hex.h" #include "lockfile.h" +#include "merge-ort.h" #include "oidmap.h" #include "parse-options.h" #include "path.h" @@ -23,6 +24,8 @@ #include "unpack-trees.h" #include "wt-status.h" +#define GIT_HISTORY_FIXUP_USAGE \ + N_("git history fixup <commit> [--dry-run] [--update-refs=(branches|head)] [--reedit-message] [--empty=(drop|keep|abort)]") #define GIT_HISTORY_REWORD_USAGE \ N_("git history reword <commit> [--dry-run] [--update-refs=(branches|head)]") #define GIT_HISTORY_SPLIT_USAGE \ @@ -91,13 +94,18 @@ static int fill_commit_message(struct repository *repo, return 0; } -static int commit_tree_with_edited_message_ext(struct repository *repo, - const char *action, - struct commit *commit_with_message, - const struct commit_list *parents, - const struct object_id *old_tree, - const struct object_id *new_tree, - struct commit **out) +enum commit_tree_flags { + COMMIT_TREE_EDIT_MESSAGE = (1 << 0), +}; + +static int commit_tree_ext(struct repository *repo, + const char *action, + struct commit *commit_with_message, + const struct commit_list *parents, + const struct object_id *old_tree, + const struct object_id *new_tree, + struct commit **out, + enum commit_tree_flags flags) { const char *exclude_gpgsig[] = { /* We reencode the message, so the encoding needs to be stripped. */ @@ -122,10 +130,14 @@ static int commit_tree_with_edited_message_ext(struct repository *repo, original_author = xmemdupz(ptr, len); find_commit_subject(original_message, &original_body); - ret = fill_commit_message(repo, old_tree, new_tree, - original_body, action, &commit_message); - if (ret < 0) - goto out; + if (flags & COMMIT_TREE_EDIT_MESSAGE) { + ret = fill_commit_message(repo, old_tree, new_tree, + original_body, action, &commit_message); + if (ret < 0) + goto out; + } else { + strbuf_addstr(&commit_message, original_body); + } original_extra_headers = read_commit_extra_headers(commit_with_message, exclude_gpgsig); @@ -168,8 +180,8 @@ static int commit_tree_with_edited_message(struct repository *repo, oidcpy(&parent_tree_oid, repo->hash_algo->empty_tree); } - return commit_tree_with_edited_message_ext(repo, action, original, original->parents, - &parent_tree_oid, tree_oid, out); + return commit_tree_ext(repo, action, original, original->parents, + &parent_tree_oid, tree_oid, out, COMMIT_TREE_EDIT_MESSAGE); } enum ref_action { @@ -272,7 +284,7 @@ static int setup_revwalk(struct repository *repo, commit_list_insert(original, &from_list); ret = repo_is_descendant_of(repo, head, from_list); - free_commit_list(from_list); + commit_list_free(from_list); if (ret < 0) { ret = error(_("cannot determine descendance")); @@ -326,10 +338,13 @@ static int handle_reference_updates(struct rev_info *revs, struct commit *original, struct commit *rewritten, const char *reflog_msg, - int dry_run) + int dry_run, + enum replay_empty_commit_action empty) { const struct name_decoration *decoration; - struct replay_revisions_options opts = { 0 }; + struct replay_revisions_options opts = { + .empty = empty, + }; struct replay_result result = { 0 }; struct ref_transaction *transaction = NULL; struct strbuf err = STRBUF_INIT; @@ -425,6 +440,236 @@ static int handle_reference_updates(struct rev_info *revs, return ret; } +static int commit_became_empty(struct repository *repo, + struct commit *original, + struct tree *result) +{ + struct commit *parent = original->parents ? original->parents->item : NULL; + struct object_id parent_tree_oid; + + if (parent) { + if (repo_parse_commit(repo, parent)) + return error(_("unable to parse parent of %s"), + oid_to_hex(&original->object.oid)); + + parent_tree_oid = repo_get_commit_tree(repo, parent)->object.oid; + } else { + oidcpy(&parent_tree_oid, repo->hash_algo->empty_tree); + } + + return oideq(&result->object.oid, &parent_tree_oid); +} + +static int parse_opt_empty(const struct option *opt, const char *arg, int unset) +{ + enum replay_empty_commit_action *value = opt->value; + + BUG_ON_OPT_NEG(unset); + + if (!strcmp(arg, "drop")) + *value = REPLAY_EMPTY_COMMIT_DROP; + else if (!strcmp(arg, "keep")) + *value = REPLAY_EMPTY_COMMIT_KEEP; + else if (!strcmp(arg, "abort")) + *value = REPLAY_EMPTY_COMMIT_ABORT; + else + die(_("unrecognized '--empty=' action '%s'; " + "valid values are \"drop\", \"keep\", and \"abort\"."), arg); + + return 0; +} + +static int cmd_history_fixup(int argc, + const char **argv, + const char *prefix, + struct repository *repo) +{ + const char * const usage[] = { + GIT_HISTORY_FIXUP_USAGE, + NULL, + }; + enum replay_empty_commit_action empty = REPLAY_EMPTY_COMMIT_DROP; + enum ref_action action = REF_ACTION_DEFAULT; + enum commit_tree_flags flags = 0; + int dry_run = 0; + struct option options[] = { + OPT_CALLBACK_F(0, "update-refs", &action, "(branches|head)", + N_("control which refs should be updated"), + PARSE_OPT_NONEG, parse_ref_action), + OPT_BOOL('n', "dry-run", &dry_run, + N_("perform a dry-run without updating any refs")), + OPT_BIT(0, "reedit-message", &flags, + N_("open an editor to modify the commit message"), + COMMIT_TREE_EDIT_MESSAGE), + OPT_CALLBACK_F(0, "empty", &empty, "(drop|keep|abort)", + N_("how to handle commits that become empty"), + PARSE_OPT_NONEG, parse_opt_empty), + OPT_END(), + }; + struct merge_result merge_result = { 0 }; + struct merge_options merge_opts = { 0 }; + struct strbuf reflog_msg = STRBUF_INIT; + struct commit *head_commit, *original, *rewritten; + struct tree *head_tree, *original_tree, *index_tree; + struct rev_info revs = { 0 }; + bool skip_commit = false; + int ret; + + argc = parse_options(argc, argv, prefix, options, usage, 0); + if (argc != 1) { + ret = error(_("command expects a single revision")); + goto out; + } + repo_config(repo, git_default_config, NULL); + + if (action == REF_ACTION_DEFAULT) + action = REF_ACTION_BRANCHES; + + if (is_bare_repository()) { + ret = error(_("cannot run fixup in a bare repository")); + goto out; + } + + /* Resolve the original commit, which is the one we want to fix up. */ + original = lookup_commit_reference_by_name(argv[0]); + if (!original) { + ret = error(_("commit cannot be found: %s"), argv[0]); + goto out; + } + + /* + * Resolve HEAD so we can use its tree as the merge base: the staged + * changes are expressed as a diff from HEAD's tree to the index tree. + */ + head_commit = lookup_commit_reference_by_name("HEAD"); + if (!head_commit) { + ret = error(_("cannot look up HEAD")); + goto out; + } + + head_tree = repo_get_commit_tree(repo, head_commit); + if (!head_tree) { + ret = error(_("cannot get tree for HEAD")); + goto out; + } + + if (repo_read_index(repo) < 0) { + ret = error(_("unable to read index")); + goto out; + } + + if (!repo_index_has_changes(repo, head_tree, NULL)) { + ret = error(_("nothing to fixup: no staged changes")); + goto out; + } + + /* + * Write the index as a tree object. This is the "theirs" side of the + * three-way merge: it is HEAD's tree with the staged changes applied. + */ + index_tree = write_in_core_index_as_tree(repo, repo->index); + if (!index_tree) { + ret = error(_("unable to write index as a tree")); + goto out; + } + + original_tree = repo_get_commit_tree(repo, original); + if (!original_tree) { + ret = error(_("cannot get tree for commit %s"), argv[0]); + goto out; + } + + /* + * Perform the three-way merge to reapply changes in the index onto the + * target commit. This is using basically the same logic as a + * cherry-pick, where the base commit is our HEAD, ours is the original + * tree and theirs is the index tree. + */ + init_basic_merge_options(&merge_opts, repo); + merge_opts.ancestor = "HEAD"; + merge_opts.branch1 = argv[0]; + merge_opts.branch2 = "staged"; + merge_incore_nonrecursive(&merge_opts, head_tree, + original_tree, index_tree, &merge_result); + + if (merge_result.clean < 0) { + ret = error(_("merge failed while applying fixup")); + goto out; + } + + if (!merge_result.clean) { + ret = error(_("fixup would produce conflicts; aborting")); + goto out; + } + + ret = commit_became_empty(repo, original, merge_result.tree); + if (ret < 0) + goto out; + if (ret > 0) { + switch (empty) { + case REPLAY_EMPTY_COMMIT_DROP: + /* + * Drop the target commit by replaying its descendants + * directly onto its parent. + */ + rewritten = original->parents ? original->parents->item : NULL; + + /* + * TODO: we don't yet have the ability to drop root + * commits, but there's ultimately no good reason for + * this restriction to exist other than a technical + * limitation. + */ + if (!rewritten) { + ret = error(_("cannot drop root commit %s: " + "it has no parent to replay onto"), + argv[0]); + goto out; + } + + skip_commit = true; + break; + case REPLAY_EMPTY_COMMIT_KEEP: + /* Proceed and record the empty commit. */ + break; + case REPLAY_EMPTY_COMMIT_ABORT: + ret = error(_("fixup makes commit %s empty"), argv[0]); + goto out; + } + } + + ret = setup_revwalk(repo, action, original, &revs); + if (ret) + goto out; + + if (!skip_commit) { + ret = commit_tree_ext(repo, "fixup", original, original->parents, + &original_tree->object.oid, &merge_result.tree->object.oid, + &rewritten, flags); + if (ret < 0) { + ret = error(_("failed writing fixed-up commit")); + goto out; + } + } + + strbuf_addf(&reflog_msg, "fixup: updating %s", argv[0]); + + ret = handle_reference_updates(&revs, action, original, rewritten, + reflog_msg.buf, dry_run, empty); + if (ret < 0) { + ret = error(_("failed replaying descendants")); + goto out; + } + + ret = 0; + +out: + merge_finalize(&merge_opts, &merge_result); + strbuf_release(&reflog_msg); + release_revisions(&revs); + return ret; +} + static int cmd_history_reword(int argc, const char **argv, const char *prefix, @@ -478,7 +723,7 @@ static int cmd_history_reword(int argc, strbuf_addf(&reflog_msg, "reword: updating %s", argv[0]); ret = handle_reference_updates(&revs, action, original, rewritten, - reflog_msg.buf, dry_run); + reflog_msg.buf, dry_run, REPLAY_EMPTY_COMMIT_ABORT); if (ret < 0) { ret = error(_("failed replaying descendants")); goto out; @@ -616,9 +861,8 @@ static int split_commit(struct repository *repo, * The first commit is constructed from the split-out tree. The base * that shall be diffed against is the parent of the original commit. */ - ret = commit_tree_with_edited_message_ext(repo, "split-out", original, - original->parents, &parent_tree_oid, - &split_tree->object.oid, &first_commit); + ret = commit_tree_ext(repo, "split-out", original, original->parents, &parent_tree_oid, + &split_tree->object.oid, &first_commit, COMMIT_TREE_EDIT_MESSAGE); if (ret < 0) { ret = error(_("failed writing first commit")); goto out; @@ -634,9 +878,8 @@ static int split_commit(struct repository *repo, old_tree_oid = &repo_get_commit_tree(repo, first_commit)->object.oid; new_tree_oid = &repo_get_commit_tree(repo, original)->object.oid; - ret = commit_tree_with_edited_message_ext(repo, "split-out", original, - parents, old_tree_oid, - new_tree_oid, &second_commit); + ret = commit_tree_ext(repo, "split-out", original, parents, old_tree_oid, + new_tree_oid, &second_commit, COMMIT_TREE_EDIT_MESSAGE); if (ret < 0) { ret = error(_("failed writing second commit")); goto out; @@ -649,7 +892,7 @@ static int split_commit(struct repository *repo, if (index_file.len) unlink(index_file.buf); strbuf_release(&index_file); - free_commit_list(parents); + commit_list_free(parents); release_index(&index); return ret; } @@ -717,7 +960,7 @@ static int cmd_history_split(int argc, strbuf_addf(&reflog_msg, "split: updating %s", argv[0]); ret = handle_reference_updates(&revs, action, original, rewritten, - reflog_msg.buf, dry_run); + reflog_msg.buf, dry_run, REPLAY_EMPTY_COMMIT_ABORT); if (ret < 0) { ret = error(_("failed replaying descendants")); goto out; @@ -738,12 +981,14 @@ int cmd_history(int argc, struct repository *repo) { const char * const usage[] = { + GIT_HISTORY_FIXUP_USAGE, GIT_HISTORY_REWORD_USAGE, GIT_HISTORY_SPLIT_USAGE, NULL, }; parse_opt_subcommand_fn *fn = NULL; struct option options[] = { + OPT_SUBCOMMAND("fixup", &fn, cmd_history_fixup), OPT_SUBCOMMAND("reword", &fn, cmd_history_reword), OPT_SUBCOMMAND("split", &fn, cmd_history_split), OPT_END(),
diff --git a/builtin/hook.c b/builtin/hook.c index c058558..cceeb35 100644 --- a/builtin/hook.c +++ b/builtin/hook.c
@@ -4,23 +4,15 @@ #include "environment.h" #include "gettext.h" #include "hook.h" -#include "hook-list.h" #include "parse-options.h" +#include "thread-utils.h" #define BUILTIN_HOOK_RUN_USAGE \ - N_("git hook run [--allow-unknown-hook-name] [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>]") + N_("git hook run [--allow-unknown-hook-name] [--ignore-missing] [--to-stdin=<path>] [(-j|--jobs) <n>]\n" \ + "<hook-name> [-- <hook-args>]") #define BUILTIN_HOOK_LIST_USAGE \ N_("git hook list [--allow-unknown-hook-name] [-z] [--show-scope] <hook-name>") -static int is_known_hook(const char *name) -{ - const char **p; - for (p = hook_name_list; *p; p++) - if (!strcmp(*p, name)) - return 1; - return 0; -} - static const char * const builtin_hook_usage[] = { BUILTIN_HOOK_RUN_USAGE, BUILTIN_HOOK_LIST_USAGE, @@ -96,14 +88,22 @@ static int list(int argc, const char **argv, const char *prefix, const char *name = h->u.configured.friendly_name; const char *scope = show_scope ? config_scope_name(h->u.configured.scope) : NULL; + /* + * Show the most relevant disable reason. Event-level + * takes precedence: if the whole event is off, that + * is what the user needs to know. The per-hook + * "disabled" surfaces once the event is re-enabled. + */ + const char *disability = + h->u.configured.event_disabled ? "event-disabled\t" : + h->u.configured.disabled ? "disabled\t" : + ""; if (scope) - printf("%s\t%s%s%c", scope, - h->u.configured.disabled ? "disabled\t" : "", - name, line_terminator); + printf("%s\t%s%s%c", scope, disability, name, + line_terminator); else - printf("%s%s%c", - h->u.configured.disabled ? "disabled\t" : "", - name, line_terminator); + printf("%s%s%c", disability, name, + line_terminator); break; } default: @@ -124,6 +124,7 @@ static int run(int argc, const char **argv, const char *prefix, struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; int ignore_missing = 0; int allow_unknown = 0; + int jobs = 0; const char *hook_name; struct option run_options[] = { OPT_BOOL(0, "allow-unknown-hook-name", &allow_unknown, @@ -132,6 +133,8 @@ static int run(int argc, const char **argv, const char *prefix, N_("silently ignore missing requested <hook-name>")), OPT_STRING(0, "to-stdin", &opt.path_to_stdin, N_("path"), N_("file to read into hooks' stdin")), + OPT_INTEGER('j', "jobs", &jobs, + N_("run up to <n> hooks simultaneously (-1 for CPU count)")), OPT_END(), }; int ret; @@ -140,6 +143,15 @@ static int run(int argc, const char **argv, const char *prefix, builtin_hook_run_usage, PARSE_OPT_KEEP_DASHDASH); + if (jobs == -1) + opt.jobs = online_cpus(); + else if (jobs < 0) + die(_("invalid value for -j: %d" + " (use -1 for CPU count or a" + " positive integer)"), jobs); + else + opt.jobs = jobs; + if (!argc) goto usage;
diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 2e4b42f..0793dc5 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c
@@ -145,8 +145,7 @@ static int check_self_contained_and_connected; static struct progress *progress; -/* We always read in 4kB chunks. */ -static unsigned char input_buffer[4096]; +static unsigned char input_buffer[DEFAULT_IO_BUFFER_SIZE]; static unsigned int input_offset, input_len; static off_t consumed_bytes; static off_t max_input_size; @@ -1213,7 +1212,6 @@ static void *threaded_second_pass(void *data) list_add(&child->list, &work_head); base_cache_used += child->size; prune_base_data(NULL); - free_base_data(child); } else if (child) { /* * This child does not have its own children. It may be @@ -1418,8 +1416,9 @@ static int write_compressed(struct hashfile *f, void *in, unsigned int size) git_zstream stream; int status; unsigned char outbuf[4096]; + struct repo_config_values *cfg = repo_config_values(the_repository); - git_deflate_init(&stream, zlib_compression_level); + git_deflate_init(&stream, cfg->zlib_compression_level); stream.next_in = in; stream.avail_in = size;
diff --git a/builtin/init-db.c b/builtin/init-db.c index bb853e6..c55517a 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c
@@ -237,9 +237,9 @@ int cmd_init_db(int argc, if (!git_work_tree_cfg) git_work_tree_cfg = xgetcwd(); if (work_tree) - set_git_work_tree(work_tree); + set_git_work_tree(the_repository, work_tree); else - set_git_work_tree(git_work_tree_cfg); + set_git_work_tree(the_repository, git_work_tree_cfg); if (access(repo_get_work_tree(the_repository), X_OK)) die_errno (_("Cannot access work tree '%s'"), repo_get_work_tree(the_repository)); @@ -248,11 +248,11 @@ int cmd_init_db(int argc, if (real_git_dir) die(_("--separate-git-dir incompatible with bare repository")); if (work_tree) - set_git_work_tree(work_tree); + set_git_work_tree(the_repository, work_tree); } flags |= INIT_DB_EXIST_OK; - ret = init_db(git_dir, real_git_dir, template_dir, hash_algo, + ret = init_db(the_repository, git_dir, real_git_dir, template_dir, hash_algo, ref_storage_format, initial_branch, init_shared_repository, flags);
diff --git a/builtin/log.c b/builtin/log.c index 8c0939d..e464b30 100644 --- a/builtin/log.c +++ b/builtin/log.c
@@ -21,10 +21,12 @@ #include "color.h" #include "commit.h" #include "diff.h" +#include "diffcore.h" #include "diff-merges.h" #include "revision.h" #include "log-tree.h" #include "oid-array.h" +#include "oidset.h" #include "tag.h" #include "reflog-walk.h" #include "patch-ids.h" @@ -43,9 +45,11 @@ #include "utf8.h" #include "commit-reach.h" +#include "promisor-remote.h" #include "range-diff.h" #include "tmp-objdir.h" #include "tree.h" +#include "userdiff.h" #include "write-or-die.h" #define MAIL_DEFAULT_WRAP 72 @@ -2602,6 +2606,131 @@ static void print_commit(char sign, struct commit *commit, int verbose, } } +/* + * Enumerate blob OIDs from a single commit's diff, inserting them into blobs. + * Skips files whose userdiff driver explicitly declares binary status + * (drv->binary > 0), since patch-ID uses oid_to_hex() for those and + * never reads blob content. Use userdiff_find_by_path() since + * diff_filespec_load_driver() is static in diff.c. + * + * Clean up with diff_queue_clear() (from diffcore.h). + */ +static void collect_diff_blob_oids(struct commit *commit, + struct diff_options *opts, + struct oidset *blobs) +{ + struct diff_queue_struct *q; + + /* + * Merge commits are filtered out by patch_id_defined() in patch-ids.c, + * so we'll never be called with one. + */ + assert(!commit->parents || !commit->parents->next); + + if (commit->parents) + diff_tree_oid(&commit->parents->item->object.oid, + &commit->object.oid, "", opts); + else + diff_root_tree_oid(&commit->object.oid, "", opts); + diffcore_std(opts); + + q = &diff_queued_diff; + for (int i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + struct userdiff_driver *drv; + + /* Skip binary files */ + drv = userdiff_find_by_path(opts->repo->index, p->one->path); + if (drv && drv->binary > 0) + continue; + + if (DIFF_FILE_VALID(p->one) && + odb_read_object_info_extended(opts->repo->objects, + &p->one->oid, NULL, + OBJECT_INFO_FOR_PREFETCH)) + oidset_insert(blobs, &p->one->oid); + if (DIFF_FILE_VALID(p->two) && + odb_read_object_info_extended(opts->repo->objects, + &p->two->oid, NULL, + OBJECT_INFO_FOR_PREFETCH)) + oidset_insert(blobs, &p->two->oid); + } + diff_queue_clear(q); +} + +static int always_match(const void *cmp_data UNUSED, + const struct hashmap_entry *entry1 UNUSED, + const struct hashmap_entry *entry2 UNUSED, + const void *keydata UNUSED) +{ + return 0; +} + +/* + * Prefetch blobs for git cherry in partial clones. + * + * Called between the revision walk (which builds the head-side + * commit list) and the has_commit_patch_id() comparison loop. + * + * Uses a cmpfn-swap trick to avoid reading blobs: temporarily + * replaces the hashmap's comparison function with a trivial + * always-match function, so hashmap_get()/hashmap_get_next() match + * any entry with the same oidhash bucket. These are the set of oids + * that would trigger patch_id_neq() during normal lookup and cause + * blobs to be read on demand, and we want to prefetch them all at + * once instead. + */ +static void prefetch_cherry_blobs(struct repository *repo, + struct commit_list *list, + struct patch_ids *ids) +{ + struct oidset blobs = OIDSET_INIT; + hashmap_cmp_fn original_cmpfn; + + /* Exit if we're not in a partial clone */ + if (!repo_has_promisor_remote(repo)) + return; + + /* Save original cmpfn, replace with always_match */ + original_cmpfn = ids->patches.cmpfn; + ids->patches.cmpfn = always_match; + + /* Find header-only collisions, gather blobs from those commits */ + for (struct commit_list *l = list; l; l = l->next) { + struct commit *c = l->item; + bool match_found = false; + for (struct patch_id *cur = patch_id_iter_first(c, ids); + cur; + cur = patch_id_iter_next(cur, ids)) { + match_found = true; + collect_diff_blob_oids(cur->commit, &ids->diffopts, + &blobs); + } + if (match_found) + collect_diff_blob_oids(c, &ids->diffopts, &blobs); + } + + /* Restore original cmpfn */ + ids->patches.cmpfn = original_cmpfn; + + /* If we have any blobs to fetch, fetch them */ + if (oidset_size(&blobs)) { + struct oid_array to_fetch = OID_ARRAY_INIT; + struct oidset_iter iter; + const struct object_id *oid; + + oidset_iter_init(&blobs, &iter); + while ((oid = oidset_iter_next(&iter))) + oid_array_append(&to_fetch, oid); + + promisor_remote_get_direct(repo, to_fetch.oid, to_fetch.nr); + + oid_array_clear(&to_fetch); + } + + oidset_clear(&blobs); +} + int cmd_cherry(int argc, const char **argv, const char *prefix, @@ -2673,6 +2802,8 @@ int cmd_cherry(int argc, commit_list_insert(commit, &list); } + prefetch_cherry_blobs(the_repository, list, &ids); + for (struct commit_list *l = list; l; l = l->next) { char sign = '+';
diff --git a/builtin/ls-files.c b/builtin/ls-files.c index b148607..99e98fc 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c
@@ -250,20 +250,23 @@ static void expand_objectsize(struct repository *repo, struct strbuf *line, const struct object_id *oid, const enum object_type type, unsigned int padded) { + static const char padding[] = " "; + size_t min_len = padded ? strlen(padding) : 0; + size_t orig_len = line->len; + size_t len; + if (type == OBJ_BLOB) { unsigned long size; if (odb_read_object_info(repo->objects, oid, &size) < 0) die(_("could not get object info about '%s'"), oid_to_hex(oid)); - if (padded) - strbuf_addf(line, "%7"PRIuMAX, (uintmax_t)size); - else - strbuf_addf(line, "%"PRIuMAX, (uintmax_t)size); - } else if (padded) { - strbuf_addf(line, "%7s", "-"); + strbuf_add_uint(line, size); } else { strbuf_addstr(line, "-"); } + len = line->len - orig_len; + if (len < min_len) + strbuf_insert(line, orig_len, padding, min_len - len); } static void show_ce_fmt(struct repository *repo, const struct cache_entry *ce, @@ -450,6 +453,17 @@ static void show_files(struct repository *repo, struct dir_struct *dir) continue; if (ce_skip_worktree(ce)) continue; + /* + * match_pathspec() is linear in pathspec.nr, so prefilter only + * the single-pathspec case. Only entries shown by show_ce() + * satisfy --error-unmatch. + */ + if (pathspec.nr == 1 && + !match_pathspec(repo->index, &pathspec, fullname.buf, + fullname.len, max_prefix_len, NULL, + S_ISDIR(ce->ce_mode) || + S_ISGITLINK(ce->ce_mode))) + continue; stat_err = lstat(fullname.buf, &st); if (stat_err && (errno != ENOENT && errno != ENOTDIR)) error_errno("cannot lstat '%s'", fullname.buf); @@ -703,8 +717,8 @@ int cmd_ls_files(int argc, if (dir.exclude_per_dir) exc_given = 1; - if (require_work_tree && !is_inside_work_tree()) - setup_work_tree(); + if (require_work_tree && !is_inside_work_tree(repo)) + setup_work_tree(repo); if (recurse_submodules && (show_deleted || show_others || show_unmerged ||
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 113e4a9..5784691 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c
@@ -26,20 +26,23 @@ static const char * const ls_tree_usage[] = { static void expand_objectsize(struct strbuf *line, const struct object_id *oid, const enum object_type type, unsigned int padded) { + static const char padding[] = " "; + size_t min_len = padded ? strlen(padding) : 0; + size_t orig_len = line->len; + size_t len; + if (type == OBJ_BLOB) { unsigned long size; if (odb_read_object_info(the_repository->objects, oid, &size) < 0) die(_("could not get object info about '%s'"), oid_to_hex(oid)); - if (padded) - strbuf_addf(line, "%7"PRIuMAX, (uintmax_t)size); - else - strbuf_addf(line, "%"PRIuMAX, (uintmax_t)size); - } else if (padded) { - strbuf_addf(line, "%7s", "-"); + strbuf_add_uint(line, size); } else { strbuf_addstr(line, "-"); } + len = line->len - orig_len; + if (len < min_len) + strbuf_insert(line, orig_len, padding, min_len - len); } struct ls_tree_options {
diff --git a/builtin/merge-base.c b/builtin/merge-base.c index c7ee97f..a87011c 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c
@@ -11,10 +11,12 @@ static int show_merge_base(struct commit **rev, size_t rev_nr, int show_all) { + enum merge_base_flags flags = show_all ? MERGE_BASE_FIND_ALL : 0; struct commit_list *result = NULL, *r; if (repo_get_merge_bases_many_dirty(the_repository, rev[0], - rev_nr - 1, rev + 1, &result) < 0) { + rev_nr - 1, rev + 1, + flags, &result) < 0) { commit_list_free(result); return -1; }
diff --git a/builtin/merge-file.c b/builtin/merge-file.c index 59a9792..8fa5765 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c
@@ -110,7 +110,7 @@ int cmd_merge_file(int argc, if (!repo && object_id) /* emit the correct "not a git repo" error in this case */ - setup_git_directory(); + setup_git_directory(the_repository); for (i = 0; i < 3; i++) { char *fname;
diff --git a/builtin/merge.c b/builtin/merge.c index 2cbce56..5b46a59 100644 --- a/builtin/merge.c +++ b/builtin/merge.c
@@ -537,7 +537,8 @@ static void finish(struct commit *head_commit, run_hooks_l(the_repository, "post-merge", squash ? "1" : "0", NULL); if (new_head) - apply_autostash_ref(the_repository, "MERGE_AUTOSTASH"); + apply_autostash_ref(the_repository, "MERGE_AUTOSTASH", + NULL, NULL, NULL, NULL); strbuf_release(&reflog_message); } @@ -1672,12 +1673,14 @@ int cmd_merge(int argc, } if (autostash) - create_autostash_ref(the_repository, "MERGE_AUTOSTASH"); + create_autostash_ref(the_repository, "MERGE_AUTOSTASH", + NULL, false); if (checkout_fast_forward(the_repository, &head_commit->object.oid, &commit->object.oid, overwrite_ignore)) { - apply_autostash_ref(the_repository, "MERGE_AUTOSTASH"); + apply_autostash_ref(the_repository, "MERGE_AUTOSTASH", + NULL, NULL, NULL, NULL); ret = 1; goto done; } @@ -1735,21 +1738,11 @@ int cmd_merge(int argc, struct commit_list *j; for (j = remoteheads; j; j = j->next) { - struct commit_list *common_one = NULL; - struct commit *common_item; - - /* - * Here we *have* to calculate the individual - * merge_bases again, otherwise "git merge HEAD^ - * HEAD^^" would be missed. - */ - if (repo_get_merge_bases(the_repository, head_commit, - j->item, &common_one) < 0) + int ret = repo_in_merge_bases(the_repository, + j->item, head_commit); + if (ret < 0) exit(128); - - common_item = common_one->item; - commit_list_free(common_one); - if (!oideq(&common_item->object.oid, &j->item->object.oid)) { + if (!ret) { up_to_date = 0; break; } @@ -1764,7 +1757,8 @@ int cmd_merge(int argc, die_ff_impossible(); if (autostash) - create_autostash_ref(the_repository, "MERGE_AUTOSTASH"); + create_autostash_ref(the_repository, "MERGE_AUTOSTASH", + NULL, false); /* We are going to make a new commit. */ git_committer_info(IDENT_STRICT); @@ -1849,7 +1843,8 @@ int cmd_merge(int argc, else fprintf(stderr, _("Merge with strategy %s failed.\n"), use_strategies[0]->name); - apply_autostash_ref(the_repository, "MERGE_AUTOSTASH"); + apply_autostash_ref(the_repository, "MERGE_AUTOSTASH", + NULL, NULL, NULL, NULL); ret = 2; goto done; } else if (best_strategy == wt_strategy)
diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c index 0f72d96..00ffb36 100644 --- a/builtin/multi-pack-index.c +++ b/builtin/multi-pack-index.c
@@ -16,11 +16,13 @@ #define BUILTIN_MIDX_WRITE_USAGE \ N_("git multi-pack-index [<options>] write [--preferred-pack=<pack>]\n" \ " [--[no-]bitmap] [--[no-]incremental] [--[no-]stdin-packs]\n" \ - " [--refs-snapshot=<path>]") + " [--refs-snapshot=<path>] [--[no-]write-chain-file]\n" \ + " [--base=<checksum>]") #define BUILTIN_MIDX_COMPACT_USAGE \ N_("git multi-pack-index [<options>] compact [--[no-]incremental]\n" \ - " [--[no-]bitmap] <from> <to>") + " [--[no-]bitmap] [--base=<checksum>] [--[no-]write-chain-file]\n" \ + " <from> <to>") #define BUILTIN_MIDX_VERIFY_USAGE \ N_("git multi-pack-index [<options>] verify") @@ -63,6 +65,7 @@ static char const * const builtin_multi_pack_index_usage[] = { static struct opts_multi_pack_index { char *object_dir; const char *preferred_pack; + const char *incremental_base; char *refs_snapshot; unsigned long batch_size; unsigned flags; @@ -151,8 +154,13 @@ static int cmd_multi_pack_index_write(int argc, const char **argv, N_("pack for reuse when computing a multi-pack bitmap")), OPT_BIT(0, "bitmap", &opts.flags, N_("write multi-pack bitmap"), MIDX_WRITE_BITMAP | MIDX_WRITE_REV_INDEX), + OPT_STRING(0, "base", &opts.incremental_base, N_("checksum"), + N_("base MIDX for incremental writes")), OPT_BIT(0, "incremental", &opts.flags, N_("write a new incremental MIDX"), MIDX_WRITE_INCREMENTAL), + OPT_NEGBIT(0, "write-chain-file", &opts.flags, + N_("write the multi-pack-index chain file"), + MIDX_WRITE_NO_CHAIN), OPT_BOOL(0, "stdin-packs", &opts.stdin_packs, N_("write multi-pack index containing only given indexes")), OPT_FILENAME(0, "refs-snapshot", &opts.refs_snapshot, @@ -178,6 +186,22 @@ static int cmd_multi_pack_index_write(int argc, const char **argv, if (argc) usage_with_options(builtin_multi_pack_index_write_usage, options); + + if (opts.flags & MIDX_WRITE_NO_CHAIN && + !(opts.flags & MIDX_WRITE_INCREMENTAL)) { + error(_("cannot use %s without %s"), + "--no-write-chain-file", "--incremental"); + usage_with_options(builtin_multi_pack_index_write_usage, + options); + } + + if (opts.incremental_base && + !(opts.flags & MIDX_WRITE_NO_CHAIN)) { + error(_("cannot use --base without --no-write-chain-file")); + usage_with_options(builtin_multi_pack_index_write_usage, + options); + } + source = handle_object_dir_option(repo); FREE_AND_NULL(options); @@ -189,7 +213,8 @@ static int cmd_multi_pack_index_write(int argc, const char **argv, ret = write_midx_file_only(source, &packs, opts.preferred_pack, - opts.refs_snapshot, opts.flags); + opts.refs_snapshot, + opts.incremental_base, opts.flags); string_list_clear(&packs, 0); free(opts.refs_snapshot); @@ -217,10 +242,15 @@ static int cmd_multi_pack_index_compact(int argc, const char **argv, struct option *options; static struct option builtin_multi_pack_index_compact_options[] = { + OPT_STRING(0, "base", &opts.incremental_base, N_("checksum"), + N_("base MIDX for incremental writes")), OPT_BIT(0, "bitmap", &opts.flags, N_("write multi-pack bitmap"), MIDX_WRITE_BITMAP | MIDX_WRITE_REV_INDEX), OPT_BIT(0, "incremental", &opts.flags, N_("write a new incremental MIDX"), MIDX_WRITE_INCREMENTAL), + OPT_NEGBIT(0, "write-chain-file", &opts.flags, + N_("write the multi-pack-index chain file"), + MIDX_WRITE_NO_CHAIN), OPT_END(), }; @@ -239,6 +269,15 @@ static int cmd_multi_pack_index_compact(int argc, const char **argv, if (argc != 2) usage_with_options(builtin_multi_pack_index_compact_usage, options); + + if (opts.flags & MIDX_WRITE_NO_CHAIN && + !(opts.flags & MIDX_WRITE_INCREMENTAL)) { + error(_("cannot use %s without %s"), + "--no-write-chain-file", "--incremental"); + usage_with_options(builtin_multi_pack_index_compact_usage, + options); + } + source = handle_object_dir_option(the_repository); FREE_AND_NULL(options); @@ -266,7 +305,8 @@ static int cmd_multi_pack_index_compact(int argc, const char **argv, die(_("MIDX %s must be an ancestor of %s"), argv[0], argv[1]); } - ret = write_midx_file_compact(source, from_midx, to_midx, opts.flags); + ret = write_midx_file_compact(source, from_midx, to_midx, + opts.incremental_base, opts.flags); return ret; }
diff --git a/builtin/mv.c b/builtin/mv.c index 2215d34..e038233 100644 --- a/builtin/mv.c +++ b/builtin/mv.c
@@ -71,7 +71,7 @@ static void internal_prefix_pathspec(struct strvec *out, trimmed = xmemdupz(pathspec[i], to_copy); maybe_basename = (flags & DUP_BASENAME) ? basename(trimmed) : trimmed; - prefixed_path = prefix_path(prefix, prefixlen, maybe_basename); + prefixed_path = prefix_path(the_repository, prefix, prefixlen, maybe_basename); strvec_push(out, prefixed_path); free(prefixed_path); @@ -394,7 +394,8 @@ int cmd_mv(int argc, for (j = 0; j < last - first; j++) { const struct cache_entry *ce = the_repository->index->cache[first + j]; const char *path = ce->name; - char *prefixed_path = prefix_path(dst_with_slash, dst_with_slash_len, path + length + 1); + char *prefixed_path = prefix_path(the_repository, dst_with_slash, + dst_with_slash_len, path + length + 1); strvec_push(&sources, path); strvec_push(&destinations, prefixed_path); @@ -574,7 +575,7 @@ int cmd_mv(int argc, if (ignore_sparse && cfg->apply_sparse_checkout && - core_sparse_checkout_cone) { + cfg->core_sparse_checkout_cone) { /* * NEEDSWORK: we are *not* paying attention to * "out-to-out" move (<source> is out-of-cone and
diff --git a/builtin/name-rev.c b/builtin/name-rev.c index d6594ad..60cbbfb 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c
@@ -17,6 +17,10 @@ #include "commit-graph.h" #include "wildmatch.h" #include "mem-pool.h" +#include "pretty.h" +#include "revision.h" +#include "notes.h" +#include "write-or-die.h" /* * One day. See the 'name a rev shortly after epoch' test in t6120 when @@ -270,6 +274,43 @@ struct name_ref_data { struct string_list exclude_filters; }; +struct pretty_format { + struct pretty_print_context ctx; + struct userformat_want want; +}; + +enum command_type { + NAME_REV = 1, + FORMAT_REV = 2, +}; + +enum stdin_mode { + TEXT = 1, + REVS = 2, +}; + +struct command { + enum command_type type; + union { + int name_only; + struct pretty_format *pretty_format; + } u; +}; + +static void init_name_rev_command(struct command *cmd, + int name_only) +{ + cmd->type = NAME_REV; + cmd->u.name_only = name_only; +} + +static void init_format_rev_command(struct command *cmd, + struct pretty_format *pretty_format) +{ + cmd->type = FORMAT_REV; + cmd->u.pretty_format = pretty_format; +} + static struct tip_table { struct tip_table_entry { struct object_id oid; @@ -464,9 +505,9 @@ static const char *get_rev_name(const struct object *o, struct strbuf *buf) if (!n) return NULL; - if (!n->generation) + if (!n->generation) { return n->tip_name; - else { + } else { strbuf_reset(buf); strbuf_addstr(buf, n->tip_name); strbuf_strip_suffix(buf, "^0"); @@ -475,6 +516,27 @@ static const char *get_rev_name(const struct object *o, struct strbuf *buf) } } +static const char *get_format_rev(const struct commit *c, + struct pretty_format *format_ctx, + struct strbuf *buf) +{ + strbuf_reset(buf); + + if (format_ctx->want.notes) { + struct strbuf notebuf = STRBUF_INIT; + + format_display_notes(&c->object.oid, ¬ebuf, + get_log_output_encoding(), + format_ctx->ctx.fmt == CMIT_FMT_USERFORMAT); + format_ctx->ctx.notes_message = strbuf_detach(¬ebuf, NULL); + } + + pretty_print_commit(&format_ctx->ctx, c, buf); + FREE_AND_NULL(format_ctx->ctx.notes_message); + + return buf->buf; +} + static void show_name(const struct object *obj, const char *caller_name, int always, int allow_undefined, int name_only) @@ -505,7 +567,7 @@ static char const * const name_rev_usage[] = { NULL }; -static void name_rev_line(char *p, struct name_ref_data *data) +static void name_rev_line(char *p, struct command *cmd) { struct strbuf buf = STRBUF_INIT; int counter = 0; @@ -514,33 +576,52 @@ static void name_rev_line(char *p, struct name_ref_data *data) for (p_start = p; *p; p++) { #define ishex(x) (isdigit((x)) || ((x) >= 'a' && (x) <= 'f')) - if (!ishex(*p)) + if (!ishex(*p)) { counter = 0; - else if (++counter == hexsz && - !ishex(*(p+1))) { + } else if (++counter == hexsz && + !ishex(*(p + 1))) { struct object_id oid; const char *name = NULL; - char c = *(p+1); + char c = *(p + 1); int p_len = p - p_start + 1; + struct object *o = NULL; + int oid_ret = 1; counter = 0; - *(p+1) = 0; - if (!repo_get_oid(the_repository, p - (hexsz - 1), &oid)) { - struct object *o = - lookup_object(the_repository, &oid); + *(p + 1) = 0; + oid_ret = repo_get_oid(the_repository, p - (hexsz - 1), &oid); + *(p + 1) = c; + + switch (cmd->type) { + case NAME_REV: + if (!oid_ret) + o = lookup_object(the_repository, &oid); if (o) name = get_rev_name(o, &buf); + if (!name) + continue; + if (cmd->u.name_only) + printf("%.*s%s", p_len - hexsz, p_start, name); + else + printf("%.*s (%s)", p_len, p_start, name); + break; + case FORMAT_REV: + if (!oid_ret) + o = parse_object(the_repository, &oid); + if (o && o->type == OBJ_COMMIT) + name = get_format_rev((const struct commit *)o, + cmd->u.pretty_format, + &buf); + if (name) + printf("%.*s%s", p_len - hexsz, p_start, name); + else + printf("%.*s", p_len, p_start); + break; + default: + BUG("uncovered case: %d", cmd->type); } - *(p+1) = c; - if (!name) - continue; - - if (data->name_only) - printf("%.*s%s", p_len - hexsz, p_start, name); - else - printf("%.*s (%s)", p_len, p_start, name); p_start = p + 1; } } @@ -565,13 +646,14 @@ int cmd_name_rev(int argc, #endif int all = 0, annotate_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0; struct name_ref_data data = { 0, 0, STRING_LIST_INIT_NODUP, STRING_LIST_INIT_NODUP }; + struct command cmd; struct option opts[] = { OPT_BOOL(0, "name-only", &data.name_only, N_("print only ref-based names (no object names)")), OPT_BOOL(0, "tags", &data.tags_only, N_("only use tags to name the commits")), OPT_STRING_LIST(0, "refs", &data.ref_filters, N_("pattern"), - N_("only use refs matching <pattern>")), + N_("only use refs matching <pattern>")), OPT_STRING_LIST(0, "exclude", &data.exclude_filters, N_("pattern"), - N_("ignore refs matching <pattern>")), + N_("ignore refs matching <pattern>")), OPT_GROUP(""), OPT_BOOL(0, "all", &all, N_("list all commits reachable from all refs")), #ifndef WITH_BREAKING_CHANGES @@ -583,10 +665,10 @@ int cmd_name_rev(int argc, #endif /* WITH_BREAKING_CHANGES */ OPT_BOOL(0, "annotate-stdin", &annotate_stdin, N_("annotate text from stdin")), OPT_BOOL(0, "undefined", &allow_undefined, N_("allow to print `undefined` names (default)")), - OPT_BOOL(0, "always", &always, - N_("show abbreviated commit object as fallback")), + OPT_BOOL(0, "always", &always, + N_("show abbreviated commit object as fallback")), OPT_HIDDEN_BOOL(0, "peel-tag", &peel_tag, - N_("dereference tags in the input (internal use)")), + N_("dereference tags in the input (internal use)")), OPT_END(), }; @@ -594,6 +676,7 @@ int cmd_name_rev(int argc, init_commit_rev_name(&rev_names); repo_config(the_repository, git_default_config, NULL); argc = parse_options(argc, argv, prefix, opts, name_rev_usage, 0); + init_name_rev_command(&cmd, data.name_only); #ifndef WITH_BREAKING_CHANGES if (transform_stdin) { @@ -661,7 +744,7 @@ int cmd_name_rev(int argc, while (strbuf_getline(&sb, stdin) != EOF) { strbuf_addch(&sb, '\n'); - name_rev_line(sb.buf, &data); + name_rev_line(sb.buf, &cmd); } strbuf_release(&sb); } else if (all) { @@ -688,3 +771,170 @@ int cmd_name_rev(int argc, object_array_clear(&revs); return 0; } + +struct format_nul_data { + bool nul_input; + bool nul_output; +}; + +static int format_nul_cb(const struct option *option, + const char *arg, + int unset) +{ + struct format_nul_data *data = option->value; + data->nul_input = 1; + data->nul_output = 1; + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); + return 0; +} + +static enum stdin_mode parse_stdin_mode(const char *stdin_mode) +{ + if (!strcmp(stdin_mode, "text")) + return TEXT; + else if (!strcmp(stdin_mode, "revs") || + !strcmp(stdin_mode, "rev")) + return REVS; + else + die(_("'%s' needs to be either text, revs, or rev"), + "--stdin-mode"); +} + +static char const *const format_rev_usage[] = { + N_("(EXPERIMENTAL!) git format-rev --stdin-mode=<mode> " + "--format=<pretty> [--[no-]notes=<ref>] " + "[-z] [--[no-]null-output] [--[no-]null-input]"), + NULL +}; + +int cmd_format_rev(int argc, + const char **argv, + const char *prefix, + struct repository *repo UNUSED) +{ + const char *format = NULL; + enum stdin_mode stdin_mode; + const char *stdin_mode_arg = NULL; + struct format_nul_data nul_data = { 0, 0 }; + char output_terminator; + strbuf_getline_fn getline_fn; + struct display_notes_opt format_notes_opt; + struct rev_info format_rev = REV_INFO_INIT; + struct pretty_format format_pp = { 0 }; + struct string_list notes = STRING_LIST_INIT_NODUP; + struct strbuf scratch_buf = STRBUF_INIT; + struct command cmd; + struct option opts[] = { + OPT_STRING(0, "format", &format, N_("format"), + N_("pretty format to use")), + OPT_STRING(0, "stdin-mode", &stdin_mode_arg, N_("stdin-mode"), + N_("how revs are processed")), + OPT_STRING_LIST(0, "notes", ¬es, N_("notes"), + N_("display notes for pretty format")), + OPT_CALLBACK_F('z', "null", &nul_data, N_("z"), + N_("Use NUL for input and output termination"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, format_nul_cb), + OPT_BOOL(0, "null-input", &nul_data.nul_input, + N_("Use NUL for input termination")), + OPT_BOOL(0, "null-output", &nul_data.nul_output, + N_("Use NUL for output termination")), + OPT_END(), + }; + + argc = parse_options(argc, argv, prefix, opts, format_rev_usage, 0); + + if (argc > 0) { + error(_("too many arguments")); + usage_with_options(format_rev_usage, opts); + } + + if (!format) + die(_("'%s' is required"), "--format"); + if (!stdin_mode_arg) + die(_("'%s' is required"), "--stdin-mode"); + + getline_fn = nul_data.nul_input ? strbuf_getline_nul : strbuf_getline_lf; + output_terminator = nul_data.nul_output ? '\0' : '\n'; + + init_display_notes(&format_notes_opt); + stdin_mode = parse_stdin_mode(stdin_mode_arg); + + get_commit_format(format, &format_rev); + format_pp.ctx.rev = &format_rev; + format_pp.ctx.fmt = format_rev.commit_format; + format_pp.ctx.abbrev = format_rev.abbrev; + format_pp.ctx.date_mode_explicit = format_rev.date_mode_explicit; + format_pp.ctx.date_mode = format_rev.date_mode; + format_pp.ctx.color = GIT_COLOR_AUTO; + + userformat_find_requirements(format, + &format_pp.want); + if (format_pp.want.notes) { + int ignore_show_notes = 0; + struct string_list_item *n; + + for_each_string_list_item(n, ¬es) + enable_ref_display_notes(&format_notes_opt, + &ignore_show_notes, + n->string); + load_display_notes(&format_notes_opt); + } + + init_format_rev_command(&cmd, &format_pp); + + switch (stdin_mode) { + case TEXT: + while (getline_fn(&scratch_buf, stdin) != EOF) { + name_rev_line(scratch_buf.buf, &cmd); + /* + * We do not pass on the terminator to name_rev_line, + * unlike name-rev. + */ + printf("%c", output_terminator); + maybe_flush_or_die(stdout, "stdout"); + } + break; + case REVS: + while (getline_fn(&scratch_buf, stdin) != EOF) { + struct object_id oid; + struct object *object; + struct object *peeled; + + if (repo_get_oid(the_repository, scratch_buf.buf, &oid)) { + fprintf(stderr, "Could not get object name for %s. Skipping.\n", + scratch_buf.buf); + continue; + } + + object = parse_object(the_repository, &oid); + if (!object) { + fprintf(stderr, "Could not get object for %s. Skipping.\n", + scratch_buf.buf); + continue; + } + + peeled = deref_tag(the_repository, object, scratch_buf.buf, 0); + if (!peeled || peeled->type != OBJ_COMMIT) { + fprintf(stderr, + "Could not get commit for %s. Skipping.\n", + scratch_buf.buf); + continue; + } + + get_format_rev((struct commit *)peeled, + &format_pp, &scratch_buf); + printf("%s%c", scratch_buf.buf, output_terminator); + maybe_flush_or_die(stdout, "stdout"); + strbuf_release(&scratch_buf); + } + break; + default: + BUG("uncovered case: %d", stdin_mode); + } + + strbuf_release(&scratch_buf); + string_list_clear(¬es, 0); + release_display_notes(&format_notes_opt); + return 0; +}
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 480cc0b..8a1709a 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c
@@ -386,8 +386,9 @@ static unsigned long do_compress(void **pptr, unsigned long size) git_zstream stream; void *in, *out; unsigned long maxsize; + struct repo_config_values *cfg = repo_config_values(the_repository); - git_deflate_init(&stream, pack_compression_level); + git_deflate_init(&stream, cfg->pack_compression_level); maxsize = git_deflate_bound(&stream, size); in = *pptr; @@ -413,8 +414,9 @@ static unsigned long write_large_blob_data(struct odb_read_stream *st, struct ha unsigned char ibuf[1024 * 16]; unsigned char obuf[1024 * 16]; unsigned long olen = 0; + struct repo_config_values *cfg = repo_config_values(the_repository); - git_deflate_init(&stream, pack_compression_level); + git_deflate_init(&stream, cfg->pack_compression_level); for (;;) { ssize_t readlen; @@ -1349,7 +1351,7 @@ static void write_pack_file(void) * length of them as buffer length. * * Note that we need to subtract one though to - * accomodate for the sideband byte. + * accommodate for the sideband byte. */ struct hashfd_options opts = { .progress = progress_state, @@ -1750,9 +1752,11 @@ static int want_object_in_pack_mtime(const struct object_id *oid, * skip the local object source. */ struct odb_source *source = the_repository->objects->sources->next; - for (; source; source = source->next) - if (odb_source_loose_has_object(source, oid)) + for (; source; source = source->next) { + struct odb_source_files *files = odb_source_files_downcast(source); + if (!odb_source_read_object_info(&files->loose->base, oid, NULL, 0)) return 0; + } } /* @@ -4135,9 +4139,11 @@ static void add_cruft_object_entry(const struct object_id *oid, enum object_type struct odb_source *source = the_repository->objects->sources; int found = 0; - for (; !found && source; source = source->next) - if (odb_source_loose_has_object(source, oid)) + for (; !found && source; source = source->next) { + struct odb_source_files *files = odb_source_files_downcast(source); + if (!odb_source_read_object_info(&files->loose->base, oid, NULL, 0)) found = 1; + } /* * If a traversed tree has a missing blob then we want @@ -4275,6 +4281,7 @@ static void enumerate_and_traverse_cruft_objects(struct string_list *fresh_packs traverse_commit_list(&revs, show_cruft_commit, show_cruft_object, NULL); stop_progress(&progress_state); + release_revisions(&revs); } static void read_cruft_objects(void) @@ -4764,7 +4771,7 @@ static int add_objects_by_path(const char *path, return 0; } -static void get_object_list_path_walk(struct rev_info *revs) +static int get_object_list_path_walk(struct rev_info *revs) { struct path_walk_info info = PATH_WALK_INFO_INIT; unsigned int processed = 0; @@ -4787,8 +4794,9 @@ static void get_object_list_path_walk(struct rev_info *revs) result = walk_objects_by_path(&info); trace2_region_leave("pack-objects", "path-walk", revs->repo); - if (result) - die(_("failed to pack objects via path-walk")); + path_walk_info_clear(&info); + + return result; } static void get_object_list(struct rev_info *revs, struct strvec *argv) @@ -4796,6 +4804,7 @@ static void get_object_list(struct rev_info *revs, struct strvec *argv) struct setup_revision_opt s_r_opt = { .allow_exclude_promisor_objects = 1, }; + struct repo_config_values *cfg = repo_config_values(the_repository); char line[1000]; int flags = 0; int save_warning; @@ -4806,8 +4815,8 @@ static void get_object_list(struct rev_info *revs, struct strvec *argv) /* make sure shallows are read */ is_repository_shallow(the_repository); - save_warning = warn_on_object_refname_ambiguity; - warn_on_object_refname_ambiguity = 0; + save_warning = cfg->warn_on_object_refname_ambiguity; + cfg->warn_on_object_refname_ambiguity = 0; while (fgets(line, sizeof(line), stdin) != NULL) { int len = strlen(line); @@ -4835,7 +4844,7 @@ static void get_object_list(struct rev_info *revs, struct strvec *argv) die(_("bad revision '%s'"), line); } - warn_on_object_refname_ambiguity = save_warning; + cfg->warn_on_object_refname_ambiguity = save_warning; if (use_bitmap_index && !get_object_list_from_bitmap(revs)) return; @@ -4851,8 +4860,13 @@ static void get_object_list(struct rev_info *revs, struct strvec *argv) fn_show_object = show_object; if (path_walk) { - get_object_list_path_walk(revs); - } else { + if (get_object_list_path_walk(revs)) { + warning(_("failed to pack objects via path-walk")); + path_walk = 0; + } + } + + if (!path_walk) { if (prepare_revision_walk(revs)) die(_("revision walk setup failed")); mark_edges_uninteresting(revs, show_edge, sparse); @@ -5013,6 +5027,7 @@ int cmd_pack_objects(int argc, struct string_list keep_pack_list = STRING_LIST_INIT_NODUP; struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT; + struct repo_config_values *cfg = repo_config_values(the_repository); struct option pack_objects_options[] = { OPT_CALLBACK_F('q', "quiet", &progress, NULL, @@ -5094,7 +5109,7 @@ int cmd_pack_objects(int argc, N_("ignore packs that have companion .keep file")), OPT_STRING_LIST(0, "keep-pack", &keep_pack_list, N_("name"), N_("ignore this pack")), - OPT_INTEGER(0, "compression", &pack_compression_level, + OPT_INTEGER(0, "compression", &cfg->pack_compression_level, N_("pack compression level")), OPT_BOOL(0, "keep-true-parents", &grafts_keep_true_parents, N_("do not hide commits by grafts")), @@ -5187,7 +5202,7 @@ int cmd_pack_objects(int argc, if (path_walk) { const char *option = NULL; - if (filter_options.choice) + if (!path_walk_filter_compatible(&filter_options)) option = "--filter"; else if (use_delta_islands) option = "--delta-islands"; @@ -5200,10 +5215,7 @@ int cmd_pack_objects(int argc, } if (path_walk) { strvec_push(&rp, "--boundary"); - /* - * We must disable the bitmaps because we are removing - * the --objects / --objects-edge[-aggressive] options. - */ + strvec_push(&rp, "--objects"); use_bitmap_index = 0; } else if (thin) { use_internal_rev_list = 1; @@ -5253,10 +5265,10 @@ int cmd_pack_objects(int argc, if (!reuse_object) reuse_delta = 0; - if (pack_compression_level == -1) - pack_compression_level = Z_DEFAULT_COMPRESSION; - else if (pack_compression_level < 0 || pack_compression_level > Z_BEST_COMPRESSION) - die(_("bad pack compression level %d"), pack_compression_level); + if (cfg->pack_compression_level == -1) + cfg->pack_compression_level = Z_DEFAULT_COMPRESSION; + else if (cfg->pack_compression_level < 0 || cfg->pack_compression_level > Z_BEST_COMPRESSION) + die(_("bad pack compression level %d"), cfg->pack_compression_level); if (!delta_search_threads) /* --threads=0 means autodetect */ delta_search_threads = online_cpus();
diff --git a/builtin/pull.c b/builtin/pull.c index 7e67fdc..d49b091 100644 --- a/builtin/pull.c +++ b/builtin/pull.c
@@ -996,9 +996,13 @@ int cmd_pull(int argc, OPT_PASSTHRU('6', "ipv6", &opt_ipv6, NULL, N_("use IPv6 addresses only"), PARSE_OPT_NOARG), - OPT_PASSTHRU_ARGV(0, "negotiation-tip", &opt_fetch, N_("revision"), + OPT_PASSTHRU_ARGV(0, "negotiation-restrict", &opt_fetch, N_("revision"), N_("report that we have only objects reachable from this object"), 0), + OPT_ALIAS(0, "negotiation-tip", "negotiation-restrict"), + OPT_PASSTHRU_ARGV(0, "negotiation-include", &opt_fetch, N_("revision"), + N_("ensure this ref is always sent as a negotiation have"), + 0), OPT_BOOL(0, "show-forced-updates", &opt_show_forced_updates, N_("check for forced-updates on all updated branches")), OPT_PASSTHRU(0, "set-upstream", &set_upstream, NULL,
diff --git a/builtin/push.c b/builtin/push.c index 7100ffb..6021b71 100644 --- a/builtin/push.c +++ b/builtin/push.c
@@ -10,6 +10,7 @@ #include "config.h" #include "environment.h" #include "gettext.h" +#include "hex.h" #include "refspec.h" #include "run-command.h" #include "remote.h" @@ -544,6 +545,123 @@ static int git_push_config(const char *k, const char *v, return git_default_config(k, v, ctx, NULL); } +static int push_multiple(struct string_list *list, + const struct string_list *push_options, + int flags, + int tags, + const char **refspecs, + int refspec_nr) +{ + int result = 0; + size_t i; + struct strvec argv = STRVEC_INIT; + + strvec_push(&argv, "push"); + + if (flags & TRANSPORT_PUSH_FORCE) + strvec_push(&argv, "--force"); + if (flags & TRANSPORT_PUSH_DRY_RUN) + strvec_push(&argv, "--dry-run"); + if (flags & TRANSPORT_PUSH_PORCELAIN) + strvec_push(&argv, "--porcelain"); + if (flags & TRANSPORT_PUSH_PRUNE) + strvec_push(&argv, "--prune"); + if (flags & TRANSPORT_PUSH_NO_HOOK) + strvec_push(&argv, "--no-verify"); + if (flags & TRANSPORT_PUSH_FOLLOW_TAGS) + strvec_push(&argv, "--follow-tags"); + if (flags & TRANSPORT_PUSH_SET_UPSTREAM) + strvec_push(&argv, "--set-upstream"); + if (flags & TRANSPORT_PUSH_FORCE_IF_INCLUDES) + strvec_push(&argv, "--force-if-includes"); + if (flags & TRANSPORT_PUSH_ALL) + strvec_push(&argv, "--all"); + if (flags & TRANSPORT_PUSH_MIRROR) + strvec_push(&argv, "--mirror"); + + if (flags & TRANSPORT_PUSH_CERT_ALWAYS) + strvec_push(&argv, "--signed=yes"); + else if (flags & TRANSPORT_PUSH_CERT_IF_ASKED) + strvec_push(&argv, "--signed=if-asked"); + if (!thin) + strvec_push(&argv, "--no-thin"); + + if (deleterefs) + strvec_push(&argv, "--delete"); + + if (receivepack) + strvec_pushf(&argv, "--receive-pack=%s", receivepack); + if (verbosity >= 2) + strvec_push(&argv, "-v"); + if (verbosity >= 1) + strvec_push(&argv, "-v"); + else if (verbosity < 0) + strvec_push(&argv, "-q"); + if (progress > 0) + strvec_push(&argv, "--progress"); + else if (progress == 0) + strvec_push(&argv, "--no-progress"); + + if (family == TRANSPORT_FAMILY_IPV4) + strvec_push(&argv, "--ipv4"); + else if (family == TRANSPORT_FAMILY_IPV6) + strvec_push(&argv, "--ipv6"); + + if (recurse_submodules == RECURSE_SUBMODULES_CHECK) + strvec_push(&argv, "--recurse-submodules=check"); + else if (recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) + strvec_push(&argv, "--recurse-submodules=on-demand"); + else if (recurse_submodules == RECURSE_SUBMODULES_ONLY) + strvec_push(&argv, "--recurse-submodules=only"); + else if (recurse_submodules == RECURSE_SUBMODULES_OFF) + strvec_push(&argv, "--recurse-submodules=no"); + + + if (tags) + strvec_push(&argv, "--tags"); + + for (i = 0; i < push_options->nr; i++) + strvec_pushf(&argv, "--push-option=%s", + push_options->items[i].string); + + for (i = 0; i < cas.nr; i++) { + if (cas.entry[i].use_tracking) { + strvec_pushf(&argv, "--force-with-lease=%s", + cas.entry[i].refname); + } else if (!is_null_oid(&cas.entry[i].expect)) { + strvec_pushf(&argv, "--force-with-lease=%s:%s", + cas.entry[i].refname, + oid_to_hex(&cas.entry[i].expect)); + } else { + strvec_push(&argv, "--force-with-lease"); + } + } + + for (i = 0; i < list->nr; i++) { + const char *name = list->items[i].string; + struct child_process cmd = CHILD_PROCESS_INIT; + int j; + + strvec_pushv(&cmd.args, argv.v); + strvec_push(&cmd.args, name); + + for (j = 0; j < refspec_nr; j++) + strvec_push(&cmd.args, refspecs[j]); + + if (verbosity >= 0) + printf(_("Pushing to %s\n"), name); + + cmd.git_cmd = 1; + if (run_command(&cmd)) { + error(_("could not push to %s"), name); + result = 1; + } + } + + strvec_clear(&argv); + return result; +} + int cmd_push(int argc, const char **argv, const char *prefix, @@ -552,12 +670,13 @@ int cmd_push(int argc, int flags = 0; int tags = 0; int push_cert = -1; - int rc; + int rc = 0; + int base_flags; const char *repo = NULL; /* default repository */ struct string_list push_options_cmdline = STRING_LIST_INIT_DUP; + struct string_list remote_group = STRING_LIST_INIT_DUP; struct string_list *push_options; const struct string_list_item *item; - struct remote *remote; struct option options[] = { OPT__VERBOSITY(&verbosity), @@ -620,39 +739,45 @@ int cmd_push(int argc, else if (recurse_submodules == RECURSE_SUBMODULES_ONLY) flags |= TRANSPORT_RECURSE_SUBMODULES_ONLY; - if (tags) - refspec_append(&rs, "refs/tags/*"); - if (argc > 0) repo = argv[0]; - remote = pushremote_get(repo); - if (!remote) { - if (repo) - die(_("bad repository '%s'"), repo); - die(_("No configured push destination.\n" - "Either specify the URL from the command-line or configure a remote repository using\n" - "\n" - " git remote add <name> <url>\n" - "\n" - "and then push using the remote name\n" - "\n" - " git push <name>\n")); - } - - if (argc > 0) - set_refspecs(argv + 1, argc - 1, remote); - - if (remote->mirror) - flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE); - - if (flags & TRANSPORT_PUSH_ALL) { - if (argc >= 2) - die(_("--all can't be combined with refspecs")); - } - if (flags & TRANSPORT_PUSH_MIRROR) { - if (argc >= 2) - die(_("--mirror can't be combined with refspecs")); + if (repo) { + if (!add_remote_or_group(repo, &remote_group)) { + /* + * Not a configured remote name or group name. + * Try treating it as a direct URL or path, e.g. + * git push /tmp/foo.git + * git push https://github.com/user/repo.git + * pushremote_get() creates an anonymous remote + * from the URL so the loop below can handle it + * identically to a named remote. + */ + struct remote *r = pushremote_get(repo); + if (!r) + die(_("bad repository '%s'"), repo); + string_list_append(&remote_group, r->name); + } + } else { + struct remote *r = pushremote_get(NULL); + if (!r) + die(_("No configured push destination.\n" + "Either specify the URL from the command-line or configure a remote repository using\n" + "\n" + " git remote add <name> <url>\n" + "\n" + "and then push using the remote name\n" + "\n" + " git push <name>\n" + "\n" + "To push to multiple remotes at once, configure a remote group using\n" + "\n" + " git config remotes.<groupname> \"<remote1> <remote2>\"\n" + "\n" + "and then push using the group name\n" + "\n" + " git push <groupname>\n")); + string_list_append(&remote_group, r->name); } if (!is_empty_cas(&cas) && (flags & TRANSPORT_PUSH_FORCE_IF_INCLUDES)) @@ -662,10 +787,70 @@ int cmd_push(int argc, if (strchr(item->string, '\n')) die(_("push options must not have new line characters")); - rc = do_push(flags, push_options, remote); + if (remote_group.nr == 1) { + /* + * Single remote (the common case): run do_push() directly + * in this process. The loop runs exactly once. + * + * Mirror detection and the --mirror/--all + refspec conflict + * checks are done here. rs is rebuilt so that per-remote push + * mappings (remote.NAME.push config) are resolved against the + * correct remote. inner_flags is a snapshot of flags so that a + * mirror remote cannot bleed TRANSPORT_PUSH_FORCE into any + * subsequent call. + */ + base_flags = flags; + { + int inner_flags = base_flags; + struct remote *r = pushremote_get(remote_group.items[0].string); + if (!r) + die(_("no such remote or remote group: %s"), + remote_group.items[0].string); + + if (r->mirror) + inner_flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE); + + if (inner_flags & TRANSPORT_PUSH_ALL) { + if (argc >= 2) + die(_("--all can't be combined with refspecs")); + } + if (inner_flags & TRANSPORT_PUSH_MIRROR) { + if (argc >= 2) + die(_("--mirror can't be combined with refspecs")); + } + + refspec_clear(&rs); + rs = (struct refspec) REFSPEC_INIT_PUSH; + + if (tags) + refspec_append(&rs, "refs/tags/*"); + if (argc > 0) + set_refspecs(argv + 1, argc - 1, r); + + rc = do_push(inner_flags, push_options, r); + } + } else { + /* + * Multiple remotes: spawn one "git push <remote> [<refspecs>]" + * subprocess per remote, sequentially. + * + * Options that only make sense for a single transport connection + * are rejected here. + */ + if (flags & TRANSPORT_PUSH_ATOMIC) + die(_("--atomic can only be used when pushing to one remote")); + + rc = push_multiple(&remote_group, push_options, flags, + tags, + argc > 1 ? argv + 1 : NULL, + argc > 1 ? argc - 1 : 0); + } + string_list_clear(&push_options_cmdline, 0); string_list_clear(&push_options_config, 0); + string_list_clear(&remote_group, 0); clear_cas_option(&cas); + if (rc == -1) usage_with_options(push_usage, options); else
diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 460b21e..999a82e 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c
@@ -229,7 +229,7 @@ int cmd_read_tree(int argc, opts.preserve_ignored = 0; /* otherwise, opts.preserve_ignored is irrelevant */ if (opts.merge && !opts.index_only) - setup_work_tree(); + setup_work_tree(the_repository); if (opts.skip_sparse_checkout) ensure_full_index(the_repository->index);
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 878aa7f..19eb6a1 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c
@@ -1456,11 +1456,12 @@ static const char *push_to_checkout(unsigned char *hash, struct strvec *env, const char *work_tree) { - struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_FORCE_SERIAL; + opt.invoked_hook = invoked_hook; - strvec_pushf(env, "GIT_WORK_TREE=%s", absolute_path(work_tree)); strvec_pushv(&opt.env, env->v); + strvec_pushf(&opt.env, "GIT_WORK_TREE=%s", absolute_path(work_tree)); strvec_push(&opt.args, hash_to_hex(hash)); if (run_hooks_opt(the_repository, push_to_checkout_hook, &opt)) return "push-to-checkout hook declined"; @@ -1641,8 +1642,8 @@ static const char *update(struct command *cmd, struct shallow_info *si) ret = NULL; /* good */ } strbuf_release(&err); - } - else { + } else { + enum ref_transaction_error tx_err; struct strbuf err = STRBUF_INIT; if (shallow_update && si->shallow_ref[cmd->index] && update_shallow_ref(cmd, si)) { @@ -1650,14 +1651,18 @@ static const char *update(struct command *cmd, struct shallow_info *si) goto out; } - if (ref_transaction_update(transaction, - namespaced_name, - new_oid, old_oid, - NULL, NULL, - 0, "push", - &err)) { + tx_err = ref_transaction_update(transaction, + namespaced_name, + new_oid, old_oid, + NULL, NULL, + 0, "push", + &err); + if (tx_err) { rp_error("%s", err.buf); - ret = "failed to update ref"; + if (tx_err == REF_TRANSACTION_ERROR_GENERIC) + ret = "failed to update ref"; + else + ret = ref_transaction_error_msg(tx_err); } else { ret = NULL; /* good */ } @@ -2642,7 +2647,7 @@ int cmd_receive_pack(int argc, setup_path(); - if (!enter_repo(service_dir, 0)) + if (!enter_repo(the_repository, service_dir, 0)) die("'%s' does not appear to be a git repository", service_dir); repo_config(the_repository, receive_pack_config, NULL);
diff --git a/builtin/repack.c b/builtin/repack.c index 4c5a82c..1524a9c 100644 --- a/builtin/repack.c +++ b/builtin/repack.c
@@ -33,7 +33,7 @@ static int midx_must_contain_cruft = 1; static const char *const git_repack_usage[] = { N_("git repack [-a] [-A] [-d] [-f] [-F] [-l] [-n] [-q] [-b] [-m]\n" "[--window=<n>] [--depth=<n>] [--threads=<n>] [--keep-pack=<pack-name>]\n" - "[--write-midx] [--name-hash-version=<n>] [--path-walk]"), + "[--write-midx[=<mode>]] [--name-hash-version=<n>] [--path-walk]"), NULL }; @@ -42,9 +42,14 @@ static const char incremental_bitmap_conflict_error[] = N_( "--no-write-bitmap-index or disable the pack.writeBitmaps configuration." ); +#define DEFAULT_MIDX_SPLIT_FACTOR 2 +#define DEFAULT_MIDX_NEW_LAYER_THRESHOLD 8 + struct repack_config_ctx { struct pack_objects_args *po_args; struct pack_objects_args *cruft_po_args; + int midx_split_factor; + int midx_new_layer_threshold; }; static int repack_config(const char *var, const char *value, @@ -94,9 +99,39 @@ static int repack_config(const char *var, const char *value, midx_must_contain_cruft = git_config_bool(var, value); return 0; } + if (!strcmp(var, "repack.midxsplitfactor")) { + repack_ctx->midx_split_factor = git_config_int(var, value, + ctx->kvi); + return 0; + } + if (!strcmp(var, "repack.midxnewlayerthreshold")) { + repack_ctx->midx_new_layer_threshold = git_config_int(var, value, + ctx->kvi); + return 0; + } return git_default_config(var, value, ctx, cb); } +static int option_parse_write_midx(const struct option *opt, const char *arg, + int unset) +{ + enum repack_write_midx_mode *cfg = opt->value; + + if (unset) { + *cfg = REPACK_WRITE_MIDX_NONE; + return 0; + } + + if (!arg || !*arg) + *cfg = REPACK_WRITE_MIDX_DEFAULT; + else if (!strcmp(arg, "incremental")) + *cfg = REPACK_WRITE_MIDX_INCREMENTAL; + else + return error(_("unknown value for %s: %s"), opt->long_name, arg); + + return 0; +} + int cmd_repack(int argc, const char **argv, const char *prefix, @@ -119,7 +154,7 @@ int cmd_repack(int argc, struct string_list keep_pack_list = STRING_LIST_INIT_NODUP; struct pack_objects_args po_args = PACK_OBJECTS_ARGS_INIT; struct pack_objects_args cruft_po_args = PACK_OBJECTS_ARGS_INIT; - int write_midx = 0; + enum repack_write_midx_mode write_midx = REPACK_WRITE_MIDX_NONE; const char *cruft_expiration = NULL; const char *expire_to = NULL; const char *filter_to = NULL; @@ -185,8 +220,14 @@ int cmd_repack(int argc, N_("do not repack this pack")), OPT_INTEGER('g', "geometric", &geometry.split_factor, N_("find a geometric progression with factor <N>")), - OPT_BOOL('m', "write-midx", &write_midx, - N_("write a multi-pack index of the resulting packs")), + OPT_CALLBACK_F(0, "write-midx", &write_midx, + N_("mode"), + N_("write a multi-pack index of the resulting packs"), + PARSE_OPT_OPTARG, option_parse_write_midx), + OPT_SET_INT_F('m', NULL, &write_midx, + N_("write a multi-pack index of the resulting packs"), + REPACK_WRITE_MIDX_DEFAULT, + PARSE_OPT_HIDDEN), OPT_STRING(0, "expire-to", &expire_to, N_("dir"), N_("pack prefix to store a pack containing pruned objects")), OPT_STRING(0, "filter-to", &filter_to, N_("dir"), @@ -199,6 +240,8 @@ int cmd_repack(int argc, memset(&config_ctx, 0, sizeof(config_ctx)); config_ctx.po_args = &po_args; config_ctx.cruft_po_args = &cruft_po_args; + config_ctx.midx_split_factor = DEFAULT_MIDX_SPLIT_FACTOR; + config_ctx.midx_new_layer_threshold = DEFAULT_MIDX_NEW_LAYER_THRESHOLD; repo_config(repo, repack_config, &config_ctx); @@ -221,14 +264,16 @@ int cmd_repack(int argc, pack_everything |= ALL_INTO_ONE; if (write_bitmaps < 0) { - if (!write_midx && + if (write_midx == REPACK_WRITE_MIDX_NONE && (!(pack_everything & ALL_INTO_ONE) || !is_bare_repository())) write_bitmaps = 0; } if (po_args.pack_kept_objects < 0) - po_args.pack_kept_objects = write_bitmaps > 0 && !write_midx; + po_args.pack_kept_objects = write_bitmaps > 0 && + write_midx == REPACK_WRITE_MIDX_NONE; - if (write_bitmaps && !(pack_everything & ALL_INTO_ONE) && !write_midx) + if (write_bitmaps && !(pack_everything & ALL_INTO_ONE) && + write_midx == REPACK_WRITE_MIDX_NONE) die(_(incremental_bitmap_conflict_error)); if (write_bitmaps && po_args.local && @@ -244,7 +289,14 @@ int cmd_repack(int argc, write_bitmaps = 0; } - if (write_midx && write_bitmaps) { + if (config_ctx.midx_split_factor < 2) + die(_("invalid value for %s: %d"), "--midx-split-factor", + config_ctx.midx_split_factor); + if (config_ctx.midx_new_layer_threshold < 1) + die(_("invalid value for %s: %d"), "--midx-new-layer-threshold", + config_ctx.midx_new_layer_threshold); + + if (write_midx != REPACK_WRITE_MIDX_NONE && write_bitmaps) { struct strbuf path = STRBUF_INIT; strbuf_addf(&path, "%s/%s_XXXXXX", @@ -267,6 +319,10 @@ int cmd_repack(int argc, if (geometry.split_factor) { if (pack_everything) die(_("options '%s' and '%s' cannot be used together"), "--geometric", "-A/-a"); + if (write_midx == REPACK_WRITE_MIDX_INCREMENTAL) { + geometry.midx_layer_threshold = config_ctx.midx_new_layer_threshold; + geometry.midx_layer_threshold_set = true; + } pack_geometry_init(&geometry, &existing, &po_args); pack_geometry_split(&geometry); } @@ -297,7 +353,7 @@ int cmd_repack(int argc, } if (repo_has_promisor_remote(repo)) strvec_push(&cmd.args, "--exclude-promisor-objects"); - if (!write_midx) { + if (write_midx == REPACK_WRITE_MIDX_NONE) { if (write_bitmaps > 0) strvec_push(&cmd.args, "--write-bitmap-index"); else if (write_bitmaps < 0) @@ -417,7 +473,7 @@ int cmd_repack(int argc, * midx_has_unknown_packs() will make the decision for * us. */ - if (!get_multi_pack_index(repo->objects->sources)) + if (!get_multi_pack_index(existing.source)) midx_must_contain_cruft = 1; } @@ -516,10 +572,13 @@ int cmd_repack(int argc, packtmp); /* End of pack replacement. */ - if (delete_redundant && pack_everything & ALL_INTO_ONE) + if (delete_redundant && pack_everything & ALL_INTO_ONE) { + if (write_midx == REPACK_WRITE_MIDX_INCREMENTAL) + existing_packs_retain_midx_packs(&existing); existing_packs_mark_for_deletion(&existing, &names); + } - if (write_midx) { + if (write_midx != REPACK_WRITE_MIDX_NONE) { struct repack_write_midx_opts opts = { .existing = &existing, .geometry = &geometry, @@ -528,11 +587,13 @@ int cmd_repack(int argc, .packdir = packdir, .show_progress = show_progress, .write_bitmaps = write_bitmaps > 0, - .midx_must_contain_cruft = midx_must_contain_cruft + .midx_must_contain_cruft = midx_must_contain_cruft, + .midx_split_factor = config_ctx.midx_split_factor, + .midx_new_layer_threshold = config_ctx.midx_new_layer_threshold, + .mode = write_midx, }; - ret = write_midx_included_packs(&opts); - + ret = repack_write_midx(&opts); if (ret) goto cleanup; } @@ -541,11 +602,15 @@ int cmd_repack(int argc, if (delete_redundant) { int opts = 0; - existing_packs_remove_redundant(&existing, packdir); + bool wrote_incremental_midx = write_midx == REPACK_WRITE_MIDX_INCREMENTAL; + + existing_packs_remove_redundant(&existing, packdir, + wrote_incremental_midx); if (geometry.split_factor) pack_geometry_remove_redundant(&geometry, &names, - &existing, packdir); + &existing, packdir, + wrote_incremental_midx); if (show_progress) opts |= PRUNE_PACKED_VERBOSE; prune_packed_objects(opts); @@ -564,8 +629,7 @@ int cmd_repack(int argc, unsigned flags = 0; if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL, 0)) flags |= MIDX_WRITE_INCREMENTAL; - write_midx_file(repo->objects->sources, - NULL, NULL, flags); + write_midx_file(existing.source, NULL, NULL, flags); } cleanup:
diff --git a/builtin/replace.c b/builtin/replace.c index 4c62c5a..aed6b2c 100644 --- a/builtin/replace.c +++ b/builtin/replace.c
@@ -127,7 +127,7 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn) } strbuf_setlen(&ref, base_len); - strbuf_addstr(&ref, oid_to_hex(&oid)); + strbuf_add_oid_hex(&ref, &oid); full_hex = ref.buf + base_len; if (refs_read_ref(get_main_ref_store(the_repository), ref.buf, &oid)) {
diff --git a/builtin/reset.c b/builtin/reset.c index 3590be5..3be6bd0 100644 --- a/builtin/reset.c +++ b/builtin/reset.c
@@ -281,11 +281,11 @@ static void parse_args(struct pathspec *pathspec, * Ok, argv[0] looks like a commit/tree; it should not * be a filename. */ - verify_non_filename(prefix, argv[0]); + verify_non_filename(the_repository, prefix, argv[0]); rev = *argv++; } else { /* Otherwise we treat this as a filename */ - verify_filename(prefix, argv[0], 1); + verify_filename(the_repository, prefix, argv[0], 1); } } @@ -468,7 +468,7 @@ int cmd_reset(int argc, trace2_cmd_mode(reset_type_names[reset_type]); if (reset_type != SOFT && (reset_type != MIXED || repo_get_work_tree(the_repository))) - setup_work_tree(); + setup_work_tree(the_repository); if (reset_type == MIXED && is_bare_repository()) die(_("%s reset is not allowed in a bare repository"),
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 218b5f3..bb88267 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c
@@ -10,6 +10,7 @@ #include "builtin.h" #include "abspath.h" +#include "bisect.h" #include "config.h" #include "commit.h" #include "environment.h" @@ -739,7 +740,7 @@ int cmd_rev_parse(int argc, /* No options; just report on whether we're in a git repo or not. */ if (argc == 1) { - setup_git_directory(); + setup_git_directory(the_repository); repo_config(the_repository, git_default_config, NULL); return 0; } @@ -749,7 +750,7 @@ int cmd_rev_parse(int argc, if (as_is) { if (show_file(arg, output_prefix) && as_is < 2) - verify_filename(prefix, arg, 0); + verify_filename(the_repository, prefix, arg, 0); continue; } @@ -774,7 +775,7 @@ int cmd_rev_parse(int argc, /* The rest of the options require a git repository. */ if (!did_repo_setup) { - prefix = setup_git_directory(); + prefix = setup_git_directory(the_repository); repo_config(the_repository, git_default_config, NULL); did_repo_setup = 1; @@ -940,13 +941,23 @@ int cmd_rev_parse(int argc, continue; } if (!strcmp(arg, "--bisect")) { + char *prefix; + char *term_bad = NULL; + char *term_good = NULL; struct refs_for_each_ref_options opts = { 0 }; - opts.prefix = "refs/bisect/bad"; + read_bisect_terms(&term_bad, &term_good); + prefix = xstrfmt("refs/bisect/%s", term_bad); + opts.prefix = prefix; refs_for_each_ref_ext(get_main_ref_store(the_repository), show_reference, NULL, &opts); - opts.prefix = "refs/bisect/good"; + free(prefix); + prefix = xstrfmt("refs/bisect/%s", term_good); + opts.prefix = prefix; refs_for_each_ref_ext(get_main_ref_store(the_repository), anti_reference, NULL, &opts); + free(prefix); + free(term_good); + free(term_bad); continue; } if (opt_with_value(arg, "--branches", &arg)) { @@ -1006,7 +1017,7 @@ int cmd_rev_parse(int argc, } if (!strcmp(arg, "--show-cdup")) { const char *pfx = prefix; - if (!is_inside_work_tree()) { + if (!is_inside_work_tree(the_repository)) { const char *work_tree = repo_get_work_tree(the_repository); if (work_tree) @@ -1063,12 +1074,12 @@ int cmd_rev_parse(int argc, continue; } if (!strcmp(arg, "--is-inside-git-dir")) { - printf("%s\n", is_inside_git_dir() ? "true" + printf("%s\n", is_inside_git_dir(the_repository) ? "true" : "false"); continue; } if (!strcmp(arg, "--is-inside-work-tree")) { - printf("%s\n", is_inside_work_tree() ? "true" + printf("%s\n", is_inside_work_tree(the_repository) ? "true" : "false"); continue; } @@ -1173,7 +1184,7 @@ int cmd_rev_parse(int argc, as_is = 1; if (!show_file(arg, output_prefix)) continue; - verify_filename(prefix, arg, 1); + verify_filename(the_repository, prefix, arg, 1); } strbuf_release(&buf); if (verify) {
diff --git a/builtin/rm.c b/builtin/rm.c index 05d89e9..081d0bc 100644 --- a/builtin/rm.c +++ b/builtin/rm.c
@@ -296,7 +296,7 @@ int cmd_rm(int argc, die(_("No pathspec was given. Which files should I remove?")); if (!index_only) - setup_work_tree(); + setup_work_tree(the_repository); prepare_repo_settings(the_repository); the_repository->settings.command_requires_full_index = 0;
diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 8b81c8a..1412b49 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c
@@ -273,8 +273,9 @@ int cmd_send_pack(int argc, fd[0] = 0; fd[1] = 1; } else { - conn = git_connect(fd, dest, "git-receive-pack", receivepack, - args.verbose ? CONNECT_VERBOSE : 0); + conn = git_connect(fd, dest, GIT_CONNECT_RECEIVE_PACK, + receivepack, + args.verbose ? CONNECT_VERBOSE : 0); } packet_reader_init(&reader, fd[0], NULL, 0,
diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index f4aa405..0863d0f 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c
@@ -63,7 +63,7 @@ static int sparse_checkout_list(int argc, const char **argv, const char *prefix, int res; struct repo_config_values *cfg = repo_config_values(the_repository); - setup_work_tree(); + setup_work_tree(the_repository); if (!cfg->apply_sparse_checkout) die(_("this worktree is not sparse")); @@ -73,7 +73,7 @@ static int sparse_checkout_list(int argc, const char **argv, const char *prefix, memset(&pl, 0, sizeof(pl)); - pl.use_cone_patterns = core_sparse_checkout_cone; + pl.use_cone_patterns = cfg->core_sparse_checkout_cone; sparse_filename = get_sparse_checkout_filename(); res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL, 0); @@ -229,7 +229,7 @@ static int update_working_directory(struct repository *r, o.dst_index = r->index; o.skip_sparse_checkout = 0; - setup_work_tree(); + setup_work_tree(the_repository); repo_hold_locked_index(r, &lock_file, LOCK_DIE_ON_ERROR); @@ -334,6 +334,7 @@ static int write_patterns_and_update(struct repository *repo, FILE *fp; struct lock_file lk = LOCK_INIT; int result; + struct repo_config_values *cfg = repo_config_values(the_repository); sparse_filename = get_sparse_checkout_filename(); @@ -353,7 +354,7 @@ static int write_patterns_and_update(struct repository *repo, if (!fp) die_errno(_("unable to fdopen %s"), get_lock_file_path(&lk)); - if (core_sparse_checkout_cone) + if (cfg->core_sparse_checkout_cone) write_cone_to_file(fp, pl); else write_patterns_to_file(fp, pl); @@ -402,15 +403,15 @@ static enum sparse_checkout_mode update_cone_mode(int *cone_mode) { /* If not specified, use previous definition of cone mode */ if (*cone_mode == -1 && cfg->apply_sparse_checkout) - *cone_mode = core_sparse_checkout_cone; + *cone_mode = cfg->core_sparse_checkout_cone; /* Set cone/non-cone mode appropriately */ cfg->apply_sparse_checkout = 1; if (*cone_mode == 1 || *cone_mode == -1) { - core_sparse_checkout_cone = 1; + cfg->core_sparse_checkout_cone = 1; return MODE_CONE_PATTERNS; } - core_sparse_checkout_cone = 0; + cfg->core_sparse_checkout_cone = 0; return MODE_ALL_PATTERNS; } @@ -468,7 +469,7 @@ static int sparse_checkout_init(int argc, const char **argv, const char *prefix, OPT_END(), }; - setup_work_tree(); + setup_work_tree(the_repository); repo_read_index(repo); init_opts.cone_mode = -1; @@ -577,7 +578,9 @@ static void add_patterns_from_input(struct pattern_list *pl, FILE *file) { int i; - if (core_sparse_checkout_cone) { + struct repo_config_values *cfg = repo_config_values(the_repository); + + if (cfg->core_sparse_checkout_cone) { struct strbuf line = STRBUF_INIT; hashmap_init(&pl->recursive_hashmap, pl_hashmap_cmp, NULL, 0); @@ -636,13 +639,14 @@ static void add_patterns_cone_mode(int argc, const char **argv, struct pattern_entry *pe; struct hashmap_iter iter; struct pattern_list existing; + struct repo_config_values *cfg = repo_config_values(the_repository); char *sparse_filename = get_sparse_checkout_filename(); add_patterns_from_input(pl, argc, argv, use_stdin ? stdin : NULL); memset(&existing, 0, sizeof(existing)); - existing.use_cone_patterns = core_sparse_checkout_cone; + existing.use_cone_patterns = cfg->core_sparse_checkout_cone; if (add_patterns_from_file_to_list(sparse_filename, "", 0, &existing, NULL, 0)) @@ -690,7 +694,7 @@ static int modify_pattern_list(struct repository *repo, switch (m) { case ADD: - if (core_sparse_checkout_cone) + if (cfg->core_sparse_checkout_cone) add_patterns_cone_mode(args->nr, args->v, pl, use_stdin); else add_patterns_literal(args->nr, args->v, pl, use_stdin); @@ -723,11 +727,12 @@ static void sanitize_paths(struct repository *repo, const char *prefix, int skip_checks) { int i; + struct repo_config_values *cfg = repo_config_values(the_repository); if (!args->nr) return; - if (prefix && *prefix && core_sparse_checkout_cone) { + if (prefix && *prefix && cfg->core_sparse_checkout_cone) { /* * The args are not pathspecs, so unfortunately we * cannot imitate how cmd_add() uses parse_pathspec(). @@ -735,7 +740,8 @@ static void sanitize_paths(struct repository *repo, int prefix_len = strlen(prefix); for (i = 0; i < args->nr; i++) { - char *prefixed_path = prefix_path(prefix, prefix_len, args->v[i]); + char *prefixed_path = prefix_path(the_repository, prefix, + prefix_len, args->v[i]); strvec_replace(args, i, prefixed_path); free(prefixed_path); } @@ -744,10 +750,10 @@ static void sanitize_paths(struct repository *repo, if (skip_checks) return; - if (prefix && *prefix && !core_sparse_checkout_cone) + if (prefix && *prefix && !cfg->core_sparse_checkout_cone) die(_("please run from the toplevel directory in non-cone mode")); - if (core_sparse_checkout_cone) { + if (cfg->core_sparse_checkout_cone) { for (i = 0; i < args->nr; i++) { if (args->v[i][0] == '/') die(_("specify directories rather than patterns (no leading slash)")); @@ -769,7 +775,7 @@ static void sanitize_paths(struct repository *repo, if (S_ISSPARSEDIR(ce->ce_mode)) continue; - if (core_sparse_checkout_cone) + if (cfg->core_sparse_checkout_cone) die(_("'%s' is not a directory; to treat it as a directory anyway, rerun with --skip-checks"), args->v[i]); else warning(_("pass a leading slash before paths such as '%s' if you want a single file (see NON-CONE PROBLEMS in the git-sparse-checkout manual)."), args->v[i]); @@ -801,7 +807,7 @@ static int sparse_checkout_add(int argc, const char **argv, const char *prefix, int ret; struct repo_config_values *cfg = repo_config_values(the_repository); - setup_work_tree(); + setup_work_tree(the_repository); if (!cfg->apply_sparse_checkout) die(_("no sparse-checkout to add to")); @@ -836,6 +842,7 @@ static struct sparse_checkout_set_opts { static int sparse_checkout_set(int argc, const char **argv, const char *prefix, struct repository *repo) { + struct repo_config_values *cfg = repo_config_values(the_repository); int default_patterns_nr = 2; const char *default_patterns[] = {"/*", "!/*/", NULL}; @@ -855,7 +862,7 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix, struct strvec patterns = STRVEC_INIT; int ret; - setup_work_tree(); + setup_work_tree(the_repository); repo_read_index(repo); set_opts.cone_mode = -1; @@ -873,7 +880,7 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix, * non-cone mode, if nothing is specified, manually select just the * top-level directory (much as 'init' would do). */ - if (!core_sparse_checkout_cone && !set_opts.use_stdin && argc == 0) { + if (!cfg->core_sparse_checkout_cone && !set_opts.use_stdin && argc == 0) { for (int i = 0; i < default_patterns_nr; i++) strvec_push(&patterns, default_patterns[i]); } else { @@ -911,7 +918,7 @@ static int sparse_checkout_reapply(int argc, const char **argv, }; struct repo_config_values *cfg = repo_config_values(the_repository); - setup_work_tree(); + setup_work_tree(the_repository); if (!cfg->apply_sparse_checkout) die(_("must be in a sparse-checkout to reapply sparsity patterns")); @@ -974,10 +981,10 @@ static int sparse_checkout_clean(int argc, const char **argv, OPT_END(), }; - setup_work_tree(); + setup_work_tree(the_repository); if (!cfg->apply_sparse_checkout) die(_("must be in a sparse-checkout to clean directories")); - if (!core_sparse_checkout_cone) + if (!cfg->core_sparse_checkout_cone) die(_("must be in a cone-mode sparse-checkout to clean directories")); argc = parse_options(argc, argv, prefix, @@ -1052,7 +1059,7 @@ static int sparse_checkout_disable(int argc, const char **argv, * forcibly return to a dense checkout regardless of initial state. */ - setup_work_tree(); + setup_work_tree(the_repository); argc = parse_options(argc, argv, prefix, builtin_sparse_checkout_disable_options, builtin_sparse_checkout_disable_usage, 0); @@ -1141,6 +1148,7 @@ static int sparse_checkout_check_rules(int argc, const char **argv, const char * FILE *fp; int ret; struct pattern_list pl = {0}; + struct repo_config_values *cfg = repo_config_values(the_repository); char *sparse_filename; check_rules_opts.cone_mode = -1; @@ -1152,7 +1160,7 @@ static int sparse_checkout_check_rules(int argc, const char **argv, const char * check_rules_opts.cone_mode = 1; update_cone_mode(&check_rules_opts.cone_mode); - pl.use_cone_patterns = core_sparse_checkout_cone; + pl.use_cone_patterns = cfg->core_sparse_checkout_cone; if (check_rules_opts.rules_file) { fp = xfopen(check_rules_opts.rules_file, "r"); add_patterns_from_input(&pl, argc, argv, fp);
diff --git a/builtin/stash.c b/builtin/stash.c index 0d27b2f..c4809f2 100644 --- a/builtin/stash.c +++ b/builtin/stash.c
@@ -44,7 +44,7 @@ #define BUILTIN_STASH_POP_USAGE \ N_("git stash pop [--index] [-q | --quiet] [<stash>]") #define BUILTIN_STASH_APPLY_USAGE \ - N_("git stash apply [--index] [-q | --quiet] [<stash>]") + N_("git stash apply [--index] [-q | --quiet] [--label-ours=<label>] [--label-theirs=<label>] [--label-base=<label>] [<stash>]") #define BUILTIN_STASH_BRANCH_USAGE \ N_("git stash branch <branchname> [<stash>]") #define BUILTIN_STASH_STORE_USAGE \ @@ -372,6 +372,56 @@ static int reset_tree(struct object_id *i_tree, int update, int reset) return 0; } +static int create_index_from_tree(const struct object_id *tree_id, + const char *index_path) +{ + int nr_trees = 1; + int ret = 0; + struct unpack_trees_options opts; + struct tree_desc t[MAX_UNPACK_TREES]; + struct tree *tree; + struct index_state dst_istate = INDEX_STATE_INIT(the_repository); + struct lock_file lock_file = LOCK_INIT; + + repo_read_index_preload(the_repository, NULL, 0); + refresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL); + + hold_lock_file_for_update(&lock_file, index_path, LOCK_DIE_ON_ERROR); + + memset(&opts, 0, sizeof(opts)); + + tree = repo_parse_tree_indirect(the_repository, tree_id); + if (!tree || repo_parse_tree(the_repository, tree)) { + ret = -1; + goto done; + } + + init_tree_desc(t, &tree->object.oid, tree->buffer, tree->size); + + opts.head_idx = 1; + opts.src_index = the_repository->index; + opts.dst_index = &dst_istate; + opts.merge = 1; + opts.reset = UNPACK_RESET_PROTECT_UNTRACKED; + opts.fn = oneway_merge; + + if (unpack_trees(nr_trees, t, &opts)) { + ret = -1; + goto done; + } + + if (write_locked_index(&dst_istate, &lock_file, COMMIT_LOCK)) { + ret = error(_("unable to write new index file")); + goto done; + } + +done: + release_index(&dst_istate); + if (ret) + rollback_lock_file(&lock_file); + return ret; +} + static int diff_tree_binary(struct strbuf *out, struct object_id *w_commit) { struct child_process cp = CHILD_PROCESS_INIT; @@ -591,7 +641,9 @@ static void unstage_changes_unless_new(struct object_id *orig_tree) } static int do_apply_stash(const char *prefix, struct stash_info *info, - int index, int quiet) + int index, int quiet, + const char *label_ours, const char *label_theirs, + const char *label_base) { int clean, ret; int has_index = index; @@ -643,9 +695,9 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, init_ui_merge_options(&o, the_repository); - o.branch1 = "Updated upstream"; - o.branch2 = "Stashed changes"; - o.ancestor = "Stash base"; + o.branch1 = label_ours ? label_ours : "Updated upstream"; + o.branch2 = label_theirs ? label_theirs : "Stashed changes"; + o.ancestor = label_base ? label_base : "Stash base"; if (oideq(&info->b_tree, &c_tree)) o.branch1 = "Version stash was based on"; @@ -723,11 +775,18 @@ static int apply_stash(int argc, const char **argv, const char *prefix, int ret = -1; int quiet = 0; int index = use_index; + const char *label_ours = NULL, *label_theirs = NULL, *label_base = NULL; struct stash_info info = STASH_INFO_INIT; struct option options[] = { OPT__QUIET(&quiet, N_("be quiet, only report errors")), OPT_BOOL(0, "index", &index, N_("attempt to recreate the index")), + OPT_STRING(0, "label-ours", &label_ours, N_("label"), + N_("label for the upstream side in conflict markers")), + OPT_STRING(0, "label-theirs", &label_theirs, N_("label"), + N_("label for the stashed side in conflict markers")), + OPT_STRING(0, "label-base", &label_base, N_("label"), + N_("label for the base in diff3 conflict markers")), OPT_END() }; @@ -737,7 +796,8 @@ static int apply_stash(int argc, const char **argv, const char *prefix, if (get_stash_info(&info, argc, argv)) goto cleanup; - ret = do_apply_stash(prefix, &info, index, quiet); + ret = do_apply_stash(prefix, &info, index, quiet, + label_ours, label_theirs, label_base); cleanup: free_stash_info(&info); return ret; @@ -836,7 +896,8 @@ static int pop_stash(int argc, const char **argv, const char *prefix, if (get_stash_info_assert(&info, argc, argv)) goto cleanup; - if ((ret = do_apply_stash(prefix, &info, index, quiet))) + if ((ret = do_apply_stash(prefix, &info, index, quiet, + NULL, NULL, NULL))) printf_ln(_("The stash entry is kept in case " "you need it again.")); else @@ -877,7 +938,8 @@ static int branch_stash(int argc, const char **argv, const char *prefix, strvec_push(&cp.args, oid_to_hex(&info.b_commit)); ret = run_command(&cp); if (!ret) - ret = do_apply_stash(prefix, &info, 1, 0); + ret = do_apply_stash(prefix, &info, 1, 0, + NULL, NULL, NULL); if (!ret && info.is_stash_ref) ret = do_drop_stash(&info, 0); @@ -1309,18 +1371,26 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps, struct interactive_options *interactive_opts) { int ret = 0; - struct child_process cp_read_tree = CHILD_PROCESS_INIT; struct child_process cp_diff_tree = CHILD_PROCESS_INIT; + struct commit *head_commit; + const struct object_id *head_tree; struct index_state istate = INDEX_STATE_INIT(the_repository); char *old_index_env = NULL, *old_repo_index_file; remove_path(stash_index_path.buf); - cp_read_tree.git_cmd = 1; - strvec_pushl(&cp_read_tree.args, "read-tree", "HEAD", NULL); - strvec_pushf(&cp_read_tree.env, "GIT_INDEX_FILE=%s", - stash_index_path.buf); - if (run_command(&cp_read_tree)) { + head_commit = lookup_commit(the_repository, &info->b_commit); + if (!head_commit || repo_parse_commit(the_repository, head_commit)) { + ret = -1; + goto done; + } + head_tree = get_commit_tree_oid(head_commit); + if (!head_tree) { + ret = -1; + goto done; + } + + if (create_index_from_tree(head_tree, stash_index_path.buf)) { ret = -1; goto done; }
diff --git a/builtin/stripspace.c b/builtin/stripspace.c index 4a566cb..18705f1 100644 --- a/builtin/stripspace.c +++ b/builtin/stripspace.c
@@ -54,7 +54,7 @@ int cmd_stripspace(int argc, usage_with_options(stripspace_usage, options); if (mode == STRIP_COMMENTS || mode == COMMENT_LINES) { - setup_git_directory_gently(&nongit); + setup_git_directory_gently(the_repository, &nongit); repo_config(the_repository, git_default_config, NULL); }
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 2f589e3..1cc82a1 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c
@@ -1250,7 +1250,7 @@ static int compute_summary_module_list(struct object_id *head_oid, if (!info->cached) { if (diff_cmd == DIFF_INDEX) - setup_work_tree(); + setup_work_tree(the_repository); if (repo_read_index_preload(the_repository, &rev.diffopt.pathspec, 0) < 0) { perror("repo_read_index_preload"); ret = -1;
diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index 76b3d0d..59e9b87 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c
@@ -9,6 +9,8 @@ #include "hex.h" #include "object-file.h" #include "odb.h" +#include "odb/streaming.h" +#include "odb/transaction.h" #include "object.h" #include "delta.h" #include "pack.h" @@ -23,8 +25,7 @@ static int dry_run, quiet, recover, has_errors, strict; static const char unpack_usage[] = "git unpack-objects [-n] [-q] [-r] [--strict]"; -/* We always read in 4kB chunks. */ -static unsigned char buffer[4096]; +static unsigned char buffer[DEFAULT_IO_BUFFER_SIZE]; static unsigned int offset, len; static off_t consumed_bytes; static off_t max_input_size; @@ -359,24 +360,21 @@ static void unpack_non_delta_entry(enum object_type type, unsigned long size, struct input_zstream_data { git_zstream *zstream; - unsigned char buf[8192]; int status; }; -static const void *feed_input_zstream(struct odb_write_stream *in_stream, - unsigned long *readlen) +static ssize_t feed_input_zstream(struct odb_write_stream *in_stream, + unsigned char *buf, size_t buf_len) { struct input_zstream_data *data = in_stream->data; git_zstream *zstream = data->zstream; void *in = fill(1); - if (in_stream->is_finished) { - *readlen = 0; - return NULL; - } + if (in_stream->is_finished) + return 0; - zstream->next_out = data->buf; - zstream->avail_out = sizeof(data->buf); + zstream->next_out = buf; + zstream->avail_out = buf_len; zstream->next_in = in; zstream->avail_in = len; @@ -384,9 +382,7 @@ static const void *feed_input_zstream(struct odb_write_stream *in_stream, in_stream->is_finished = data->status != Z_OK; use(len - zstream->avail_in); - *readlen = sizeof(data->buf) - zstream->avail_out; - - return data->buf; + return buf_len - zstream->avail_out; } static void stream_blob(unsigned long size, unsigned nr)
diff --git a/builtin/update-index.c b/builtin/update-index.c index 8a59077..3d6646c 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c
@@ -19,6 +19,7 @@ #include "tree-walk.h" #include "object-file.h" #include "odb.h" +#include "odb/transaction.h" #include "refs.h" #include "resolve-undo.h" #include "parse-options.h" @@ -655,7 +656,7 @@ static int do_unresolve(int ac, const char **av, for (i = 1; i < ac; i++) { const char *arg = av[i]; - char *p = prefix_path(prefix, prefix_length, arg); + char *p = prefix_path(the_repository, prefix, prefix_length, arg); err |= unresolve_one(p); free(p); } @@ -732,7 +733,7 @@ struct refresh_params { static int refresh(struct refresh_params *o, unsigned int flag) { - setup_work_tree(); + setup_work_tree(the_repository); repo_read_index(the_repository); *o->has_errors |= refresh_index(the_repository->index, o->flags | flag, NULL, NULL, NULL); @@ -901,7 +902,7 @@ static enum parse_opt_result reupdate_callback( BUG_ON_OPT_ARG(arg); /* consume remaining arguments. */ - setup_work_tree(); + setup_work_tree(the_repository); *has_errors = do_reupdate(ctx->argv + 1, prefix); if (*has_errors) the_repository->index->cache_changed = 0; @@ -1157,8 +1158,8 @@ int cmd_update_index(int argc, transaction = NULL; } - setup_work_tree(); - p = prefix_path(prefix, prefix_length, path); + setup_work_tree(the_repository); + p = prefix_path(the_repository, prefix, prefix_length, path); update_one(p); if (set_executable_bit) chmod_path(set_executable_bit, p); @@ -1199,7 +1200,7 @@ int cmd_update_index(int argc, struct strbuf buf = STRBUF_INIT; struct strbuf unquoted = STRBUF_INIT; - setup_work_tree(); + setup_work_tree(the_repository); while (getline_fn(&buf, stdin) != EOF) { char *p; if (!nul_term_line && buf.buf[0] == '"') { @@ -1208,7 +1209,7 @@ int cmd_update_index(int argc, die("line is badly quoted"); strbuf_swap(&buf, &unquoted); } - p = prefix_path(prefix, prefix_length, buf.buf); + p = prefix_path(the_repository, prefix, prefix_length, buf.buf); update_one(p); if (set_executable_bit) chmod_path(set_executable_bit, p); @@ -1253,7 +1254,7 @@ int cmd_update_index(int argc, report(_("Untracked cache disabled")); break; case UC_TEST: - setup_work_tree(); + setup_work_tree(the_repository); return !test_if_untracked_cache_is_supported(); case UC_ENABLE: case UC_FORCE:
diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 2d68c40..6355c3d 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c
@@ -25,6 +25,15 @@ static unsigned int default_flags; static unsigned create_reflog_flag; static const char *msg; +struct command_options { + /* + * Individual updates are allowed to fail without causing + * update-ref to exit. This is set when using the + * '--batch-updates' flag. + */ + bool allow_update_failures; +}; + /* * Parse one whitespace- or NUL-terminated, possibly C-quoted argument * and append the result to arg. Return a pointer to the terminator. @@ -234,6 +243,53 @@ static int parse_next_oid(const char **next, const char *end, command, refname); } +static void print_rejected_refs(const char *refname, + const struct object_id *old_oid, + const struct object_id *new_oid, + const char *old_target, + const char *new_target, + enum ref_transaction_error err, + const char *details, + void *cb_data UNUSED) +{ + struct strbuf sb = STRBUF_INIT; + + if (details && *details) + error("%s", details); + + strbuf_addf(&sb, "rejected %s %s %s %s\n", refname, + new_oid ? oid_to_hex(new_oid) : new_target, + old_oid ? oid_to_hex(old_oid) : old_target, + ref_transaction_error_msg(err)); + + fwrite(sb.buf, sb.len, 1, stdout); + strbuf_release(&sb); +} + +/* + * Handle transaction errors. If we're using batches updates, we want to only + * die for generic errors and print the remaining to the user. + */ +static void handle_ref_transaction_error(const char *refname, + struct object_id *new_oid, + struct object_id *old_oid, + const char *new_target, + const char *old_target, + enum ref_transaction_error tx_err, + struct strbuf *err, + struct command_options *opts) +{ + if (!tx_err) + return; + + if (tx_err != REF_TRANSACTION_ERROR_GENERIC && opts->allow_update_failures) { + print_rejected_refs(refname, old_oid, new_oid, old_target, + new_target, tx_err, err->buf, NULL); + return; + } + + die("%s", err->buf); +} /* * The following five parse_cmd_*() functions parse the corresponding @@ -246,11 +302,13 @@ static int parse_next_oid(const char **next, const char *end, */ static void parse_cmd_update(struct ref_transaction *transaction, - const char *next, const char *end) + const char *next, const char *end, + struct command_options *opts) { struct strbuf err = STRBUF_INIT; char *refname; struct object_id new_oid, old_oid; + enum ref_transaction_error tx_err; int have_old; refname = parse_refname(&next); @@ -267,12 +325,14 @@ static void parse_cmd_update(struct ref_transaction *transaction, if (*next != line_termination) die("update %s: extra input: %s", refname, next); - if (ref_transaction_update(transaction, refname, - &new_oid, have_old ? &old_oid : NULL, - NULL, NULL, - update_flags | create_reflog_flag, - msg, &err)) - die("%s", err.buf); + tx_err = ref_transaction_update(transaction, refname, + &new_oid, have_old ? &old_oid : NULL, + NULL, NULL, + update_flags | create_reflog_flag, + msg, &err); + handle_ref_transaction_error(refname, &new_oid, have_old ? &old_oid : NULL, + NULL, NULL, tx_err, &err, opts); + update_flags = default_flags; free(refname); @@ -280,9 +340,11 @@ static void parse_cmd_update(struct ref_transaction *transaction, } static void parse_cmd_symref_update(struct ref_transaction *transaction, - const char *next, const char *end UNUSED) + const char *next, const char *end UNUSED, + struct command_options *opts) { char *refname, *new_target, *old_arg; + enum ref_transaction_error tx_err; char *old_target = NULL; struct strbuf err = STRBUF_INIT; struct object_id old_oid; @@ -319,13 +381,15 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction, if (*next != line_termination) die("symref-update %s: extra input: %s", refname, next); - if (ref_transaction_update(transaction, refname, NULL, - have_old_oid ? &old_oid : NULL, - new_target, - have_old_oid ? NULL : old_target, - update_flags | create_reflog_flag, - msg, &err)) - die("%s", err.buf); + tx_err = ref_transaction_update(transaction, refname, NULL, + have_old_oid ? &old_oid : NULL, + new_target, + have_old_oid ? NULL : old_target, + update_flags | create_reflog_flag, + msg, &err); + handle_ref_transaction_error(refname, NULL, have_old_oid ? &old_oid : NULL, + new_target, have_old_oid ? NULL : old_target, + tx_err, &err, opts); update_flags = default_flags; free(refname); @@ -336,11 +400,13 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction, } static void parse_cmd_create(struct ref_transaction *transaction, - const char *next, const char *end) + const char *next, const char *end, + struct command_options *opts) { struct strbuf err = STRBUF_INIT; char *refname; struct object_id new_oid; + enum ref_transaction_error tx_err; refname = parse_refname(&next); if (!refname) @@ -355,22 +421,24 @@ static void parse_cmd_create(struct ref_transaction *transaction, if (*next != line_termination) die("create %s: extra input: %s", refname, next); - if (ref_transaction_create(transaction, refname, &new_oid, NULL, - update_flags | create_reflog_flag, - msg, &err)) - die("%s", err.buf); + tx_err = ref_transaction_create(transaction, refname, &new_oid, NULL, + update_flags | create_reflog_flag, + msg, &err); + handle_ref_transaction_error(refname, &new_oid, NULL, NULL, NULL, tx_err, + &err, opts); update_flags = default_flags; free(refname); strbuf_release(&err); } - static void parse_cmd_symref_create(struct ref_transaction *transaction, - const char *next, const char *end UNUSED) + const char *next, const char *end UNUSED, + struct command_options *opts) { struct strbuf err = STRBUF_INIT; char *refname, *new_target; + enum ref_transaction_error tx_err; refname = parse_refname(&next); if (!refname) @@ -383,10 +451,11 @@ static void parse_cmd_symref_create(struct ref_transaction *transaction, if (*next != line_termination) die("symref-create %s: extra input: %s", refname, next); - if (ref_transaction_create(transaction, refname, NULL, new_target, - update_flags | create_reflog_flag, - msg, &err)) - die("%s", err.buf); + tx_err = ref_transaction_create(transaction, refname, NULL, new_target, + update_flags | create_reflog_flag, + msg, &err); + handle_ref_transaction_error(refname, NULL, NULL, new_target, NULL, + tx_err, &err, opts); update_flags = default_flags; free(refname); @@ -395,7 +464,8 @@ static void parse_cmd_symref_create(struct ref_transaction *transaction, } static void parse_cmd_delete(struct ref_transaction *transaction, - const char *next, const char *end) + const char *next, const char *end, + struct command_options *opts UNUSED) { struct strbuf err = STRBUF_INIT; char *refname; @@ -428,9 +498,9 @@ static void parse_cmd_delete(struct ref_transaction *transaction, strbuf_release(&err); } - static void parse_cmd_symref_delete(struct ref_transaction *transaction, - const char *next, const char *end UNUSED) + const char *next, const char *end UNUSED, + struct command_options *opts UNUSED) { struct strbuf err = STRBUF_INIT; char *refname, *old_target; @@ -457,9 +527,9 @@ static void parse_cmd_symref_delete(struct ref_transaction *transaction, strbuf_release(&err); } - static void parse_cmd_verify(struct ref_transaction *transaction, - const char *next, const char *end) + const char *next, const char *end, + struct command_options *opts UNUSED) { struct strbuf err = STRBUF_INIT; char *refname; @@ -486,7 +556,8 @@ static void parse_cmd_verify(struct ref_transaction *transaction, } static void parse_cmd_symref_verify(struct ref_transaction *transaction, - const char *next, const char *end UNUSED) + const char *next, const char *end UNUSED, + struct command_options *opts UNUSED) { struct strbuf err = STRBUF_INIT; struct object_id old_oid; @@ -528,7 +599,8 @@ static void report_ok(const char *command) } static void parse_cmd_option(struct ref_transaction *transaction UNUSED, - const char *next, const char *end UNUSED) + const char *next, const char *end UNUSED, + struct command_options *opts UNUSED) { const char *rest; if (skip_prefix(next, "no-deref", &rest) && *rest == line_termination) @@ -538,7 +610,8 @@ static void parse_cmd_option(struct ref_transaction *transaction UNUSED, } static void parse_cmd_start(struct ref_transaction *transaction UNUSED, - const char *next, const char *end UNUSED) + const char *next, const char *end UNUSED, + struct command_options *opts UNUSED) { if (*next != line_termination) die("start: extra input: %s", next); @@ -546,7 +619,8 @@ static void parse_cmd_start(struct ref_transaction *transaction UNUSED, } static void parse_cmd_prepare(struct ref_transaction *transaction, - const char *next, const char *end UNUSED) + const char *next, const char *end UNUSED, + struct command_options *opts UNUSED) { struct strbuf error = STRBUF_INIT; if (*next != line_termination) @@ -557,7 +631,8 @@ static void parse_cmd_prepare(struct ref_transaction *transaction, } static void parse_cmd_abort(struct ref_transaction *transaction, - const char *next, const char *end UNUSED) + const char *next, const char *end UNUSED, + struct command_options *opts UNUSED) { struct strbuf error = STRBUF_INIT; if (*next != line_termination) @@ -567,31 +642,9 @@ static void parse_cmd_abort(struct ref_transaction *transaction, report_ok("abort"); } -static void print_rejected_refs(const char *refname, - const struct object_id *old_oid, - const struct object_id *new_oid, - const char *old_target, - const char *new_target, - enum ref_transaction_error err, - const char *details, - void *cb_data UNUSED) -{ - struct strbuf sb = STRBUF_INIT; - - if (details && *details) - error("%s", details); - - strbuf_addf(&sb, "rejected %s %s %s %s\n", refname, - new_oid ? oid_to_hex(new_oid) : new_target, - old_oid ? oid_to_hex(old_oid) : old_target, - ref_transaction_error_msg(err)); - - fwrite(sb.buf, sb.len, 1, stdout); - strbuf_release(&sb); -} - static void parse_cmd_commit(struct ref_transaction *transaction, - const char *next, const char *end UNUSED) + const char *next, const char *end UNUSED, + struct command_options *opts UNUSED) { struct strbuf error = STRBUF_INIT; if (*next != line_termination) @@ -619,7 +672,8 @@ enum update_refs_state { static const struct parse_cmd { const char *prefix; - void (*fn)(struct ref_transaction *, const char *, const char *); + void (*fn)(struct ref_transaction *, const char *, const char *, + struct command_options *); unsigned args; enum update_refs_state state; } command[] = { @@ -645,6 +699,10 @@ static void update_refs_stdin(unsigned int flags) struct ref_transaction *transaction; int i, j; + struct command_options opts = { + .allow_update_failures = flags & REF_TRANSACTION_ALLOW_FAILURE, + }; + transaction = ref_store_transaction_begin(get_main_ref_store(the_repository), flags, &err); if (!transaction) @@ -722,7 +780,7 @@ static void update_refs_stdin(unsigned int flags) } cmd->fn(transaction, input.buf + strlen(cmd->prefix) + !!cmd->args, - input.buf + input.len); + input.buf + input.len, &opts); } switch (state) {
diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c index 25312bb..718e74b 100644 --- a/builtin/upload-archive.c +++ b/builtin/upload-archive.c
@@ -31,7 +31,7 @@ int cmd_upload_archive_writer(int argc, if (argc != 2) usage(upload_archive_usage); - if (!enter_repo(argv[1], 0)) + if (!enter_repo(the_repository, argv[1], 0)) die("'%s' does not appear to be a git repository", argv[1]); init_archivers();
diff --git a/builtin/upload-pack.c b/builtin/upload-pack.c index 30498fa..32831fb 100644 --- a/builtin/upload-pack.c +++ b/builtin/upload-pack.c
@@ -59,7 +59,7 @@ int cmd_upload_pack(int argc, if (strict) enter_repo_flags |= ENTER_REPO_STRICT; - if (!enter_repo(dir, enter_repo_flags)) + if (!enter_repo(the_repository, dir, enter_repo_flags)) die("'%s' does not appear to be a git repository", dir); switch (determine_protocol_version_server()) {
diff --git a/builtin/url-parse.c b/builtin/url-parse.c new file mode 100644 index 0000000..7e70553 --- /dev/null +++ b/builtin/url-parse.c
@@ -0,0 +1,135 @@ +#include "builtin.h" +#include "gettext.h" +#include "parse-options.h" +#include "url.h" +#include "urlmatch.h" + +static const char * const builtin_url_parse_usage[] = { + N_("git url-parse [-c <component>] [--] <url>..."), + NULL +}; + +static char *component_arg; + +static struct option builtin_url_parse_options[] = { + OPT_STRING('c', "component", &component_arg, N_("component"), + N_("which URL component to extract")), + OPT_END(), +}; + +enum url_component { + URL_NONE = 0, + URL_SCHEME, + URL_USER, + URL_PASSWORD, + URL_HOST, + URL_PORT, + URL_PATH, +}; + +static void parse_or_die(const char *url, struct url_info *info) +{ + if (url_is_local_not_ssh(url)) { + if (*url == '/') + die("'%s' is not a URL; if you meant a local " + "repository, use 'file://%s'", url, url); + if (has_dos_drive_prefix(url)) + die("'%s' is not a URL; if you meant a local " + "repository, use 'file:///%s'", url, url); + die("'%s' is not a URL; if you meant a local repository, " + "use a 'file://' URL with an absolute path", url); + } + if (!url_parse(url, info)) + die("invalid git URL '%s': %s", url, info->err); +} + +static enum url_component get_component_or_die(const char *arg) +{ + if (!strcmp("path", arg)) + return URL_PATH; + if (!strcmp("host", arg)) + return URL_HOST; + if (!strcmp("scheme", arg)) + return URL_SCHEME; + if (!strcmp("user", arg)) + return URL_USER; + if (!strcmp("password", arg)) + return URL_PASSWORD; + if (!strcmp("port", arg)) + return URL_PORT; + die("invalid git URL component '%s'", arg); +} + +static char *extract_component(enum url_component component, + struct url_info *info) +{ + size_t offset, length; + + switch (component) { + case URL_SCHEME: + offset = 0; + length = info->scheme_len; + break; + case URL_USER: + offset = info->user_off; + length = info->user_len; + break; + case URL_PASSWORD: + offset = info->passwd_off; + length = info->passwd_len; + break; + case URL_HOST: + offset = info->host_off; + length = info->host_len; + break; + case URL_PORT: + offset = info->port_off; + length = info->port_len; + break; + case URL_PATH: + offset = info->path_off; + length = info->path_len; + break; + case URL_NONE: + return NULL; + } + + return xstrndup(info->url + offset, length); +} + +int cmd_url_parse(int argc, + const char **argv, + const char *prefix, + struct repository *repo UNUSED) +{ + struct url_info info; + enum url_component selected = URL_NONE; + char *extracted; + int i; + + argc = parse_options(argc, argv, prefix, builtin_url_parse_options, + builtin_url_parse_usage, 0); + + if (argc == 0) + usage_with_options(builtin_url_parse_usage, + builtin_url_parse_options); + + if (component_arg) + selected = get_component_or_die(component_arg); + + for (i = 0; i < argc; i++) { + parse_or_die(argv[i], &info); + + if (selected != URL_NONE) { + extracted = extract_component(selected, &info); + if (extracted) { + puts(extracted); + free(extracted); + } + } + + free(info.url); + } + + return 0; +}
diff --git a/builtin/worktree.c b/builtin/worktree.c index 4fd6f75..d21c43f 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c
@@ -609,7 +609,7 @@ static int add_worktree(const char *path, const char *refname, * is_junk is cleared, but do return appropriate code when hook fails. */ if (!ret && opts->checkout && !opts->orphan) { - struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_FORCE_SERIAL; strvec_pushl(&opt.env, "GIT_DIR", "GIT_WORK_TREE", NULL); strvec_pushl(&opt.args,
diff --git a/cache-tree.c b/cache-tree.c index 7881b42..184f7e2 100644 --- a/cache-tree.c +++ b/cache-tree.c
@@ -10,6 +10,7 @@ #include "cache-tree.h" #include "object-file.h" #include "odb.h" +#include "odb/transaction.h" #include "read-cache-ll.h" #include "replace-object.h" #include "repository.h" @@ -238,8 +239,8 @@ int cache_tree_fully_valid(struct cache_tree *it) if (!it) return 0; if (it->entry_count < 0 || - odb_has_object(the_repository->objects, &it->oid, - ODB_HAS_OBJECT_RECHECK_PACKED | ODB_HAS_OBJECT_FETCH_PROMISOR)) + !odb_has_object(the_repository->objects, &it->oid, + ODB_HAS_OBJECT_RECHECK_PACKED | ODB_HAS_OBJECT_FETCH_PROMISOR)) return 0; for (i = 0; i < it->subtree_nr; i++) { if (!cache_tree_fully_valid(it->down[i]->cache_tree))
diff --git a/cbtree.c b/cbtree.c index 4ab794b..8f5edbb 100644 --- a/cbtree.c +++ b/cbtree.c
@@ -7,6 +7,11 @@ #include "git-compat-util.h" #include "cbtree.h" +static inline uint8_t *cb_node_key(struct cb_tree *t, struct cb_node *node) +{ + return (uint8_t *) node + t->key_offset; +} + static struct cb_node *cb_node_of(const void *p) { return (struct cb_node *)((uintptr_t)p - 1); @@ -33,6 +38,7 @@ struct cb_node *cb_insert(struct cb_tree *t, struct cb_node *node, size_t klen) uint8_t c; int newdirection; struct cb_node **wherep, *p; + uint8_t *node_key, *p_key; assert(!((uintptr_t)node & 1)); /* allocations must be aligned */ @@ -41,23 +47,26 @@ struct cb_node *cb_insert(struct cb_tree *t, struct cb_node *node, size_t klen) return NULL; /* success */ } + node_key = cb_node_key(t, node); + /* see if a node already exists */ - p = cb_internal_best_match(t->root, node->k, klen); + p = cb_internal_best_match(t->root, node_key, klen); + p_key = cb_node_key(t, p); /* find first differing byte */ for (newbyte = 0; newbyte < klen; newbyte++) { - if (p->k[newbyte] != node->k[newbyte]) + if (p_key[newbyte] != node_key[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 = p_key[newbyte] ^ node_key[newbyte]; newotherbits |= newotherbits >> 1; newotherbits |= newotherbits >> 2; newotherbits |= newotherbits >> 4; newotherbits = (newotherbits & ~(newotherbits >> 1)) ^ 255; - c = p->k[newbyte]; + c = p_key[newbyte]; newdirection = (1 + (newotherbits | c)) >> 8; node->byte = newbyte; @@ -78,7 +87,7 @@ struct cb_node *cb_insert(struct cb_tree *t, struct cb_node *node, size_t klen) break; if (q->byte == newbyte && q->otherbits > newotherbits) break; - c = q->byte < klen ? node->k[q->byte] : 0; + c = q->byte < klen ? node_key[q->byte] : 0; direction = (1 + (q->otherbits | c)) >> 8; wherep = q->child + direction; } @@ -93,7 +102,7 @@ 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; + return p && !memcmp(cb_node_key(t, p), k, klen) ? p : NULL; } static int cb_descend(struct cb_node *p, cb_iter fn, void *arg) @@ -115,6 +124,7 @@ int cb_each(struct cb_tree *t, const uint8_t *kpfx, size_t klen, struct cb_node *p = t->root; struct cb_node *top = p; size_t i = 0; + uint8_t *p_key; if (!p) return 0; /* empty tree */ @@ -130,8 +140,9 @@ int cb_each(struct cb_tree *t, const uint8_t *kpfx, size_t klen, top = p; } + p_key = cb_node_key(t, p); for (i = 0; i < klen; i++) { - if (p->k[i] != kpfx[i]) + if (p_key[i] != kpfx[i]) return 0; /* "best" match failed */ }
diff --git a/cbtree.h b/cbtree.h index c374b1b..4647d4a 100644 --- a/cbtree.h +++ b/cbtree.h
@@ -6,9 +6,9 @@ * * 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[]" + * "struct cb_node" and provide `key_offset` to indicate where the key can be + * found relative to the `struct cb_node` for memcmp. + * If "klen" is variable, then it should be embedded into the key. * Recursion is bound by the maximum value of "klen" used. */ #ifndef CBTREE_H @@ -23,18 +23,19 @@ struct cb_node { */ uint32_t byte; uint8_t otherbits; - uint8_t k[FLEX_ARRAY]; /* arbitrary data, unaligned */ }; struct cb_tree { struct cb_node *root; + ptrdiff_t key_offset; }; -#define CBTREE_INIT { 0 } - -static inline void cb_init(struct cb_tree *t) +static inline void cb_init(struct cb_tree *t, + ptrdiff_t key_offset) { - struct cb_tree blank = CBTREE_INIT; + struct cb_tree blank = { + .key_offset = key_offset, + }; memcpy(t, &blank, sizeof(*t)); }
diff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh index c55441d..10c3530 100755 --- a/ci/install-dependencies.sh +++ b/ci/install-dependencies.sh
@@ -29,7 +29,7 @@ apk add --update shadow sudo meson ninja-build gcc libc-dev curl-dev openssl-dev expat-dev gettext \ zlib-ng-dev pcre2-dev python3 musl-libintl perl-utils ncurses \ apache2 apache2-http2 apache2-proxy apache2-ssl apache2-webdav apr-util-dbd_sqlite3 \ - bash cvs gnupg perl-cgi perl-dbd-sqlite perl-io-tty >/dev/null + bash cvs gnupg perl-cgi perl-dbd-sqlite perl-io-tty cargo >/dev/null ;; fedora-*|almalinux-*) case "$jobname" in
diff --git a/ci/lib.sh b/ci/lib.sh index a671994..b939110 100755 --- a/ci/lib.sh +++ b/ci/lib.sh
@@ -254,7 +254,7 @@ CI_OS_NAME=osx JOBS=$(nproc) ;; - *,alpine:*|*,fedora:*|*,ubuntu:*|*,i386/ubuntu:*) + *,almalinux:*|*,alpine:*|*,debian:*|*,fedora:*|*,ubuntu:*|*,i386/ubuntu:*) CI_OS_NAME=linux JOBS=$(nproc) ;; @@ -314,11 +314,13 @@ export GIT_TEST_CLONE_2GB=true export SKIP_DASHED_BUILT_INS=YesPlease -# Enable expensive tests on push builds to integration branches, but -# not on PR builds where the extra time is not justified for every -# iteration. +# In order to give maximum test coverage to contributor builds, +# preferrably even before the changes consume public review bandwidth, +# enable "expensive" tests for PR events. +# In order to catch bugs introduced at integration time by mismerges, +# enable the long tests for pushes to the integration branches as well. case "$GITHUB_EVENT_NAME,$CI_BRANCH" in -push,*next*|push,*master*|push,*main*|push,*maint*) +pull_request,*|push,*next*|push,*master*|push,*main*|push,*maint*) export GIT_TEST_LONG=YesPlease ;; esac @@ -381,6 +383,9 @@ osx-meson) MESONFLAGS="$MESONFLAGS -Dcredential_helpers=osxkeychain" ;; +windows-*) + export NO_RUST=UnfortunatelyYes + ;; esac MAKEFLAGS="$MAKEFLAGS CC=${CC:-cc}"
diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh index 28cfe73..1d9a0a7 100755 --- a/ci/run-build-and-tests.sh +++ b/ci/run-build-and-tests.sh
@@ -8,11 +8,19 @@ export TEST_CONTRIB_TOO=yes case "$jobname" in +linux-musl-meson) + MESONFLAGS="$MESONFLAGS -Drust=disabled" + export GIT_TEST_USE_SET_E=yes + ;; +almalinux-*|debian-*|fedora-*|linux-*) + export GIT_TEST_USE_SET_E=yes + ;; +esac + +case "$jobname" in fedora-breaking-changes-musl|linux-breaking-changes) export WITH_BREAKING_CHANGES=YesPlease - export WITH_RUST=YesPlease MESONFLAGS="$MESONFLAGS -Dbreaking_changes=true" - MESONFLAGS="$MESONFLAGS -Drust=enabled" ;; linux-TEST-vars) export OPENSSL_SHA1_UNSAFE=YesPlease @@ -30,6 +38,7 @@ export GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL=1 ;; linux-clang) + export NO_RUST=UnfortunatelyYes export GIT_TEST_DEFAULT_HASH=sha1 ;; linux-sha256)
diff --git a/combine-diff.c b/combine-diff.c index b799862..720768c 100644 --- a/combine-diff.c +++ b/combine-diff.c
@@ -666,7 +666,7 @@ static int make_hunks(struct sline *sline, unsigned long cnt, * (-) line, which records from what parents the line * was removed; this line does not appear in the result. * then check the set of parents the result has difference - * from, from all lines. If there are lines that has + * from, from all lines. If there are lines that have * different set of parents that the result has differences * from, that means we have more than two versions. *
diff --git a/command-list.txt b/command-list.txt index f9005cf..21b802c 100644 --- a/command-list.txt +++ b/command-list.txt
@@ -108,6 +108,7 @@ git-for-each-ref plumbinginterrogators git-for-each-repo plumbinginterrogators git-format-patch mainporcelain +git-format-rev plumbinginterrogators git-fsck ancillaryinterrogators complete git-gc mainporcelain git-get-tar-commit-id plumbinginterrogators @@ -202,6 +203,7 @@ git-update-server-info synchingrepositories git-upload-archive synchelpers git-upload-pack synchelpers +git-url-parse purehelpers git-var plumbinginterrogators git-verify-commit ancillaryinterrogators git-verify-pack plumbinginterrogators
diff --git a/commit-graph.c b/commit-graph.c index 9abe62b..801471a 100644 --- a/commit-graph.c +++ b/commit-graph.c
@@ -740,13 +740,13 @@ static struct commit_graph *prepare_commit_graph(struct repository *r) struct odb_source *source; /* - * Early return if there is no git dir or if the commit graph is + * Early return if there is no object database or if the commit graph is * disabled. * * This must come before the "already attempted?" check below, because * we want to disable even an already-loaded graph file. */ - if (!r->gitdir || r->commit_graph_disabled) + if (!r->objects || r->commit_graph_disabled) return NULL; if (r->objects->commit_graph_attempted) @@ -1669,7 +1669,7 @@ static void compute_reachable_generation_numbers( struct commit *current = list->item; struct commit_list *parent; int all_parents_computed = 1; - uint32_t max_gen = 0; + timestamp_t max_gen = 0; for (parent = current->parents; parent; parent = parent->next) { repo_parse_commit(info->r, parent->item);
diff --git a/commit-graph.h b/commit-graph.h index f6a5433..13ca4ff 100644 --- a/commit-graph.h +++ b/commit-graph.h
@@ -18,7 +18,7 @@ * This method is only used to enhance coverage of the commit-graph * feature in the test suite with the GIT_TEST_COMMIT_GRAPH and * GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS environment variables. Do not - * call this method oustide of a builtin, and only if you know what + * call this method outside of a builtin, and only if you know what * you are doing! */ void git_test_write_commit_graph_or_die(struct odb_source *source);
diff --git a/commit-reach.c b/commit-reach.c index d3a9b3e..5df471a 100644 --- a/commit-reach.c +++ b/commit-reach.c
@@ -17,8 +17,9 @@ #define PARENT2 (1u<<17) #define STALE (1u<<18) #define RESULT (1u<<19) +#define ENQUEUED (1u<<20) -static const unsigned all_flags = (PARENT1 | PARENT2 | STALE | RESULT); +static const unsigned all_flags = (PARENT1 | PARENT2 | STALE | RESULT | ENQUEUED); static int compare_commits_by_gen(const void *_a, const void *_b) { @@ -39,14 +40,60 @@ static int compare_commits_by_gen(const void *_a, const void *_b) return 0; } -static int queue_has_nonstale(struct prio_queue *queue) +/* + * A prio_queue with O(1) termination check. 'max_nonstale' tracks + * the lowest-priority non-stale commit enqueued so far; once it is + * popped, every remaining entry is known to be STALE. + */ +struct nonstale_queue { + struct prio_queue pq; + struct commit *max_nonstale; +}; + +static void nonstale_queue_put(struct nonstale_queue *queue, + struct commit *c) { - for (size_t i = 0; i < queue->nr; i++) { - struct commit *commit = queue->array[i].data; - if (!(commit->object.flags & STALE)) - return 1; - } - return 0; + struct commit *old = queue->max_nonstale; + + prio_queue_put(&queue->pq, c); + if (c->object.flags & STALE) + return; + if (!old || queue->pq.compare(old, c, queue->pq.cb_data) <= 0) + queue->max_nonstale = c; +} + +static struct commit *nonstale_queue_get(struct nonstale_queue *queue) +{ + struct commit *commit = prio_queue_get(&queue->pq); + + if (commit == queue->max_nonstale) + queue->max_nonstale = NULL; + + return commit; +} + +static void clear_nonstale_queue(struct nonstale_queue *queue) +{ + clear_prio_queue(&queue->pq); + queue->max_nonstale = NULL; +} + +static void nonstale_queue_put_dedup(struct nonstale_queue *queue, + struct commit *c) +{ + if (c->object.flags & ENQUEUED) + return; + c->object.flags |= ENQUEUED; + nonstale_queue_put(queue, c); +} + +static struct commit *nonstale_queue_get_dedup(struct nonstale_queue *queue) +{ + struct commit *commit = nonstale_queue_get(queue); + + if (commit) + commit->object.flags &= ~ENQUEUED; + return commit; } /* all input commits in one and twos[] must have been parsed! */ @@ -54,31 +101,33 @@ static int paint_down_to_common(struct repository *r, struct commit *one, int n, struct commit **twos, timestamp_t min_generation, - int ignore_missing_commits, + enum merge_base_flags mb_flags, struct commit_list **result) { - struct prio_queue queue = { compare_commits_by_gen_then_commit_date }; + struct nonstale_queue queue = { + { compare_commits_by_gen_then_commit_date } + }; int i; timestamp_t last_gen = GENERATION_NUMBER_INFINITY; struct commit_list **tail = result; if (!min_generation && !corrected_commit_dates_enabled(r)) - queue.compare = compare_commits_by_commit_date; + queue.pq.compare = compare_commits_by_commit_date; one->object.flags |= PARENT1; if (!n) { commit_list_append(one, result); return 0; } - prio_queue_put(&queue, one); + nonstale_queue_put_dedup(&queue, one); for (i = 0; i < n; i++) { twos[i]->object.flags |= PARENT2; - prio_queue_put(&queue, twos[i]); + nonstale_queue_put_dedup(&queue, twos[i]); } - while (queue_has_nonstale(&queue)) { - struct commit *commit = prio_queue_get(&queue); + while (queue.max_nonstale) { + struct commit *commit = nonstale_queue_get_dedup(&queue); struct commit_list *parents; int flags; timestamp_t generation = commit_graph_generation(commit); @@ -97,6 +146,14 @@ static int paint_down_to_common(struct repository *r, if (!(commit->object.flags & RESULT)) { commit->object.flags |= RESULT; tail = commit_list_append(commit, tail); + /* + * The queue is generation-ordered; no + * remaining common ancestor can be a + * descendant of this one. + */ + if (!(mb_flags & MERGE_BASE_FIND_ALL) && + generation < GENERATION_NUMBER_INFINITY) + break; } /* Mark parents of a found merge stale */ flags |= STALE; @@ -108,7 +165,7 @@ static int paint_down_to_common(struct repository *r, if ((p->object.flags & flags) == flags) continue; if (repo_parse_commit(r, p)) { - clear_prio_queue(&queue); + clear_nonstale_queue(&queue); commit_list_free(*result); *result = NULL; /* @@ -118,17 +175,17 @@ static int paint_down_to_common(struct repository *r, * corrupt commits would already have been * dispatched with a `die()`. */ - if (ignore_missing_commits) + if (mb_flags & MERGE_BASE_IGNORE_MISSING_COMMITS) return 0; return error(_("could not parse commit %s"), oid_to_hex(&p->object.oid)); } p->object.flags |= flags; - prio_queue_put(&queue, p); + nonstale_queue_put_dedup(&queue, p); } } - clear_prio_queue(&queue); + clear_nonstale_queue(&queue); commit_list_sort_by_date(result); return 0; } @@ -136,6 +193,7 @@ static int paint_down_to_common(struct repository *r, static int merge_bases_many(struct repository *r, struct commit *one, int n, struct commit **twos, + enum merge_base_flags mb_flags, struct commit_list **result) { struct commit_list *list = NULL, **tail = result; @@ -165,7 +223,7 @@ static int merge_bases_many(struct repository *r, oid_to_hex(&twos[i]->object.oid)); } - if (paint_down_to_common(r, one, n, twos, 0, 0, &list)) { + if (paint_down_to_common(r, one, n, twos, 0, mb_flags, &list)) { commit_list_free(list); return -1; } @@ -246,7 +304,8 @@ static int remove_redundant_no_gen(struct repository *r, min_generation = curr_generation; } if (paint_down_to_common(r, array[i], filled, - work, min_generation, 0, &common)) { + work, min_generation, + MERGE_BASE_FIND_ALL, &common)) { clear_commit_marks(array[i], all_flags); clear_commit_marks_many(filled, work, all_flags); commit_list_free(common); @@ -425,6 +484,7 @@ static int get_merge_bases_many_0(struct repository *r, size_t n, struct commit **twos, int cleanup, + enum merge_base_flags mb_flags, struct commit_list **result) { struct commit_list *list, **tail = result; @@ -432,7 +492,7 @@ static int get_merge_bases_many_0(struct repository *r, size_t cnt, i; int ret; - if (merge_bases_many(r, one, n, twos, result) < 0) + if (merge_bases_many(r, one, n, twos, mb_flags, result) < 0) return -1; for (i = 0; i < n; i++) { if (one == twos[i]) @@ -475,16 +535,18 @@ int repo_get_merge_bases_many(struct repository *r, struct commit **twos, struct commit_list **result) { - return get_merge_bases_many_0(r, one, n, twos, 1, result); + return get_merge_bases_many_0(r, one, n, twos, 1, + MERGE_BASE_FIND_ALL, result); } int repo_get_merge_bases_many_dirty(struct repository *r, struct commit *one, size_t n, struct commit **twos, + enum merge_base_flags mb_flags, struct commit_list **result) { - return get_merge_bases_many_0(r, one, n, twos, 0, result); + return get_merge_bases_many_0(r, one, n, twos, 0, mb_flags, result); } int repo_get_merge_bases(struct repository *r, @@ -492,7 +554,8 @@ int repo_get_merge_bases(struct repository *r, struct commit *two, struct commit_list **result) { - return get_merge_bases_many_0(r, one, 1, &two, 1, result); + return get_merge_bases_many_0(r, one, 1, &two, 1, + MERGE_BASE_FIND_ALL, result); } /* @@ -537,6 +600,10 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit, struct commit_list *bases = NULL; int ret = 0, i; timestamp_t generation, max_generation = GENERATION_NUMBER_ZERO; + enum merge_base_flags mb_flags = MERGE_BASE_FIND_ALL; + + if (ignore_missing_commits) + mb_flags |= MERGE_BASE_IGNORE_MISSING_COMMITS; if (repo_parse_commit(r, commit)) return ignore_missing_commits ? 0 : -1; @@ -555,7 +622,7 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit, if (paint_down_to_common(r, commit, nr_reference, reference, - generation, ignore_missing_commits, &bases)) + generation, mb_flags, &bases)) ret = -1; else if (commit->object.flags & PARENT2) ret = 1; @@ -1022,11 +1089,11 @@ struct commit_list *get_reachable_subset(struct commit **from, size_t nr_from, define_commit_slab(bit_arrays, struct bitmap *); static struct bit_arrays bit_arrays; -static void insert_no_dup(struct prio_queue *queue, struct commit *c) +static void insert_no_dup(struct nonstale_queue *queue, struct commit *c) { if (c->object.flags & PARENT2) return; - prio_queue_put(queue, c); + nonstale_queue_put(queue, c); c->object.flags |= PARENT2; } @@ -1051,7 +1118,9 @@ void ahead_behind(struct repository *r, struct commit **commits, size_t commits_nr, struct ahead_behind_count *counts, size_t counts_nr) { - struct prio_queue queue = { .compare = compare_commits_by_gen_then_commit_date }; + struct nonstale_queue queue = { + { .compare = compare_commits_by_gen_then_commit_date } + }; size_t width = DIV_ROUND_UP(commits_nr, BITS_IN_EWORD); if (!commits_nr || !counts_nr) @@ -1074,8 +1143,8 @@ void ahead_behind(struct repository *r, insert_no_dup(&queue, c); } - while (queue_has_nonstale(&queue)) { - struct commit *c = prio_queue_get(&queue); + while (queue.max_nonstale) { + struct commit *c = nonstale_queue_get(&queue); struct commit_list *p; struct bitmap *bitmap_c = get_bit_array(c, width); @@ -1117,15 +1186,14 @@ void ahead_behind(struct repository *r, /* STALE is used here, PARENT2 is used by insert_no_dup(). */ repo_clear_commit_marks(r, PARENT2 | STALE); - for (size_t i = 0; i < queue.nr; i++) - free_bit_array(queue.array[i].data); + for (size_t i = 0; i < queue.pq.nr; i++) + free_bit_array(queue.pq.array[i].data); clear_bit_arrays(&bit_arrays); - clear_prio_queue(&queue); + clear_nonstale_queue(&queue); } struct commit_and_index { struct commit *commit; - unsigned int index; timestamp_t generation; }; @@ -1165,7 +1233,6 @@ void tips_reachable_from_bases(struct repository *r, for (size_t i = 0; i < tips_nr; i++) { commits[i].commit = tips[i]; - commits[i].index = i; commits[i].generation = commit_graph_generation(tips[i]); } @@ -1173,6 +1240,9 @@ void tips_reachable_from_bases(struct repository *r, QSORT(commits, tips_nr, compare_commit_and_index_by_generation); min_generation = commits[0].generation; + for (size_t i = 0; i < tips_nr; i++) + commits[i].commit->object.flags |= RESULT; + while (bases) { repo_parse_commit(r, bases->item); commit_list_insert(bases->item, &stack); @@ -1183,20 +1253,16 @@ void tips_reachable_from_bases(struct repository *r, int explored_all_parents = 1; struct commit_list *p; struct commit *c = stack->item; - timestamp_t c_gen = commit_graph_generation(c); /* Does it match any of our tips? */ - for (size_t j = min_generation_index; j < tips_nr; j++) { - if (c_gen < commits[j].generation) - break; + { + if (c->object.flags & RESULT) { + c->object.flags |= mark; - if (commits[j].commit == c) { - tips[commits[j].index]->object.flags |= mark; - - if (j == min_generation_index) { - unsigned int k = j + 1; + if (commits[min_generation_index].commit->object.flags & mark) { + unsigned int k = min_generation_index + 1; while (k < tips_nr && - (tips[commits[k].index]->object.flags & mark)) + (commits[k].commit->object.flags & mark)) k++; /* Terminate early if all found. */ @@ -1232,6 +1298,8 @@ void tips_reachable_from_bases(struct repository *r, } done: + for (size_t i = 0; i < tips_nr; i++) + commits[i].commit->object.flags &= ~RESULT; free(commits); repo_clear_commit_marks(r, SEEN); commit_list_free(stack);
diff --git a/commit-reach.h b/commit-reach.h index 6012402..3f3a563 100644 --- a/commit-reach.h +++ b/commit-reach.h
@@ -17,10 +17,20 @@ int repo_get_merge_bases_many(struct repository *r, struct commit *one, size_t n, struct commit **twos, struct commit_list **result); -/* To be used only when object flags after this call no longer matter */ +enum merge_base_flags { + MERGE_BASE_IGNORE_MISSING_COMMITS = (1 << 0), + MERGE_BASE_FIND_ALL = (1 << 1), +}; + +/* + * To be used only when object flags after this call no longer matter. + * Without MERGE_BASE_FIND_ALL and with generation numbers available, + * returns after finding the first merge-base, skipping the STALE drain. + */ int repo_get_merge_bases_many_dirty(struct repository *r, struct commit *one, size_t n, struct commit **twos, + enum merge_base_flags mb_flags, struct commit_list **result); int get_octopus_merge_bases(struct commit_list *in, struct commit_list **result);
diff --git a/commit.c b/commit.c index 80d8d07..f1717cc 100644 --- a/commit.c +++ b/commit.c
@@ -434,6 +434,27 @@ static inline void set_commit_tree(struct commit *c, struct tree *t) c->maybe_tree = t; } +static void load_tree_from_commit_contents(struct repository *r, struct commit *commit) +{ + enum object_type type; + unsigned long size; + char *buf; + const char *p; + struct object_id tree_oid; + + buf = odb_read_object(r->objects, &commit->object.oid, &type, &size); + if (!buf) + return; + + if (type == OBJ_COMMIT && + skip_prefix(buf, "tree ", &p) && + !parse_oid_hex_algop(p, &tree_oid, &p, r->hash_algo) && + *p == '\n') + set_commit_tree(commit, lookup_tree(r, &tree_oid)); + + free(buf); +} + struct tree *repo_get_commit_tree(struct repository *r, const struct commit *commit) { @@ -443,7 +464,17 @@ struct tree *repo_get_commit_tree(struct repository *r, if (commit_graph_position(commit) != COMMIT_NOT_FROM_GRAPH) return get_commit_tree_in_graph(r, commit); - return NULL; + /* + * This is either a corrupt commit, or one which we partially loaded + * from a graph file but then subsequently threw away the graph data. + * + * Optimistically assume it's the latter and try to reload from + * scratch. This gives a performance penalty if it really is a corrupt + * commit, but presumably that happens rarely (and only once per + * process). + */ + load_tree_from_commit_contents(r, (struct commit *)commit); + return commit->maybe_tree; } struct object_id *get_commit_tree_oid(const struct commit *commit) @@ -729,19 +760,6 @@ void commit_list_free(struct commit_list *list) pop_commit(&list); } -struct commit_list * commit_list_insert_by_date(struct commit *item, struct commit_list **list) -{ - struct commit_list **pp = list; - struct commit_list *p; - while ((p = *pp) != NULL) { - if (p->item->date < item->date) { - break; - } - pp = &p->next; - } - return commit_list_insert(item, pp); -} - static int commit_list_compare_by_date(const struct commit_list *a, const struct commit_list *b) { @@ -1558,16 +1576,16 @@ int commit_tree(const char *msg, size_t msg_len, const struct object_id *tree, return result; } -static int find_invalid_utf8(const char *buf, int len) +static bool has_invalid_utf8(const char *buf, size_t len, size_t *bad_offset) { - int offset = 0; + size_t offset = 0; static const unsigned int max_codepoint[] = { 0x7f, 0x7ff, 0xffff, 0x10ffff }; while (len) { unsigned char c = *buf++; - int bytes, bad_offset; + unsigned bytes; unsigned int codepoint; unsigned int min_val, max_val; @@ -1578,7 +1596,7 @@ static int find_invalid_utf8(const char *buf, int len) if (c < 0x80) continue; - bad_offset = offset-1; + *bad_offset = offset-1; /* * Count how many more high bits set: that's how @@ -1595,11 +1613,11 @@ static int find_invalid_utf8(const char *buf, int len) * codepoints beyond U+10FFFF, which are guaranteed never to exist. */ if (bytes < 1 || 3 < bytes) - return bad_offset; + return true; /* Do we *have* that many bytes? */ if (len < bytes) - return bad_offset; + return true; /* * Place the encoded bits at the bottom of the value and compute the @@ -1617,43 +1635,42 @@ static int find_invalid_utf8(const char *buf, int len) codepoint <<= 6; codepoint |= *buf & 0x3f; if ((*buf++ & 0xc0) != 0x80) - return bad_offset; + return true; } while (--bytes); /* Reject codepoints that are out of range for the sequence length. */ if (codepoint < min_val || codepoint > max_val) - return bad_offset; + return true; /* Surrogates are only for UTF-16 and cannot be encoded in UTF-8. */ if ((codepoint & 0x1ff800) == 0xd800) - return bad_offset; + return true; /* U+xxFFFE and U+xxFFFF are guaranteed non-characters. */ if ((codepoint & 0xfffe) == 0xfffe) - return bad_offset; + return true; /* So are anything in the range U+FDD0..U+FDEF. */ if (codepoint >= 0xfdd0 && codepoint <= 0xfdef) - return bad_offset; + return true; } - return -1; + return false; } /* - * This verifies that the buffer is in proper utf8 format. + * This ensures that the buffer is in proper utf8 format. * * If it isn't, it assumes any non-utf8 characters are Latin1, * and does the conversion. */ -static int verify_utf8(struct strbuf *buf) +static int ensure_utf8(struct strbuf *buf) { int ok = 1; - long pos = 0; + size_t pos = 0; for (;;) { - int bad; + size_t bad; unsigned char c; unsigned char replace[2]; - bad = find_invalid_utf8(buf->buf + pos, buf->len - pos); - if (bad < 0) + if (!has_invalid_utf8(buf->buf + pos, buf->len - pos, &bad)) return ok; pos += bad; ok = 0; @@ -1726,6 +1743,7 @@ int commit_tree_extended(const char *msg, size_t msg_len, struct repository *r = the_repository; int result = 0; int encoding_is_utf8; + bool warned = false; struct strbuf buffer = STRBUF_INIT, compat_buffer = STRBUF_INIT; struct strbuf sig = STRBUF_INIT, compat_sig = STRBUF_INIT; struct object_id *parent_buf = NULL, *compat_oid = NULL; @@ -1747,6 +1765,13 @@ int commit_tree_extended(const char *msg, size_t msg_len, oidcpy(&parent_buf[i++], &p->item->object.oid); write_commit_tree(&buffer, msg, msg_len, tree, parent_buf, nparents, author, committer, extra); + + /* And check the encoding. */ + if (encoding_is_utf8 && !ensure_utf8(&buffer)) { + fprintf(stderr, _(commit_utf8_warn)); + warned = true; + } + if (sign_commit && sign_buffer(&buffer, &sig, sign_commit, SIGN_BUFFER_USE_DEFAULT_KEY)) { result = -1; @@ -1780,6 +1805,9 @@ int commit_tree_extended(const char *msg, size_t msg_len, free_commit_extra_headers(compat_extra); free(mapped_parents); + if (encoding_is_utf8 && !ensure_utf8(&compat_buffer) && !warned) + fprintf(stderr, _(commit_utf8_warn)); + if (sign_commit && sign_buffer(&compat_buffer, &compat_sig, sign_commit, SIGN_BUFFER_USE_DEFAULT_KEY)) { @@ -1818,10 +1846,6 @@ int commit_tree_extended(const char *msg, size_t msg_len, } } - /* And check the encoding. */ - if (encoding_is_utf8 && (!verify_utf8(&buffer) || !verify_utf8(&compat_buffer))) - fprintf(stderr, _(commit_utf8_warn)); - if (r->compat_hash_algo) { hash_object_file(r->compat_hash_algo, compat_buffer.buf, compat_buffer.len, OBJ_COMMIT, &compat_oid_buf); @@ -1970,7 +1994,7 @@ size_t ignored_log_message_bytes(const char *buf, size_t len) int run_commit_hook(int editor_is_used, const char *index_file, int *invoked_hook, const char *name, ...) { - struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_FORCE_SERIAL; va_list args; const char *arg;
diff --git a/commit.h b/commit.h index 5815004..1061ed7 100644 --- a/commit.h +++ b/commit.h
@@ -191,8 +191,6 @@ int commit_list_contains(struct commit *item, struct commit_list **commit_list_append(struct commit *commit, struct commit_list **next); unsigned commit_list_count(const struct commit_list *l); -struct commit_list *commit_list_insert_by_date(struct commit *item, - struct commit_list **list); void commit_list_sort_by_date(struct commit_list **list); /* Shallow copy of the input list */ @@ -203,25 +201,6 @@ struct commit_list *commit_list_reverse(struct commit_list *list); void commit_list_free(struct commit_list *list); -/* - * Deprecated compatibility functions for `struct commit_list`, to be removed - * once Git 2.53 is released. - */ -static inline struct commit_list *copy_commit_list(struct commit_list *l) -{ - return commit_list_copy(l); -} - -static inline struct commit_list *reverse_commit_list(struct commit_list *l) -{ - return commit_list_reverse(l); -} - -static inline void free_commit_list(struct commit_list *l) -{ - commit_list_free(l); -} - struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */ const char *repo_logmsg_reencode(struct repository *r,
diff --git a/compat/fsmonitor/fsm-health-linux.c b/compat/fsmonitor/fsm-health-linux.c new file mode 100644 index 0000000..43d67c4 --- /dev/null +++ b/compat/fsmonitor/fsm-health-linux.c
@@ -0,0 +1,33 @@ +#include "git-compat-util.h" +#include "config.h" +#include "fsmonitor-ll.h" +#include "fsm-health.h" +#include "fsmonitor--daemon.h" + +/* + * The Linux fsmonitor implementation uses inotify which has its own + * mechanisms for detecting filesystem unmount and other events that + * would require the daemon to shutdown. Therefore, we don't need + * a separate health thread like Windows does. + * + * These stub functions satisfy the interface requirements. + */ + +int fsm_health__ctor(struct fsmonitor_daemon_state *state UNUSED) +{ + return 0; +} + +void fsm_health__dtor(struct fsmonitor_daemon_state *state UNUSED) +{ + return; +} + +void fsm_health__loop(struct fsmonitor_daemon_state *state UNUSED) +{ + return; +} + +void fsm_health__stop_async(struct fsmonitor_daemon_state *state UNUSED) +{ +}
diff --git a/compat/fsmonitor/fsm-ipc-darwin.c b/compat/fsmonitor/fsm-ipc-unix.c similarity index 100% rename from compat/fsmonitor/fsm-ipc-darwin.c rename to compat/fsmonitor/fsm-ipc-unix.c
diff --git a/compat/fsmonitor/fsm-listen-linux.c b/compat/fsmonitor/fsm-listen-linux.c new file mode 100644 index 0000000..e3dca14 --- /dev/null +++ b/compat/fsmonitor/fsm-listen-linux.c
@@ -0,0 +1,746 @@ +#include "git-compat-util.h" +#include "dir.h" +#include "fsmonitor-ll.h" +#include "fsm-listen.h" +#include "fsmonitor--daemon.h" +#include "fsmonitor-path-utils.h" +#include "gettext.h" +#include "simple-ipc.h" +#include "string-list.h" +#include "trace.h" + +#include <sys/inotify.h> + +/* + * Safe value to bitwise OR with rest of mask for + * kernels that do not support IN_MASK_CREATE + */ +#ifndef IN_MASK_CREATE +#define IN_MASK_CREATE 0x00000000 +#endif + +enum shutdown_reason { + SHUTDOWN_CONTINUE = 0, + SHUTDOWN_STOP, + SHUTDOWN_ERROR, + SHUTDOWN_FORCE +}; + +struct watch_entry { + struct hashmap_entry ent; + int wd; + uint32_t cookie; + const char *dir; +}; + +struct rename_entry { + struct hashmap_entry ent; + time_t whence; + uint32_t cookie; + const char *dir; +}; + +struct fsm_listen_data { + int fd_inotify; + enum shutdown_reason shutdown; + struct hashmap watches; + struct hashmap renames; + struct hashmap revwatches; +}; + +static int watch_entry_cmp(const void *cmp_data UNUSED, + const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, + const void *keydata UNUSED) +{ + const struct watch_entry *e1, *e2; + + e1 = container_of(eptr, const struct watch_entry, ent); + e2 = container_of(entry_or_key, const struct watch_entry, ent); + return e1->wd != e2->wd; +} + +static int revwatches_entry_cmp(const void *cmp_data UNUSED, + const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, + const void *keydata UNUSED) +{ + const struct watch_entry *e1, *e2; + + e1 = container_of(eptr, const struct watch_entry, ent); + e2 = container_of(entry_or_key, const struct watch_entry, ent); + return strcmp(e1->dir, e2->dir); +} + +static int rename_entry_cmp(const void *cmp_data UNUSED, + const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, + const void *keydata UNUSED) +{ + const struct rename_entry *e1, *e2; + + e1 = container_of(eptr, const struct rename_entry, ent); + e2 = container_of(entry_or_key, const struct rename_entry, ent); + return e1->cookie != e2->cookie; +} + +/* + * Register an inotify watch, add watch descriptor to path mapping + * and the reverse mapping. + */ +static int add_watch(const char *path, struct fsm_listen_data *data) +{ + const char *interned = strintern(path); + struct watch_entry *w1, *w2; + + /* add the inotify watch, don't allow watches to be modified */ + int wd = inotify_add_watch(data->fd_inotify, interned, + (IN_ALL_EVENTS | IN_ONLYDIR | IN_MASK_CREATE) + ^ IN_ACCESS ^ IN_CLOSE ^ IN_OPEN); + if (wd < 0) { + if (errno == ENOENT || errno == ENOTDIR) + return 0; /* directory was deleted or is not a directory */ + if (errno == EEXIST) + return 0; /* watch already exists, no action needed */ + if (errno == ENOSPC) + return error(_("inotify watch limit reached; " + "increase fs.inotify.max_user_watches")); + return error_errno(_("inotify_add_watch('%s') failed"), interned); + } + + /* add watch descriptor -> directory mapping */ + CALLOC_ARRAY(w1, 1); + w1->wd = wd; + w1->dir = interned; + hashmap_entry_init(&w1->ent, memhash(&w1->wd, sizeof(int))); + hashmap_add(&data->watches, &w1->ent); + + /* add directory -> watch descriptor mapping */ + CALLOC_ARRAY(w2, 1); + w2->wd = wd; + w2->dir = interned; + hashmap_entry_init(&w2->ent, strhash(w2->dir)); + hashmap_add(&data->revwatches, &w2->ent); + + return 0; +} + +/* + * Remove the inotify watch, the watch descriptor to path mapping + * and the reverse mapping. + */ +static void remove_watch(struct watch_entry *w, struct fsm_listen_data *data) +{ + struct watch_entry k1, k2, *w1, *w2; + + /* remove watch, ignore error if kernel already did it */ + if (inotify_rm_watch(data->fd_inotify, w->wd) && errno != EINVAL) + error_errno(_("inotify_rm_watch() failed")); + + k1.wd = w->wd; + hashmap_entry_init(&k1.ent, memhash(&k1.wd, sizeof(int))); + w1 = hashmap_remove_entry(&data->watches, &k1, ent, NULL); + if (!w1) + BUG("double remove of watch for '%s'", w->dir); + + if (w1->cookie) + BUG("removing watch for '%s' which has a pending rename", w1->dir); + + k2.dir = w->dir; + hashmap_entry_init(&k2.ent, strhash(k2.dir)); + w2 = hashmap_remove_entry(&data->revwatches, &k2, ent, NULL); + if (!w2) + BUG("double remove of reverse watch for '%s'", w->dir); + + /* w1->dir and w2->dir are interned strings, we don't own them */ + free(w1); + free(w2); +} + +/* + * Check for stale directory renames. + * + * https://man7.org/linux/man-pages/man7/inotify.7.html + * + * Allow for some small timeout to account for the fact that insertion of the + * IN_MOVED_FROM+IN_MOVED_TO event pair is not atomic, and the possibility that + * there may not be any IN_MOVED_TO event. + * + * If the IN_MOVED_TO event is not received within the timeout then events have + * been missed and the monitor is in an inconsistent state with respect to the + * filesystem. + */ +static int check_stale_dir_renames(struct hashmap *renames, time_t max_age) +{ + struct rename_entry *re; + struct hashmap_iter iter; + + hashmap_for_each_entry(renames, &iter, re, ent) { + if (re->whence <= max_age) + return -1; + } + return 0; +} + +/* + * Track pending renames. + * + * Tracking is done via an event cookie to watch descriptor mapping. + * + * A rename is not complete until matching an IN_MOVED_TO event is received + * for a corresponding IN_MOVED_FROM event. + */ +static void add_dir_rename(uint32_t cookie, const char *path, + struct fsm_listen_data *data) +{ + struct watch_entry k, *w; + struct rename_entry *re; + + /* lookup the watch descriptor for the given path */ + k.dir = path; + hashmap_entry_init(&k.ent, strhash(path)); + w = hashmap_get_entry(&data->revwatches, &k, ent, NULL); + if (!w) { + /* + * This can happen in rare cases where the directory was + * moved before we had a chance to add a watch on it. + * Just ignore this rename. + */ + trace_printf_key(&trace_fsmonitor, + "no watch found for rename from '%s'", path); + return; + } + w->cookie = cookie; + + /* add the pending rename to match against later */ + CALLOC_ARRAY(re, 1); + re->dir = w->dir; + re->cookie = w->cookie; + re->whence = time(NULL); + hashmap_entry_init(&re->ent, memhash(&re->cookie, sizeof(uint32_t))); + hashmap_add(&data->renames, &re->ent); +} + +/* + * Handle directory renames + * + * Once an IN_MOVED_TO event is received, lookup the rename tracking information + * via the event cookie and use this information to update the watch. + */ +static void rename_dir(uint32_t cookie, const char *path, + struct fsm_listen_data *data) +{ + struct rename_entry rek, *re; + struct watch_entry k, *w; + + /* lookup a pending rename to match */ + rek.cookie = cookie; + hashmap_entry_init(&rek.ent, memhash(&rek.cookie, sizeof(uint32_t))); + re = hashmap_get_entry(&data->renames, &rek, ent, NULL); + if (re) { + k.dir = re->dir; + hashmap_entry_init(&k.ent, strhash(k.dir)); + w = hashmap_get_entry(&data->revwatches, &k, ent, NULL); + if (w) { + w->cookie = 0; /* rename handled */ + remove_watch(w, data); + if (add_watch(path, data)) + trace_printf_key(&trace_fsmonitor, + "failed to add watch for renamed dir '%s'", + path); + } else { + /* Directory was moved out of watch tree */ + trace_printf_key(&trace_fsmonitor, + "no matching watch for rename to '%s'", path); + } + hashmap_remove_entry(&data->renames, &rek, ent, NULL); + free(re); + } else { + /* Directory was moved from outside the watch tree */ + trace_printf_key(&trace_fsmonitor, + "no matching cookie for rename to '%s'", path); + } +} + +/* + * Recursively add watches to every directory under path + */ +static int register_inotify(const char *path, + struct fsmonitor_daemon_state *state, + struct fsmonitor_batch *batch) +{ + DIR *dir; + const char *rel; + struct strbuf current = STRBUF_INIT; + struct dirent *de; + struct stat fs; + int ret = -1; + + dir = opendir(path); + if (!dir) { + if (errno == ENOENT || errno == ENOTDIR) + return 0; /* directory was deleted */ + return error_errno(_("opendir('%s') failed"), path); + } + + while ((de = readdir_skip_dot_and_dotdot(dir)) != NULL) { + strbuf_reset(¤t); + strbuf_addf(¤t, "%s/%s", path, de->d_name); + if (lstat(current.buf, &fs)) { + if (errno == ENOENT) + continue; /* file was deleted */ + error_errno(_("lstat('%s') failed"), current.buf); + goto failed; + } + + /* recurse into directory */ + if (S_ISDIR(fs.st_mode)) { + if (add_watch(current.buf, state->listen_data)) + goto failed; + if (register_inotify(current.buf, state, batch)) + goto failed; + } else if (batch) { + rel = current.buf + state->path_worktree_watch.len + 1; + trace_printf_key(&trace_fsmonitor, "explicitly adding '%s'", rel); + fsmonitor_batch__add_path(batch, rel); + } + } + ret = 0; + +failed: + strbuf_release(¤t); + if (closedir(dir) < 0) + return error_errno(_("closedir('%s') failed"), path); + return ret; +} + +static int em_rename_dir_from(uint32_t mask) +{ + return ((mask & IN_ISDIR) && (mask & IN_MOVED_FROM)); +} + +static int em_rename_dir_to(uint32_t mask) +{ + return ((mask & IN_ISDIR) && (mask & IN_MOVED_TO)); +} + +static int em_remove_watch(uint32_t mask) +{ + return (mask & IN_DELETE_SELF); +} + +static int em_dir_renamed(uint32_t mask) +{ + return ((mask & IN_ISDIR) && (mask & IN_MOVE)); +} + +static int em_dir_created(uint32_t mask) +{ + return ((mask & IN_ISDIR) && (mask & IN_CREATE)); +} + +static int em_dir_deleted(uint32_t mask) +{ + return ((mask & IN_ISDIR) && (mask & IN_DELETE)); +} + +static int em_force_shutdown(uint32_t mask) +{ + return (mask & IN_UNMOUNT) || (mask & IN_Q_OVERFLOW); +} + +static int em_ignore(uint32_t mask) +{ + return (mask & IN_IGNORED) || (mask & IN_MOVE_SELF); +} + +static void log_mask_set(const char *path, uint32_t mask) +{ + struct strbuf msg = STRBUF_INIT; + + if (mask & IN_ACCESS) + strbuf_addstr(&msg, "IN_ACCESS|"); + if (mask & IN_MODIFY) + strbuf_addstr(&msg, "IN_MODIFY|"); + if (mask & IN_ATTRIB) + strbuf_addstr(&msg, "IN_ATTRIB|"); + if (mask & IN_CLOSE_WRITE) + strbuf_addstr(&msg, "IN_CLOSE_WRITE|"); + if (mask & IN_CLOSE_NOWRITE) + strbuf_addstr(&msg, "IN_CLOSE_NOWRITE|"); + if (mask & IN_OPEN) + strbuf_addstr(&msg, "IN_OPEN|"); + if (mask & IN_MOVED_FROM) + strbuf_addstr(&msg, "IN_MOVED_FROM|"); + if (mask & IN_MOVED_TO) + strbuf_addstr(&msg, "IN_MOVED_TO|"); + if (mask & IN_CREATE) + strbuf_addstr(&msg, "IN_CREATE|"); + if (mask & IN_DELETE) + strbuf_addstr(&msg, "IN_DELETE|"); + if (mask & IN_DELETE_SELF) + strbuf_addstr(&msg, "IN_DELETE_SELF|"); + if (mask & IN_MOVE_SELF) + strbuf_addstr(&msg, "IN_MOVE_SELF|"); + if (mask & IN_UNMOUNT) + strbuf_addstr(&msg, "IN_UNMOUNT|"); + if (mask & IN_Q_OVERFLOW) + strbuf_addstr(&msg, "IN_Q_OVERFLOW|"); + if (mask & IN_IGNORED) + strbuf_addstr(&msg, "IN_IGNORED|"); + if (mask & IN_ISDIR) + strbuf_addstr(&msg, "IN_ISDIR|"); + + strbuf_strip_suffix(&msg, "|"); + + trace_printf_key(&trace_fsmonitor, "inotify_event: '%s', mask=%#8.8x %s", + path, mask, msg.buf); + + strbuf_release(&msg); +} + +int fsm_listen__ctor(struct fsmonitor_daemon_state *state) +{ + int fd; + int ret = 0; + struct fsm_listen_data *data; + + CALLOC_ARRAY(data, 1); + state->listen_data = data; + state->listen_error_code = -1; + data->fd_inotify = -1; + data->shutdown = SHUTDOWN_ERROR; + + fd = inotify_init1(O_NONBLOCK); + if (fd < 0) { + FREE_AND_NULL(state->listen_data); + return error_errno(_("inotify_init1() failed")); + } + + data->fd_inotify = fd; + + hashmap_init(&data->watches, watch_entry_cmp, NULL, 0); + hashmap_init(&data->renames, rename_entry_cmp, NULL, 0); + hashmap_init(&data->revwatches, revwatches_entry_cmp, NULL, 0); + + if (add_watch(state->path_worktree_watch.buf, data)) + ret = -1; + else if (register_inotify(state->path_worktree_watch.buf, state, NULL)) + ret = -1; + else if (state->nr_paths_watching > 1) { + if (add_watch(state->path_gitdir_watch.buf, data)) + ret = -1; + else if (register_inotify(state->path_gitdir_watch.buf, state, NULL)) + ret = -1; + } + + if (!ret) { + state->listen_error_code = 0; + data->shutdown = SHUTDOWN_CONTINUE; + } + + return ret; +} + +void fsm_listen__dtor(struct fsmonitor_daemon_state *state) +{ + struct fsm_listen_data *data; + struct hashmap_iter iter; + struct watch_entry *w; + struct watch_entry **to_remove; + size_t nr_to_remove = 0, alloc_to_remove = 0; + size_t i; + int fd; + + if (!state || !state->listen_data) + return; + + data = state->listen_data; + fd = data->fd_inotify; + + /* + * Collect all entries first, then remove them. + * We can't modify the hashmap while iterating over it. + */ + to_remove = NULL; + hashmap_for_each_entry(&data->watches, &iter, w, ent) { + ALLOC_GROW(to_remove, nr_to_remove + 1, alloc_to_remove); + to_remove[nr_to_remove++] = w; + } + + for (i = 0; i < nr_to_remove; i++) { + to_remove[i]->cookie = 0; /* ignore any pending renames */ + remove_watch(to_remove[i], data); + } + free(to_remove); + + hashmap_clear(&data->watches); + + hashmap_clear(&data->revwatches); /* remove_watch freed the entries */ + + hashmap_clear_and_free(&data->renames, struct rename_entry, ent); + + FREE_AND_NULL(state->listen_data); + + if (fd >= 0 && (close(fd) < 0)) + error_errno(_("closing inotify file descriptor failed")); +} + +void fsm_listen__stop_async(struct fsmonitor_daemon_state *state) +{ + if (state && state->listen_data && + state->listen_data->shutdown == SHUTDOWN_CONTINUE) + state->listen_data->shutdown = SHUTDOWN_STOP; +} + +/* + * Process a single inotify event and queue for publication. + */ +static int process_event(const char *path, + const struct inotify_event *event, + struct fsmonitor_batch **batch, + struct string_list *cookie_list, + struct fsmonitor_daemon_state *state) +{ + const char *rel; + const char *last_sep; + + switch (fsmonitor_classify_path_absolute(state, path)) { + case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX: + case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX: + /* Use just the filename of the cookie file. */ + last_sep = find_last_dir_sep(path); + string_list_append(cookie_list, + last_sep ? last_sep + 1 : path); + break; + case IS_INSIDE_DOT_GIT: + case IS_INSIDE_GITDIR: + break; + case IS_DOT_GIT: + case IS_GITDIR: + /* + * If .git directory is deleted or renamed away, + * we have to quit. + */ + if (em_dir_deleted(event->mask)) { + trace_printf_key(&trace_fsmonitor, + "event: gitdir removed"); + state->listen_data->shutdown = SHUTDOWN_FORCE; + goto done; + } + + if (em_dir_renamed(event->mask)) { + trace_printf_key(&trace_fsmonitor, + "event: gitdir renamed"); + state->listen_data->shutdown = SHUTDOWN_FORCE; + goto done; + } + break; + case IS_WORKDIR_PATH: + /* normal events in the working directory */ + if (trace_pass_fl(&trace_fsmonitor)) + log_mask_set(path, event->mask); + + if (!*batch) + *batch = fsmonitor_batch__new(); + + rel = path + state->path_worktree_watch.len + 1; + fsmonitor_batch__add_path(*batch, rel); + + if (em_dir_deleted(event->mask)) + break; + + /* received IN_MOVE_FROM, add tracking for expected IN_MOVE_TO */ + if (em_rename_dir_from(event->mask)) + add_dir_rename(event->cookie, path, state->listen_data); + + /* received IN_MOVE_TO, update watch to reflect new path */ + if (em_rename_dir_to(event->mask)) { + rename_dir(event->cookie, path, state->listen_data); + if (register_inotify(path, state, *batch)) { + state->listen_data->shutdown = SHUTDOWN_ERROR; + goto done; + } + } + + if (em_dir_created(event->mask)) { + if (add_watch(path, state->listen_data)) { + state->listen_data->shutdown = SHUTDOWN_ERROR; + goto done; + } + if (register_inotify(path, state, *batch)) { + state->listen_data->shutdown = SHUTDOWN_ERROR; + goto done; + } + } + break; + case IS_OUTSIDE_CONE: + default: + trace_printf_key(&trace_fsmonitor, + "ignoring '%s'", path); + break; + } + return 0; +done: + return -1; +} + +/* + * Read the inotify event stream and pre-process events before further + * processing and eventual publishing. + */ +static void handle_events(struct fsmonitor_daemon_state *state) +{ + /* See https://man7.org/linux/man-pages/man7/inotify.7.html */ + char buf[4096] + __attribute__ ((aligned(__alignof__(struct inotify_event)))); + + struct hashmap *watches = &state->listen_data->watches; + struct fsmonitor_batch *batch = NULL; + struct string_list cookie_list = STRING_LIST_INIT_DUP; + struct watch_entry k, *w; + struct strbuf path = STRBUF_INIT; + const struct inotify_event *event; + int fd = state->listen_data->fd_inotify; + ssize_t len; + char *ptr, *p; + + for (;;) { + len = read(fd, buf, sizeof(buf)); + if (len == -1) { + if (errno == EAGAIN || errno == EINTR) + goto done; + error_errno(_("reading inotify message stream failed")); + state->listen_data->shutdown = SHUTDOWN_ERROR; + goto done; + } + + /* nothing to read */ + if (len == 0) + goto done; + + /* Loop over all events in the buffer. */ + for (ptr = buf; ptr < buf + len; + ptr += sizeof(struct inotify_event) + event->len) { + + event = (const struct inotify_event *)ptr; + + if (em_ignore(event->mask)) + continue; + + /* File system was unmounted or event queue overflowed */ + if (em_force_shutdown(event->mask)) { + if (trace_pass_fl(&trace_fsmonitor)) + log_mask_set("forcing shutdown", event->mask); + state->listen_data->shutdown = SHUTDOWN_FORCE; + goto done; + } + + k.wd = event->wd; + hashmap_entry_init(&k.ent, memhash(&k.wd, sizeof(int))); + + w = hashmap_get_entry(watches, &k, ent, NULL); + if (!w) { + /* Watch was removed, skip event */ + continue; + } + + /* directory watch was removed */ + if (em_remove_watch(event->mask)) { + remove_watch(w, state->listen_data); + continue; + } + + strbuf_reset(&path); + strbuf_addf(&path, "%s/%s", w->dir, event->name); + + p = fsmonitor__resolve_alias(path.buf, &state->alias); + if (!p) + p = strbuf_detach(&path, NULL); + + if (process_event(p, event, &batch, &cookie_list, state)) { + free(p); + goto done; + } + free(p); + } + strbuf_reset(&path); + fsmonitor_publish(state, batch, &cookie_list); + string_list_clear(&cookie_list, 0); + batch = NULL; + } +done: + strbuf_release(&path); + fsmonitor_batch__free_list(batch); + string_list_clear(&cookie_list, 0); +} + +/* + * Non-blocking read of the inotify events stream. The inotify fd is polled + * frequently to help minimize the number of queue overflows. + */ +void fsm_listen__loop(struct fsmonitor_daemon_state *state) +{ + int poll_num; + /* + * Interval in seconds between checks for stale directory renames. + * A directory rename that is not completed within this window + * (i.e. no matching IN_MOVED_TO for an IN_MOVED_FROM) indicates + * missed events, forcing a shutdown. + */ + const int interval = 1; + time_t checked = time(NULL); + struct pollfd fds[1]; + + fds[0].fd = state->listen_data->fd_inotify; + fds[0].events = POLLIN; + + /* + * Our fs event listener is now running, so it's safe to start + * serving client requests. + */ + ipc_server_start_async(state->ipc_server_data); + + for (;;) { + switch (state->listen_data->shutdown) { + case SHUTDOWN_CONTINUE: + poll_num = poll(fds, 1, 50); + if (poll_num == -1) { + if (errno == EINTR) + continue; + error_errno(_("polling inotify message stream failed")); + state->listen_data->shutdown = SHUTDOWN_ERROR; + continue; + } + + if ((time(NULL) - checked) >= interval) { + checked = time(NULL); + if (check_stale_dir_renames(&state->listen_data->renames, + checked - interval)) { + trace_printf_key(&trace_fsmonitor, + "missed IN_MOVED_TO events, forcing shutdown"); + state->listen_data->shutdown = SHUTDOWN_FORCE; + continue; + } + } + + if (poll_num > 0 && (fds[0].revents & POLLIN)) + handle_events(state); + + continue; + case SHUTDOWN_ERROR: + state->listen_error_code = -1; + ipc_server_stop_async(state->ipc_server_data); + break; + case SHUTDOWN_FORCE: + state->listen_error_code = 0; + ipc_server_stop_async(state->ipc_server_data); + break; + case SHUTDOWN_STOP: + default: + state->listen_error_code = 0; + break; + } + return; + } +}
diff --git a/compat/fsmonitor/fsm-path-utils-linux.c b/compat/fsmonitor/fsm-path-utils-linux.c new file mode 100644 index 0000000..c9866b1 --- /dev/null +++ b/compat/fsmonitor/fsm-path-utils-linux.c
@@ -0,0 +1,217 @@ +#include "git-compat-util.h" +#include "fsmonitor-ll.h" +#include "fsmonitor-path-utils.h" +#include "gettext.h" +#include "trace.h" + +#include <sys/statfs.h> + +#ifdef HAVE_LINUX_MAGIC_H +#include <linux/magic.h> +#endif + +/* + * Filesystem magic numbers for remote filesystems. + * Defined here if not available in linux/magic.h. + */ +#ifndef CIFS_SUPER_MAGIC +#define CIFS_SUPER_MAGIC 0xff534d42 +#endif +#ifndef SMB_SUPER_MAGIC +#define SMB_SUPER_MAGIC 0x517b +#endif +#ifndef SMB2_SUPER_MAGIC +#define SMB2_SUPER_MAGIC 0xfe534d42 +#endif +#ifndef NFS_SUPER_MAGIC +#define NFS_SUPER_MAGIC 0x6969 +#endif +#ifndef AFS_SUPER_MAGIC +#define AFS_SUPER_MAGIC 0x5346414f +#endif +#ifndef CODA_SUPER_MAGIC +#define CODA_SUPER_MAGIC 0x73757245 +#endif +#ifndef FUSE_SUPER_MAGIC +#define FUSE_SUPER_MAGIC 0x65735546 +#endif + +/* + * Check if filesystem type is a remote filesystem. + */ +static int is_remote_fs(unsigned long f_type) +{ + switch (f_type) { + case CIFS_SUPER_MAGIC: + case SMB_SUPER_MAGIC: + case SMB2_SUPER_MAGIC: + case NFS_SUPER_MAGIC: + case AFS_SUPER_MAGIC: + case CODA_SUPER_MAGIC: + case FUSE_SUPER_MAGIC: + return 1; + default: + return 0; + } +} + +/* + * Map filesystem magic numbers to human-readable names as a fallback + * when /proc/mounts is unavailable. This only covers the remote and + * special filesystems in is_remote_fs() above; local filesystems are + * never flagged as incompatible, so we do not need their names here. + */ +static const char *get_fs_typename(unsigned long f_type) +{ + switch (f_type) { + case CIFS_SUPER_MAGIC: + return "cifs"; + case SMB_SUPER_MAGIC: + return "smb"; + case SMB2_SUPER_MAGIC: + return "smb2"; + case NFS_SUPER_MAGIC: + return "nfs"; + case AFS_SUPER_MAGIC: + return "afs"; + case CODA_SUPER_MAGIC: + return "coda"; + case FUSE_SUPER_MAGIC: + return "fuse"; + default: + return "unknown"; + } +} + +/* + * Find the mount point for a given path by reading /proc/mounts. + * + * statfs(2) gives us f_type (the magic number) but not the human-readable + * filesystem type string. We scan /proc/mounts to find the mount entry + * whose path is the longest prefix of ours and whose f_fsid matches, + * which gives us the fstype string (e.g. "nfs", "ext4") for logging. + */ +static char *find_mount(const char *path, const struct statfs *path_fs) +{ + FILE *fp; + struct strbuf line = STRBUF_INIT; + struct strbuf match = STRBUF_INIT; + struct strbuf fstype = STRBUF_INIT; + char *result = NULL; + + fp = fopen("/proc/mounts", "r"); + if (!fp) + return NULL; + + while (strbuf_getline(&line, fp) != EOF) { + char *fields[6]; + char *p = line.buf; + int i; + + /* Parse mount entry: device mountpoint fstype options dump pass */ + for (i = 0; i < 6 && p; i++) { + fields[i] = p; + p = strchr(p, ' '); + if (p) + *p++ = '\0'; + } + + if (i >= 3) { + const char *mountpoint = fields[1]; + const char *type = fields[2]; + struct statfs mount_fs; + + /* Check if this mount point is a prefix of our path */ + if (starts_with(path, mountpoint) && + (path[strlen(mountpoint)] == '/' || + path[strlen(mountpoint)] == '\0')) { + /* Check if filesystem ID matches */ + if (statfs(mountpoint, &mount_fs) == 0 && + !memcmp(&mount_fs.f_fsid, &path_fs->f_fsid, + sizeof(mount_fs.f_fsid))) { + /* Keep the longest matching mount point */ + if (strlen(mountpoint) > match.len) { + strbuf_reset(&match); + strbuf_addstr(&match, mountpoint); + strbuf_reset(&fstype); + strbuf_addstr(&fstype, type); + } + } + } + } + } + + fclose(fp); + strbuf_release(&line); + strbuf_release(&match); + + if (fstype.len) + result = strbuf_detach(&fstype, NULL); + else + strbuf_release(&fstype); + + return result; +} + +int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info) +{ + struct statfs fs; + + if (statfs(path, &fs) == -1) { + int saved_errno = errno; + trace_printf_key(&trace_fsmonitor, "statfs('%s') failed: %s", + path, strerror(saved_errno)); + errno = saved_errno; + return -1; + } + + trace_printf_key(&trace_fsmonitor, + "statfs('%s') [type 0x%08lx]", + path, (unsigned long)fs.f_type); + + fs_info->is_remote = is_remote_fs(fs.f_type); + + /* + * Try to get filesystem type from /proc/mounts for a more + * descriptive name. + */ + fs_info->typename = find_mount(path, &fs); + if (!fs_info->typename) + fs_info->typename = xstrdup(get_fs_typename(fs.f_type)); + + trace_printf_key(&trace_fsmonitor, + "'%s' is_remote: %d, typename: %s", + path, fs_info->is_remote, fs_info->typename); + + return 0; +} + +int fsmonitor__is_fs_remote(const char *path) +{ + struct fs_info fs; + + if (fsmonitor__get_fs_info(path, &fs)) + return -1; + + free(fs.typename); + + return fs.is_remote; +} + +/* + * No-op for Linux - we don't have firmlinks like macOS. + */ +int fsmonitor__get_alias(const char *path UNUSED, + struct alias_info *info UNUSED) +{ + return 0; +} + +/* + * No-op for Linux - we don't have firmlinks like macOS. + */ +char *fsmonitor__resolve_alias(const char *path UNUSED, + const struct alias_info *info UNUSED) +{ + return NULL; +}
diff --git a/compat/fsmonitor/fsm-settings-darwin.c b/compat/fsmonitor/fsm-settings-unix.c similarity index 100% rename from compat/fsmonitor/fsm-settings-darwin.c rename to compat/fsmonitor/fsm-settings-unix.c
diff --git a/compat/mingw.c b/compat/mingw.c index 2023c16..41e055f 100644 --- a/compat/mingw.c +++ b/compat/mingw.c
@@ -13,6 +13,7 @@ #include "symlinks.h" #include "trace2.h" #include "win32.h" +#include "win32/exit-process.h" #include "win32/lazyload.h" #include "wrapper.h" #include <aclapi.h> @@ -449,20 +450,63 @@ static wchar_t *normalize_ntpath(wchar_t *wbuf) return wbuf; } +/* + * Use SetFileInformationByHandle(FileDispositionInfo) to force legacy + * (non-POSIX) delete semantics. On Windows 11, DeleteFileW() uses POSIX + * delete semantics internally, allowing deletion even with active + * MapViewOfFile views. This helper simulates Windows 10 behavior where + * deletion fails if a file mapping exists. + * + * Returns nonzero on success (like DeleteFileW), 0 on failure. + */ +static int legacy_delete_file(const wchar_t *wpathname) +{ + FILE_DISPOSITION_INFO fdi = { TRUE }; + DWORD gle; + HANDLE h = CreateFileW(wpathname, DELETE, + FILE_SHARE_READ | FILE_SHARE_WRITE | + FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT, NULL); + if (h == INVALID_HANDLE_VALUE) + return 0; + + if (SetFileInformationByHandle(h, FileDispositionInfo, + &fdi, sizeof(fdi))) { + CloseHandle(h); + return 1; + } + gle = GetLastError(); + CloseHandle(h); + SetLastError(gle); + return 0; +} + +static int try_delete_file(const wchar_t *wpathname, int use_legacy) +{ + if (use_legacy) + return legacy_delete_file(wpathname); + return DeleteFileW(wpathname); +} + int mingw_unlink(const char *pathname, int handle_in_use_error) { + static int use_legacy_delete = -1; int tries = 0; wchar_t wpathname[MAX_PATH]; if (xutftowcs_path(wpathname, pathname) < 0) return -1; - if (DeleteFileW(wpathname)) + if (use_legacy_delete < 0) + use_legacy_delete = git_env_bool("GIT_TEST_LEGACY_DELETE", 0); + + if (try_delete_file(wpathname, use_legacy_delete)) return 0; do { /* read-only files cannot be removed */ _wchmod(wpathname, 0666); - if (!_wunlink(wpathname)) + if (try_delete_file(wpathname, use_legacy_delete)) return 0; if (!is_file_in_use_error(GetLastError())) break; @@ -2208,16 +2252,28 @@ int mingw_execvp(const char *cmd, char *const *argv) int mingw_kill(pid_t pid, int sig) { if (pid > 0 && sig == SIGTERM) { - HANDLE h = OpenProcess(PROCESS_TERMINATE, FALSE, pid); + HANDLE h = OpenProcess(PROCESS_CREATE_THREAD | + PROCESS_QUERY_INFORMATION | + PROCESS_VM_OPERATION | PROCESS_VM_WRITE | + PROCESS_VM_READ | PROCESS_TERMINATE, + FALSE, pid); + int ret; - if (TerminateProcess(h, -1)) { - CloseHandle(h); - return 0; + if (h) + ret = exit_process(h, 128 + sig); + else { + h = OpenProcess(PROCESS_TERMINATE, FALSE, pid); + if (!h) { + errno = err_win_to_posix(GetLastError()); + return -1; + } + ret = terminate_process_tree(h, 128 + sig); } - - errno = err_win_to_posix(GetLastError()); - CloseHandle(h); - return -1; + if (ret) { + errno = err_win_to_posix(GetLastError()); + CloseHandle(h); + } + return ret; } else if (pid > 0 && sig == 0) { HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); if (h) { @@ -3567,7 +3623,14 @@ static void adjust_symlink_flags(void) symlink_file_flags |= 2; symlink_directory_flags |= 2; } +} +static BOOL WINAPI handle_ctrl_c(DWORD ctrl_type) +{ + if (ctrl_type != CTRL_C_EVENT) + return FALSE; /* we did not handle this */ + mingw_raise(SIGINT); + return TRUE; /* we did handle this */ } #ifdef _MSC_VER @@ -3604,6 +3667,8 @@ int wmain(int argc, const wchar_t **wargv) #endif #endif + SetConsoleCtrlHandler(handle_ctrl_c, TRUE); + maybe_redirect_std_handles(); adjust_symlink_flags();
diff --git a/compat/nedmalloc/License.txt b/compat/nedmalloc/License.txt deleted file mode 100644 index 36b7cd9..0000000 --- a/compat/nedmalloc/License.txt +++ /dev/null
@@ -1,23 +0,0 @@ -Boost Software License - Version 1.0 - August 17th, 2003 - -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: - -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE.
diff --git a/compat/nedmalloc/Readme.txt b/compat/nedmalloc/Readme.txt deleted file mode 100644 index 07cbf50..0000000 --- a/compat/nedmalloc/Readme.txt +++ /dev/null
@@ -1,136 +0,0 @@ -nedalloc v1.05 15th June 2008: --=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - -by Niall Douglas (http://www.nedprod.com/programs/portable/nedmalloc/) - -Enclosed is nedalloc, an alternative malloc implementation for multiple -threads without lock contention based on dlmalloc v2.8.4. It is more -or less a newer implementation of ptmalloc2, the standard allocator in -Linux (which is based on dlmalloc v2.7.0) but also contains a per-thread -cache for maximum CPU scalability. - -It is licensed under the Boost Software License which basically means -you can do anything you like with it. This does not apply to the malloc.c.h -file which remains copyright to others. - -It has been tested on win32 (x86), win64 (x64), Linux (x64), FreeBSD (x64) -and Apple MacOS X (x86). It works very well on all of these and is very -significantly faster than the system allocator on all of these platforms. - -By literally dropping in this allocator as a replacement for your system -allocator, you can see real world improvements of up to three times in normal -code! - -To use: --=-=-=- -Drop in nedmalloc.h, nedmalloc.c and malloc.c.h into your project. -Configure using the instructions in nedmalloc.h. Run and enjoy. - -To test, compile test.c. It will run a comparison between your system -allocator and nedalloc and tell you how much faster nedalloc is. It also -serves as an example of usage. - -Notes: --=-=-= -If you want the very latest version of this allocator, get it from the -TnFOX SVN repository at svn://svn.berlios.de/viewcvs/tnfox/trunk/src/nedmalloc - -Because of how nedalloc allocates an mspace per thread, it can cause -severe bloating of memory usage under certain allocation patterns. -You can substantially reduce this wastage by setting MAXTHREADSINPOOL -or the threads parameter to nedcreatepool() to a fraction of the number of -threads which would normally be in a pool at once. This will reduce -bloating at the cost of an increase in lock contention. If allocated size -is less than THREADCACHEMAX, locking is avoided 90-99% of the time and -if most of your allocations are below this value, you can safely set -MAXTHREADSINPOOL to one. - -You will suffer memory leakage unless you call neddisablethreadcache() -per pool for every thread which exits. This is because nedalloc cannot -portably know when a thread exits and thus when its thread cache can -be returned for use by other code. Don't forget pool zero, the system pool. - -For C++ type allocation patterns (where the same sizes of memory are -regularly allocated and deallocated as objects are created and destroyed), -the threadcache always benefits performance. If however your allocation -patterns are different, searching the threadcache may significantly slow -down your code - as a rule of thumb, if cache utilisation is below 80% -(see the source for neddisablethreadcache() for how to enable debug -printing in release mode) then you should disable the thread cache for -that thread. You can compile out the threadcache code by setting -THREADCACHEMAX to zero. - -Speed comparisons: --=-=-=-=-=-=-=-=-= -See Benchmarks.xls for details. - -The enclosed test.c can do two things: it can be a torture test or a speed -test. The speed test is designed to be a representative synthetic -memory allocator test. It works by randomly mixing allocations with frees -with half of the allocation sizes being a two power multiple less than -512 bytes (to mimic C++ stack instantiated objects) and the other half -being a simple random value less than 16Kb. - -The real world code results are from Tn's TestIO benchmark. This is a -heavily multithreaded and memory intensive benchmark with a lot of branching -and other stuff modern processors don't like so much. As you'll note, the -test doesn't show the benefits of the threadcache mostly due to the saturation -of the memory bus being the limiting factor. - -ChangeLog: --=-=-=-=-= -v1.05 15th June 2008: - * { 1042 } Added error check for TLSSET() and TLSFREE() macros. Thanks to -Markus Elfring for reporting this. - * { 1043 } Fixed a segfault when freeing memory allocated using -nedindependent_comalloc(). Thanks to Pavel Vozenilek for reporting this. - -v1.04 14th July 2007: - * Fixed a bug with the new optimised implementation that failed to lock -on a realloc under certain conditions. - * Fixed lack of thread synchronisation in InitPool() causing pool corruption - * Fixed a memory leak of thread cache contents on disabling. Thanks to Earl -Chew for reporting this. - * Added a sanity check for freed blocks being valid. - * Reworked test.c into being a torture test. - * Fixed GCC assembler optimisation misspecification - -v1.04alpha_svn915 7th October 2006: - * Fixed failure to unlock thread cache list if allocating a new list failed. -Thanks to Dmitry Chichkov for reporting this. Further thanks to Aleksey Sanin. - * Fixed realloc(0, <size>) segfaulting. Thanks to Dmitry Chichkov for -reporting this. - * Made config defines #ifndef so they can be overridden by the build system. -Thanks to Aleksey Sanin for suggesting this. - * Fixed deadlock in nedprealloc() due to unnecessary locking of preferred -thread mspace when mspace_realloc() always uses the original block's mspace -anyway. Thanks to Aleksey Sanin for reporting this. - * Made some speed improvements by hacking mspace_malloc() to no longer lock -its mspace, thus allowing the recursive mutex implementation to be removed -with an associated speed increase. Thanks to Aleksey Sanin for suggesting this. - * Fixed a bug where allocating mspaces overran its max limit. Thanks to -Aleksey Sanin for reporting this. - -v1.03 10th July 2006: - * Fixed memory corruption bug in threadcache code which only appeared with >4 -threads and in heavy use of the threadcache. - -v1.02 15th May 2006: - * Integrated dlmalloc v2.8.4, fixing the win32 memory release problem and -improving performance still further. Speed is now up to twice the speed of v1.01 -(average is 67% faster). - * Fixed win32 critical section implementation. Thanks to Pavel Kuznetsov -for reporting this. - * Wasn't locking mspace if all mspaces were locked. Thanks to Pavel Kuznetsov -for reporting this. - * Added Apple Mac OS X support. - -v1.01 24th February 2006: - * Fixed multiprocessor scaling problems by removing sources of cache sloshing - * Earl Chew <earl_chew <at> agilent <dot> com> sent patches for the following: - 1. size2binidx() wasn't working for default code path (non x86) - 2. Fixed failure to release mspace lock under certain circumstances which - caused a deadlock - -v1.00 1st January 2006: - * First release
diff --git a/compat/nedmalloc/malloc.c.h b/compat/nedmalloc/malloc.c.h deleted file mode 100644 index e0c5675..0000000 --- a/compat/nedmalloc/malloc.c.h +++ /dev/null
@@ -1,5761 +0,0 @@ -/* - This is a version (aka dlmalloc) of malloc/free/realloc written by - Doug Lea and released to the public domain, as explained at - http://creativecommons.org/licenses/publicdomain. Send questions, - comments, complaints, performance data, etc to dl@cs.oswego.edu - -* Version pre-2.8.4 Mon Nov 27 11:22:37 2006 (dl at gee) - - Note: There may be an updated version of this malloc obtainable at - ftp://gee.cs.oswego.edu/pub/misc/malloc.c - Check before installing! - -* Quickstart - - This library is all in one file to simplify the most common usage: - ftp it, compile it (-O3), and link it into another program. All of - the compile-time options default to reasonable values for use on - most platforms. You might later want to step through various - compile-time and dynamic tuning options. - - For convenience, an include file for code using this malloc is at: - ftp://gee.cs.oswego.edu/pub/misc/malloc-2.8.4.h - You don't really need this .h file unless you call functions not - defined in your system include files. The .h file contains only the - excerpts from this file needed for using this malloc on ANSI C/C++ - systems, so long as you haven't changed compile-time options about - naming and tuning parameters. If you do, then you can create your - own malloc.h that does include all settings by cutting at the point - indicated below. Note that you may already by default be using a C - library containing a malloc that is based on some version of this - malloc (for example in linux). You might still want to use the one - in this file to customize settings or to avoid overheads associated - with library versions. - -* Vital statistics: - - Supported pointer/size_t representation: 4 or 8 bytes - size_t MUST be an unsigned type of the same width as - pointers. (If you are using an ancient system that declares - size_t as a signed type, or need it to be a different width - than pointers, you can use a previous release of this malloc - (e.g. 2.7.2) supporting these.) - - Alignment: 8 bytes (default) - This suffices for nearly all current machines and C compilers. - However, you can define MALLOC_ALIGNMENT to be wider than this - if necessary (up to 128bytes), at the expense of using more space. - - Minimum overhead per allocated chunk: 4 or 8 bytes (if 4byte sizes) - 8 or 16 bytes (if 8byte sizes) - Each malloced chunk has a hidden word of overhead holding size - and status information, and additional cross-check word - if FOOTERS is defined. - - Minimum allocated size: 4-byte ptrs: 16 bytes (including overhead) - 8-byte ptrs: 32 bytes (including overhead) - - Even a request for zero bytes (i.e., malloc(0)) returns a - pointer to something of the minimum allocatable size. - The maximum overhead wastage (i.e., number of extra bytes - allocated than were requested in malloc) is less than or equal - to the minimum size, except for requests >= mmap_threshold that - are serviced via mmap(), where the worst case wastage is about - 32 bytes plus the remainder from a system page (the minimal - mmap unit); typically 4096 or 8192 bytes. - - Security: static-safe; optionally more or less - The "security" of malloc refers to the ability of malicious - code to accentuate the effects of errors (for example, freeing - space that is not currently malloc'ed or overwriting past the - ends of chunks) in code that calls malloc. This malloc - guarantees not to modify any memory locations below the base of - heap, i.e., static variables, even in the presence of usage - errors. The routines additionally detect most improper frees - and reallocs. All this holds as long as the static bookkeeping - for malloc itself is not corrupted by some other means. This - is only one aspect of security -- these checks do not, and - cannot, detect all possible programming errors. - - If FOOTERS is defined nonzero, then each allocated chunk - carries an additional check word to verify that it was malloced - from its space. These check words are the same within each - execution of a program using malloc, but differ across - executions, so externally crafted fake chunks cannot be - freed. This improves security by rejecting frees/reallocs that - could corrupt heap memory, in addition to the checks preventing - writes to statics that are always on. This may further improve - security at the expense of time and space overhead. (Note that - FOOTERS may also be worth using with MSPACES.) - - By default detected errors cause the program to abort (calling - "abort()"). You can override this to instead proceed past - errors by defining PROCEED_ON_ERROR. In this case, a bad free - has no effect, and a malloc that encounters a bad address - caused by user overwrites will ignore the bad address by - dropping pointers and indices to all known memory. This may - be appropriate for programs that should continue if at all - possible in the face of programming errors, although they may - run out of memory because dropped memory is never reclaimed. - - If you don't like either of these options, you can define - CORRUPTION_ERROR_ACTION and USAGE_ERROR_ACTION to do anything - else. And if you are sure that your program using malloc has - no errors or vulnerabilities, you can define INSECURE to 1, - which might (or might not) provide a small performance improvement. - - Thread-safety: NOT thread-safe unless USE_LOCKS defined - When USE_LOCKS is defined, each public call to malloc, free, - etc is surrounded with either a pthread mutex or a win32 - spinlock (depending on WIN32). This is not especially fast, and - can be a major bottleneck. It is designed only to provide - minimal protection in concurrent environments, and to provide a - basis for extensions. If you are using malloc in a concurrent - program, consider instead using nedmalloc - (http://www.nedprod.com/programs/portable/nedmalloc/) or - ptmalloc (See http://www.malloc.de), which are derived - from versions of this malloc. - - System requirements: Any combination of MORECORE and/or MMAP/MUNMAP - This malloc can use unix sbrk or any emulation (invoked using - the CALL_MORECORE macro) and/or mmap/munmap or any emulation - (invoked using CALL_MMAP/CALL_MUNMAP) to get and release system - memory. On most unix systems, it tends to work best if both - MORECORE and MMAP are enabled. On Win32, it uses emulations - based on VirtualAlloc. It also uses common C library functions - like memset. - - Compliance: I believe it is compliant with the Single Unix Specification - (See http://www.unix.org). Also SVID/XPG, ANSI C, and probably - others as well. - -* Overview of algorithms - - This is not the fastest, most space-conserving, most portable, or - most tunable malloc ever written. However it is among the fastest - while also being among the most space-conserving, portable and - tunable. Consistent balance across these factors results in a good - general-purpose allocator for malloc-intensive programs. - - In most ways, this malloc is a best-fit allocator. Generally, it - chooses the best-fitting existing chunk for a request, with ties - broken in approximately least-recently-used order. (This strategy - normally maintains low fragmentation.) However, for requests less - than 256bytes, it deviates from best-fit when there is not an - exactly fitting available chunk by preferring to use space adjacent - to that used for the previous small request, as well as by breaking - ties in approximately most-recently-used order. (These enhance - locality of series of small allocations.) And for very large requests - (>= 256Kb by default), it relies on system memory mapping - facilities, if supported. (This helps avoid carrying around and - possibly fragmenting memory used only for large chunks.) - - All operations (except malloc_stats and mallinfo) have execution - times that are bounded by a constant factor of the number of bits in - a size_t, not counting any clearing in calloc or copying in realloc, - or actions surrounding MORECORE and MMAP that have times - proportional to the number of non-contiguous regions returned by - system allocation routines, which is often just 1. In real-time - applications, you can optionally suppress segment traversals using - NO_SEGMENT_TRAVERSAL, which assures bounded execution even when - system allocators return non-contiguous spaces, at the typical - expense of carrying around more memory and increased fragmentation. - - The implementation is not very modular and seriously overuses - macros. Perhaps someday all C compilers will do as good a job - inlining modular code as can now be done by brute-force expansion, - but now, enough of them seem not to. - - Some compilers issue a lot of warnings about code that is - dead/unreachable only on some platforms, and also about intentional - uses of negation on unsigned types. All known cases of each can be - ignored. - - For a longer but out of date high-level description, see - http://gee.cs.oswego.edu/dl/html/malloc.html - -* MSPACES - If MSPACES is defined, then in addition to malloc, free, etc., - this file also defines mspace_malloc, mspace_free, etc. These - are versions of malloc routines that take an "mspace" argument - obtained using create_mspace, to control all internal bookkeeping. - If ONLY_MSPACES is defined, only these versions are compiled. - So if you would like to use this allocator for only some allocations, - and your system malloc for others, you can compile with - ONLY_MSPACES and then do something like... - static mspace mymspace = create_mspace(0,0); // for example - #define mymalloc(bytes) mspace_malloc(mymspace, bytes) - - (Note: If you only need one instance of an mspace, you can instead - use "USE_DL_PREFIX" to relabel the global malloc.) - - You can similarly create thread-local allocators by storing - mspaces as thread-locals. For example: - static __thread mspace tlms = 0; - void* tlmalloc(size_t bytes) { - if (tlms == 0) tlms = create_mspace(0, 0); - return mspace_malloc(tlms, bytes); - } - void tlfree(void* mem) { mspace_free(tlms, mem); } - - Unless FOOTERS is defined, each mspace is completely independent. - You cannot allocate from one and free to another (although - conformance is only weakly checked, so usage errors are not always - caught). If FOOTERS is defined, then each chunk carries around a tag - indicating its originating mspace, and frees are directed to their - originating spaces. - - ------------------------- Compile-time options --------------------------- - -Be careful in setting #define values for numerical constants of type -size_t. On some systems, literal values are not automatically extended -to size_t precision unless they are explicitly casted. You can also -use the symbolic values MAX_SIZE_T, SIZE_T_ONE, etc below. - -WIN32 default: defined if _WIN32 defined - Defining WIN32 sets up defaults for MS environment and compilers. - Otherwise defaults are for unix. Beware that there seem to be some - cases where this malloc might not be a pure drop-in replacement for - Win32 malloc: Random-looking failures from Win32 GDI API's (eg; - SetDIBits()) may be due to bugs in some video driver implementations - when pixel buffers are malloc()ed, and the region spans more than - one VirtualAlloc()ed region. Because dlmalloc uses a small (64Kb) - default granularity, pixel buffers may straddle virtual allocation - regions more often than when using the Microsoft allocator. You can - avoid this by using VirtualAlloc() and VirtualFree() for all pixel - buffers rather than using malloc(). If this is not possible, - recompile this malloc with a larger DEFAULT_GRANULARITY. - -MALLOC_ALIGNMENT default: (size_t)8 - Controls the minimum alignment for malloc'ed chunks. It must be a - power of two and at least 8, even on machines for which smaller - alignments would suffice. It may be defined as larger than this - though. Note however that code and data structures are optimized for - the case of 8-byte alignment. - -MSPACES default: 0 (false) - If true, compile in support for independent allocation spaces. - This is only supported if HAVE_MMAP is true. - -ONLY_MSPACES default: 0 (false) - If true, only compile in mspace versions, not regular versions. - -USE_LOCKS default: 0 (false) - Causes each call to each public routine to be surrounded with - pthread or WIN32 mutex lock/unlock. (If set true, this can be - overridden on a per-mspace basis for mspace versions.) If set to a - non-zero value other than 1, locks are used, but their - implementation is left out, so lock functions must be supplied manually. - -USE_SPIN_LOCKS default: 1 iff USE_LOCKS and on x86 using gcc or MSC - If true, uses custom spin locks for locking. This is currently - supported only for x86 platforms using gcc or recent MS compilers. - Otherwise, posix locks or win32 critical sections are used. - -FOOTERS default: 0 - If true, provide extra checking and dispatching by placing - information in the footers of allocated chunks. This adds - space and time overhead. - -INSECURE default: 0 - If true, omit checks for usage errors and heap space overwrites. - -USE_DL_PREFIX default: NOT defined - Causes compiler to prefix all public routines with the string 'dl'. - This can be useful when you only want to use this malloc in one part - of a program, using your regular system malloc elsewhere. - -ABORT default: defined as abort() - Defines how to abort on failed checks. On most systems, a failed - check cannot die with an "assert" or even print an informative - message, because the underlying print routines in turn call malloc, - which will fail again. Generally, the best policy is to simply call - abort(). It's not very useful to do more than this because many - errors due to overwriting will show up as address faults (null, odd - addresses etc) rather than malloc-triggered checks, so will also - abort. Also, most compilers know that abort() does not return, so - can better optimize code conditionally calling it. - -PROCEED_ON_ERROR default: defined as 0 (false) - Controls whether detected bad addresses cause them to bypassed - rather than aborting. If set, detected bad arguments to free and - realloc are ignored. And all bookkeeping information is zeroed out - upon a detected overwrite of freed heap space, thus losing the - ability to ever return it from malloc again, but enabling the - application to proceed. If PROCEED_ON_ERROR is defined, the - static variable malloc_corruption_error_count is compiled in - and can be examined to see if errors have occurred. This option - generates slower code than the default abort policy. - -DEBUG default: NOT defined - The DEBUG setting is mainly intended for people trying to modify - this code or diagnose problems when porting to new platforms. - However, it may also be able to better isolate user errors than just - using runtime checks. The assertions in the check routines spell - out in more detail the assumptions and invariants underlying the - algorithms. The checking is fairly extensive, and will slow down - execution noticeably. Calling malloc_stats or mallinfo with DEBUG - set will attempt to check every non-mmapped allocated and free chunk - in the course of computing the summaries. - -ABORT_ON_ASSERT_FAILURE default: defined as 1 (true) - Debugging assertion failures can be nearly impossible if your - version of the assert macro causes malloc to be called, which will - lead to a cascade of further failures, blowing the runtime stack. - ABORT_ON_ASSERT_FAILURE cause assertions failures to call abort(), - which will usually make debugging easier. - -MALLOC_FAILURE_ACTION default: sets errno to ENOMEM, or no-op on win32 - The action to take before "return 0" when malloc fails to be able to - return memory because there is none available. - -HAVE_MORECORE default: 1 (true) unless win32 or ONLY_MSPACES - True if this system supports sbrk or an emulation of it. - -MORECORE default: sbrk - The name of the sbrk-style system routine to call to obtain more - memory. See below for guidance on writing custom MORECORE - functions. The type of the argument to sbrk/MORECORE varies across - systems. It cannot be size_t, because it supports negative - arguments, so it is normally the signed type of the same width as - size_t (sometimes declared as "intptr_t"). It doesn't much matter - though. Internally, we only call it with arguments less than half - the max value of a size_t, which should work across all reasonable - possibilities, although sometimes generating compiler warnings. - -MORECORE_CONTIGUOUS default: 1 (true) if HAVE_MORECORE - If true, take advantage of fact that consecutive calls to MORECORE - with positive arguments always return contiguous increasing - addresses. This is true of unix sbrk. It does not hurt too much to - set it true anyway, since malloc copes with non-contiguities. - Setting it false when definitely non-contiguous saves time - and possibly wasted space it would take to discover this though. - -MORECORE_CANNOT_TRIM default: NOT defined - True if MORECORE cannot release space back to the system when given - negative arguments. This is generally necessary only if you are - using a hand-crafted MORECORE function that cannot handle negative - arguments. - -NO_SEGMENT_TRAVERSAL default: 0 - If non-zero, suppresses traversals of memory segments - returned by either MORECORE or CALL_MMAP. This disables - merging of segments that are contiguous, and selectively - releasing them to the OS if unused, but bounds execution times. - -HAVE_MMAP default: 1 (true) - True if this system supports mmap or an emulation of it. If so, and - HAVE_MORECORE is not true, MMAP is used for all system - allocation. If set and HAVE_MORECORE is true as well, MMAP is - primarily used to directly allocate very large blocks. It is also - used as a backup strategy in cases where MORECORE fails to provide - space from system. Note: A single call to MUNMAP is assumed to be - able to unmap memory that may have be allocated using multiple calls - to MMAP, so long as they are adjacent. - -HAVE_MREMAP default: 1 on linux, else 0 - If true realloc() uses mremap() to re-allocate large blocks and - extend or shrink allocation spaces. - -MMAP_CLEARS default: 1 except on WINCE. - True if mmap clears memory so calloc doesn't need to. This is true - for standard unix mmap using /dev/zero and on WIN32 except for WINCE. - -USE_BUILTIN_FFS default: 0 (i.e., not used) - Causes malloc to use the builtin ffs() function to compute indices. - Some compilers may recognize and intrinsify ffs to be faster than the - supplied C version. Also, the case of x86 using gcc is special-cased - to an asm instruction, so is already as fast as it can be, and so - this setting has no effect. Similarly for Win32 under recent MS compilers. - (On most x86s, the asm version is only slightly faster than the C version.) - -malloc_getpagesize default: derive from system includes, or 4096. - The system page size. To the extent possible, this malloc manages - memory from the system in page-size units. This may be (and - usually is) a function rather than a constant. This is ignored - if WIN32, where page size is determined using getSystemInfo during - initialization. - -USE_DEV_RANDOM default: 0 (i.e., not used) - Causes malloc to use /dev/random to initialize secure magic seed for - stamping footers. Otherwise, the current time is used. - -NO_MALLINFO default: 0 - If defined, don't compile "mallinfo". This can be a simple way - of dealing with mismatches between system declarations and - those in this file. - -MALLINFO_FIELD_TYPE default: size_t - The type of the fields in the mallinfo struct. This was originally - defined as "int" in SVID etc, but is more usefully defined as - size_t. The value is used only if HAVE_USR_INCLUDE_MALLOC_H is not set - -REALLOC_ZERO_BYTES_FREES default: not defined - This should be set if a call to realloc with zero bytes should - be the same as a call to free. Some people think it should. Otherwise, - since this malloc returns a unique pointer for malloc(0), so does - realloc(p, 0). - -LACKS_UNISTD_H, LACKS_FCNTL_H, LACKS_SYS_PARAM_H, LACKS_SYS_MMAN_H -LACKS_STRINGS_H, LACKS_STRING_H, LACKS_SYS_TYPES_H, LACKS_ERRNO_H -LACKS_STDLIB_H default: NOT defined unless on WIN32 - Define these if your system does not have these header files. - You might need to manually insert some of the declarations they provide. - -DEFAULT_GRANULARITY default: page size if MORECORE_CONTIGUOUS, - system_info.dwAllocationGranularity in WIN32, - otherwise 64K. - Also settable using mallopt(M_GRANULARITY, x) - The unit for allocating and deallocating memory from the system. On - most systems with contiguous MORECORE, there is no reason to - make this more than a page. However, systems with MMAP tend to - either require or encourage larger granularities. You can increase - this value to prevent system allocation functions to be called so - often, especially if they are slow. The value must be at least one - page and must be a power of two. Setting to 0 causes initialization - to either page size or win32 region size. (Note: In previous - versions of malloc, the equivalent of this option was called - "TOP_PAD") - -DEFAULT_TRIM_THRESHOLD default: 2MB - Also settable using mallopt(M_TRIM_THRESHOLD, x) - The maximum amount of unused top-most memory to keep before - releasing via malloc_trim in free(). Automatic trimming is mainly - useful in long-lived programs using contiguous MORECORE. Because - trimming via sbrk can be slow on some systems, and can sometimes be - wasteful (in cases where programs immediately afterward allocate - more large chunks) the value should be high enough so that your - overall system performance would improve by releasing this much - memory. As a rough guide, you might set to a value close to the - average size of a process (program) running on your system. - Releasing this much memory would allow such a process to run in - memory. Generally, it is worth tuning trim thresholds when a - program undergoes phases where several large chunks are allocated - and released in ways that can reuse each other's storage, perhaps - mixed with phases where there are no such chunks at all. The trim - value must be greater than page size to have any useful effect. To - disable trimming completely, you can set to MAX_SIZE_T. Note that the trick - some people use of mallocing a huge space and then freeing it at - program startup, in an attempt to reserve system memory, doesn't - have the intended effect under automatic trimming, since that memory - will immediately be returned to the system. - -DEFAULT_MMAP_THRESHOLD default: 256K - Also settable using mallopt(M_MMAP_THRESHOLD, x) - The request size threshold for using MMAP to directly service a - request. Requests of at least this size that cannot be allocated - using already-existing space will be serviced via mmap. (If enough - normal freed space already exists it is used instead.) Using mmap - segregates relatively large chunks of memory so that they can be - individually obtained and released from the host system. A request - serviced through mmap is never reused by any other request (at least - not directly; the system may just so happen to remap successive - requests to the same locations). Segregating space in this way has - the benefits that: Mmapped space can always be individually released - back to the system, which helps keep the system level memory demands - of a long-lived program low. Also, mapped memory doesn't become - `locked' between other chunks, as can happen with normally allocated - chunks, which means that even trimming via malloc_trim would not - release them. However, it has the disadvantage that the space - cannot be reclaimed, consolidated, and then used to service later - requests, as happens with normal chunks. The advantages of mmap - nearly always outweigh disadvantages for "large" chunks, but the - value of "large" may vary across systems. The default is an - empirically derived value that works well in most systems. You can - disable mmap by setting to MAX_SIZE_T. - -MAX_RELEASE_CHECK_RATE default: 4095 unless not HAVE_MMAP - The number of consolidated frees between checks to release - unused segments when freeing. When using non-contiguous segments, - especially with multiple mspaces, checking only for topmost space - doesn't always suffice to trigger trimming. To compensate for this, - free() will, with a period of MAX_RELEASE_CHECK_RATE (or the - current number of segments, if greater) try to release unused - segments to the OS when freeing chunks that result in - consolidation. The best value for this parameter is a compromise - between slowing down frees with relatively costly checks that - rarely trigger versus holding on to unused memory. To effectively - disable, set to MAX_SIZE_T. This may lead to a very slight speed - improvement at the expense of carrying around more memory. -*/ - -/* Version identifier to allow people to support multiple versions */ -#ifndef DLMALLOC_VERSION -#define DLMALLOC_VERSION 20804 -#endif /* DLMALLOC_VERSION */ - -#if defined(linux) -#define _GNU_SOURCE 1 -#endif - -#ifndef WIN32 -#ifdef _WIN32 -#define WIN32 1 -#endif /* _WIN32 */ -#ifdef _WIN32_WCE -#define LACKS_FCNTL_H -#define WIN32 1 -#endif /* _WIN32_WCE */ -#endif /* WIN32 */ -#ifdef WIN32 -#define WIN32_LEAN_AND_MEAN -#ifndef _WIN32_WINNT -#define _WIN32_WINNT 0x603 -#endif -#include <windows.h> -#define HAVE_MMAP 1 -#define HAVE_MORECORE 0 -#define LACKS_UNISTD_H -#define LACKS_SYS_PARAM_H -#define LACKS_SYS_MMAN_H -#define LACKS_STRING_H -#define LACKS_STRINGS_H -#define LACKS_SYS_TYPES_H -#define LACKS_ERRNO_H -#ifndef MALLOC_FAILURE_ACTION -#define MALLOC_FAILURE_ACTION -#endif /* MALLOC_FAILURE_ACTION */ -#ifdef _WIN32_WCE /* WINCE reportedly does not clear */ -#define MMAP_CLEARS 0 -#else -#define MMAP_CLEARS 1 -#endif /* _WIN32_WCE */ -#endif /* WIN32 */ - -#if defined(DARWIN) || defined(_DARWIN) -/* Mac OSX docs advise not to use sbrk; it seems better to use mmap */ -#ifndef HAVE_MORECORE -#define HAVE_MORECORE 0 -#define HAVE_MMAP 1 -/* OSX allocators provide 16 byte alignment */ -#ifndef MALLOC_ALIGNMENT -#define MALLOC_ALIGNMENT ((size_t)16U) -#endif -#endif /* HAVE_MORECORE */ -#endif /* DARWIN */ - -#ifndef LACKS_SYS_TYPES_H -#include <sys/types.h> /* For size_t */ -#endif /* LACKS_SYS_TYPES_H */ - -/* The maximum possible size_t value has all bits set */ -#define MAX_SIZE_T (~(size_t)0) - -#ifndef ONLY_MSPACES -#define ONLY_MSPACES 0 /* define to a value */ -#else -#define ONLY_MSPACES 1 -#endif /* ONLY_MSPACES */ -#ifndef MSPACES -#if ONLY_MSPACES -#define MSPACES 1 -#else /* ONLY_MSPACES */ -#define MSPACES 0 -#endif /* ONLY_MSPACES */ -#endif /* MSPACES */ -#ifndef MALLOC_ALIGNMENT -#define MALLOC_ALIGNMENT ((size_t)8U) -#endif /* MALLOC_ALIGNMENT */ -#ifndef FOOTERS -#define FOOTERS 0 -#endif /* FOOTERS */ -#ifndef ABORT -#define ABORT abort() -#endif /* ABORT */ -#ifndef ABORT_ON_ASSERT_FAILURE -#define ABORT_ON_ASSERT_FAILURE 1 -#endif /* ABORT_ON_ASSERT_FAILURE */ -#ifndef PROCEED_ON_ERROR -#define PROCEED_ON_ERROR 0 -#endif /* PROCEED_ON_ERROR */ -#ifndef USE_LOCKS -#define USE_LOCKS 0 -#endif /* USE_LOCKS */ -#ifndef USE_SPIN_LOCKS -#if USE_LOCKS && (defined(__GNUC__) && ((defined(__i386__) || defined(__x86_64__)))) || (defined(_MSC_VER) && _MSC_VER>=1310) -#define USE_SPIN_LOCKS 1 -#else -#define USE_SPIN_LOCKS 0 -#endif /* USE_LOCKS && ... */ -#endif /* USE_SPIN_LOCKS */ -#ifndef INSECURE -#define INSECURE 0 -#endif /* INSECURE */ -#ifndef HAVE_MMAP -#define HAVE_MMAP 1 -#endif /* HAVE_MMAP */ -#ifndef MMAP_CLEARS -#define MMAP_CLEARS 1 -#endif /* MMAP_CLEARS */ -#ifndef HAVE_MREMAP -#ifdef linux -#define HAVE_MREMAP 1 -#else /* linux */ -#define HAVE_MREMAP 0 -#endif /* linux */ -#endif /* HAVE_MREMAP */ -#ifndef MALLOC_FAILURE_ACTION -#define MALLOC_FAILURE_ACTION errno = ENOMEM; -#endif /* MALLOC_FAILURE_ACTION */ -#ifndef HAVE_MORECORE -#if ONLY_MSPACES -#define HAVE_MORECORE 0 -#else /* ONLY_MSPACES */ -#define HAVE_MORECORE 1 -#endif /* ONLY_MSPACES */ -#endif /* HAVE_MORECORE */ -#if !HAVE_MORECORE -#define MORECORE_CONTIGUOUS 0 -#else /* !HAVE_MORECORE */ -#define MORECORE_DEFAULT sbrk -#ifndef MORECORE_CONTIGUOUS -#define MORECORE_CONTIGUOUS 1 -#endif /* MORECORE_CONTIGUOUS */ -#endif /* HAVE_MORECORE */ -#ifndef DEFAULT_GRANULARITY -#if (MORECORE_CONTIGUOUS || defined(WIN32)) -#define DEFAULT_GRANULARITY (0) /* 0 means to compute in init_mparams */ -#else /* MORECORE_CONTIGUOUS */ -#define DEFAULT_GRANULARITY ((size_t)64U * (size_t)1024U) -#endif /* MORECORE_CONTIGUOUS */ -#endif /* DEFAULT_GRANULARITY */ -#ifndef DEFAULT_TRIM_THRESHOLD -#ifndef MORECORE_CANNOT_TRIM -#define DEFAULT_TRIM_THRESHOLD ((size_t)2U * (size_t)1024U * (size_t)1024U) -#else /* MORECORE_CANNOT_TRIM */ -#define DEFAULT_TRIM_THRESHOLD MAX_SIZE_T -#endif /* MORECORE_CANNOT_TRIM */ -#endif /* DEFAULT_TRIM_THRESHOLD */ -#ifndef DEFAULT_MMAP_THRESHOLD -#if HAVE_MMAP -#define DEFAULT_MMAP_THRESHOLD ((size_t)256U * (size_t)1024U) -#else /* HAVE_MMAP */ -#define DEFAULT_MMAP_THRESHOLD MAX_SIZE_T -#endif /* HAVE_MMAP */ -#endif /* DEFAULT_MMAP_THRESHOLD */ -#ifndef MAX_RELEASE_CHECK_RATE -#if HAVE_MMAP -#define MAX_RELEASE_CHECK_RATE 4095 -#else -#define MAX_RELEASE_CHECK_RATE MAX_SIZE_T -#endif /* HAVE_MMAP */ -#endif /* MAX_RELEASE_CHECK_RATE */ -#ifndef USE_BUILTIN_FFS -#define USE_BUILTIN_FFS 0 -#endif /* USE_BUILTIN_FFS */ -#ifndef USE_DEV_RANDOM -#define USE_DEV_RANDOM 0 -#endif /* USE_DEV_RANDOM */ -#ifndef NO_MALLINFO -#define NO_MALLINFO 0 -#endif /* NO_MALLINFO */ -#ifndef MALLINFO_FIELD_TYPE -#define MALLINFO_FIELD_TYPE size_t -#endif /* MALLINFO_FIELD_TYPE */ -#ifndef NO_SEGMENT_TRAVERSAL -#define NO_SEGMENT_TRAVERSAL 0 -#endif /* NO_SEGMENT_TRAVERSAL */ - -/* - mallopt tuning options. SVID/XPG defines four standard parameter - numbers for mallopt, normally defined in malloc.h. None of these - are used in this malloc, so setting them has no effect. But this - malloc does support the following options. -*/ - -#define M_TRIM_THRESHOLD (-1) -#define M_GRANULARITY (-2) -#define M_MMAP_THRESHOLD (-3) - -/* ------------------------ Mallinfo declarations ------------------------ */ - -#if !NO_MALLINFO -/* - This version of malloc supports the standard SVID/XPG mallinfo - routine that returns a struct containing usage properties and - statistics. It should work on any system that has a - /usr/include/malloc.h defining struct mallinfo. The main - declaration needed is the mallinfo struct that is returned (by-copy) - by mallinfo(). The malloinfo struct contains a bunch of fields that - are not even meaningful in this version of malloc. These fields are - are instead filled by mallinfo() with other numbers that might be of - interest. - - HAVE_USR_INCLUDE_MALLOC_H should be set if you have a - /usr/include/malloc.h file that includes a declaration of struct - mallinfo. If so, it is included; else a compliant version is - declared below. These must be precisely the same for mallinfo() to - work. The original SVID version of this struct, defined on most - systems with mallinfo, declares all fields as ints. But some others - define as unsigned long. If your system defines the fields using a - type of different width than listed here, you MUST #include your - system version and #define HAVE_USR_INCLUDE_MALLOC_H. -*/ - -/* #define HAVE_USR_INCLUDE_MALLOC_H */ - -#ifdef HAVE_USR_INCLUDE_MALLOC_H -#include "/usr/include/malloc.h" -#else /* HAVE_USR_INCLUDE_MALLOC_H */ -#ifndef STRUCT_MALLINFO_DECLARED -#define STRUCT_MALLINFO_DECLARED 1 -struct mallinfo { - MALLINFO_FIELD_TYPE arena; /* non-mmapped space allocated from system */ - MALLINFO_FIELD_TYPE ordblks; /* number of free chunks */ - MALLINFO_FIELD_TYPE smblks; /* always 0 */ - MALLINFO_FIELD_TYPE hblks; /* always 0 */ - MALLINFO_FIELD_TYPE hblkhd; /* space in mmapped regions */ - MALLINFO_FIELD_TYPE usmblks; /* maximum total allocated space */ - MALLINFO_FIELD_TYPE fsmblks; /* always 0 */ - MALLINFO_FIELD_TYPE uordblks; /* total allocated space */ - MALLINFO_FIELD_TYPE fordblks; /* total free space */ - MALLINFO_FIELD_TYPE keepcost; /* releasable (via malloc_trim) space */ -}; -#endif /* STRUCT_MALLINFO_DECLARED */ -#endif /* HAVE_USR_INCLUDE_MALLOC_H */ -#endif /* NO_MALLINFO */ - -/* - Try to persuade compilers to inline. The most critical functions for - inlining are defined as macros, so these aren't used for them. -*/ - -#ifdef __MINGW64_VERSION_MAJOR -#undef FORCEINLINE -#endif -#ifndef FORCEINLINE - #if defined(__GNUC__) -#define FORCEINLINE __inline __attribute__ ((always_inline)) - #elif defined(_MSC_VER) - #define FORCEINLINE __forceinline - #endif -#endif -#ifndef NOINLINE - #if defined(__GNUC__) - #define NOINLINE __attribute__ ((noinline)) - #elif defined(_MSC_VER) - #define NOINLINE __declspec(noinline) - #else - #define NOINLINE - #endif -#endif - -#ifdef __cplusplus -extern "C" { -#ifndef FORCEINLINE - #define FORCEINLINE inline -#endif -#endif /* __cplusplus */ -#ifndef FORCEINLINE - #define FORCEINLINE -#endif - -#if !ONLY_MSPACES - -/* ------------------- Declarations of public routines ------------------- */ - -#ifndef USE_DL_PREFIX -#define dlcalloc calloc -#define dlfree free -#define dlmalloc malloc -#define dlmemalign memalign -#define dlrealloc realloc -#define dlvalloc valloc -#define dlpvalloc pvalloc -#define dlmallinfo mallinfo -#define dlmallopt mallopt -#define dlmalloc_trim malloc_trim -#define dlmalloc_stats malloc_stats -#define dlmalloc_usable_size malloc_usable_size -#define dlmalloc_footprint malloc_footprint -#define dlmalloc_max_footprint malloc_max_footprint -#define dlindependent_calloc independent_calloc -#define dlindependent_comalloc independent_comalloc -#endif /* USE_DL_PREFIX */ - - -/* - malloc(size_t n) - Returns a pointer to a newly allocated chunk of at least n bytes, or - null if no space is available, in which case errno is set to ENOMEM - on ANSI C systems. - - If n is zero, malloc returns a minimum-sized chunk. (The minimum - size is 16 bytes on most 32bit systems, and 32 bytes on 64bit - systems.) Note that size_t is an unsigned type, so calls with - arguments that would be negative if signed are interpreted as - requests for huge amounts of space, which will often fail. The - maximum supported value of n differs across systems, but is in all - cases less than the maximum representable value of a size_t. -*/ -void* dlmalloc(size_t); - -/* - free(void* p) - Releases the chunk of memory pointed to by p, that had been previously - allocated using malloc or a related routine such as realloc. - It has no effect if p is null. If p was not malloced or already - freed, free(p) will by default cause the current program to abort. -*/ -void dlfree(void*); - -/* - calloc(size_t n_elements, size_t element_size); - Returns a pointer to n_elements * element_size bytes, with all locations - set to zero. -*/ -void* dlcalloc(size_t, size_t); - -/* - realloc(void* p, size_t n) - Returns a pointer to a chunk of size n that contains the same data - as does chunk p up to the minimum of (n, p's size) bytes, or null - if no space is available. - - The returned pointer may or may not be the same as p. The algorithm - prefers extending p in most cases when possible, otherwise it - employs the equivalent of a malloc-copy-free sequence. - - If p is null, realloc is equivalent to malloc. - - If space is not available, realloc returns null, errno is set (if on - ANSI) and p is NOT freed. - - if n is for fewer bytes than already held by p, the newly unused - space is lopped off and freed if possible. realloc with a size - argument of zero (re)allocates a minimum-sized chunk. - - The old unix realloc convention of allowing the last-free'd chunk - to be used as an argument to realloc is not supported. -*/ - -void* dlrealloc(void*, size_t); - -/* - memalign(size_t alignment, size_t n); - Returns a pointer to a newly allocated chunk of n bytes, aligned - in accord with the alignment argument. - - The alignment argument should be a power of two. If the argument is - not a power of two, the nearest greater power is used. - 8-byte alignment is guaranteed by normal malloc calls, so don't - bother calling memalign with an argument of 8 or less. - - Overreliance on memalign is a sure way to fragment space. -*/ -void* dlmemalign(size_t, size_t); - -/* - valloc(size_t n); - Equivalent to memalign(pagesize, n), where pagesize is the page - size of the system. If the pagesize is unknown, 4096 is used. -*/ -void* dlvalloc(size_t); - -/* - mallopt(int parameter_number, int parameter_value) - Sets tunable parameters The format is to provide a - (parameter-number, parameter-value) pair. mallopt then sets the - corresponding parameter to the argument value if it can (i.e., so - long as the value is meaningful), and returns 1 if successful else - 0. To workaround the fact that mallopt is specified to use int, - not size_t parameters, the value -1 is specially treated as the - maximum unsigned size_t value. - - SVID/XPG/ANSI defines four standard param numbers for mallopt, - normally defined in malloc.h. None of these are use in this malloc, - so setting them has no effect. But this malloc also supports other - options in mallopt. See below for details. Briefly, supported - parameters are as follows (listed defaults are for "typical" - configurations). - - Symbol param # default allowed param values - M_TRIM_THRESHOLD -1 2*1024*1024 any (-1 disables) - M_GRANULARITY -2 page size any power of 2 >= page size - M_MMAP_THRESHOLD -3 256*1024 any (or 0 if no MMAP support) -*/ -int dlmallopt(int, int); - -/* - malloc_footprint(); - Returns the number of bytes obtained from the system. The total - number of bytes allocated by malloc, realloc etc., is less than this - value. Unlike mallinfo, this function returns only a precomputed - result, so can be called frequently to monitor memory consumption. - Even if locks are otherwise defined, this function does not use them, - so results might not be up to date. -*/ -size_t dlmalloc_footprint(void); - -/* - malloc_max_footprint(); - Returns the maximum number of bytes obtained from the system. This - value will be greater than current footprint if deallocated space - has been reclaimed by the system. The peak number of bytes allocated - by malloc, realloc etc., is less than this value. Unlike mallinfo, - this function returns only a precomputed result, so can be called - frequently to monitor memory consumption. Even if locks are - otherwise defined, this function does not use them, so results might - not be up to date. -*/ -size_t dlmalloc_max_footprint(void); - -#if !NO_MALLINFO -/* - mallinfo() - Returns (by copy) a struct containing various summary statistics: - - arena: current total non-mmapped bytes allocated from system - ordblks: the number of free chunks - smblks: always zero. - hblks: current number of mmapped regions - hblkhd: total bytes held in mmapped regions - usmblks: the maximum total allocated space. This will be greater - than current total if trimming has occurred. - fsmblks: always zero - uordblks: current total allocated space (normal or mmapped) - fordblks: total free space - keepcost: the maximum number of bytes that could ideally be released - back to system via malloc_trim. ("ideally" means that - it ignores page restrictions etc.) - - Because these fields are ints, but internal bookkeeping may - be kept as longs, the reported values may wrap around zero and - thus be inaccurate. -*/ -struct mallinfo dlmallinfo(void); -#endif /* NO_MALLINFO */ - -/* - independent_calloc(size_t n_elements, size_t element_size, void* chunks[]); - - independent_calloc is similar to calloc, but instead of returning a - single cleared space, it returns an array of pointers to n_elements - independent elements that can hold contents of size elem_size, each - of which starts out cleared, and can be independently freed, - realloc'ed etc. The elements are guaranteed to be adjacently - allocated (this is not guaranteed to occur with multiple callocs or - mallocs), which may also improve cache locality in some - applications. - - The "chunks" argument is optional (i.e., may be null, which is - probably the most typical usage). If it is null, the returned array - is itself dynamically allocated and should also be freed when it is - no longer needed. Otherwise, the chunks array must be of at least - n_elements in length. It is filled in with the pointers to the - chunks. - - In either case, independent_calloc returns this pointer array, or - null if the allocation failed. If n_elements is zero and "chunks" - is null, it returns a chunk representing an array with zero elements - (which should be freed if not wanted). - - Each element must be individually freed when it is no longer - needed. If you'd like to instead be able to free all at once, you - should instead use regular calloc and assign pointers into this - space to represent elements. (In this case though, you cannot - independently free elements.) - - independent_calloc simplifies and speeds up implementations of many - kinds of pools. It may also be useful when constructing large data - structures that initially have a fixed number of fixed-sized nodes, - but the number is not known at compile time, and some of the nodes - may later need to be freed. For example: - - struct Node { int item; struct Node* next; }; - - struct Node* build_list() { - struct Node** pool; - int n = read_number_of_nodes_needed(); - if (n <= 0) return 0; - pool = (struct Node**)(independent_calloc(n, sizeof(struct Node), 0); - if (pool == 0) die(); - // organize into a linked list... - struct Node* first = pool[0]; - for (i = 0; i < n-1; ++i) - pool[i]->next = pool[i+1]; - free(pool); // Can now free the array (or not, if it is needed later) - return first; - } -*/ -void** dlindependent_calloc(size_t, size_t, void**); - -/* - independent_comalloc(size_t n_elements, size_t sizes[], void* chunks[]); - - independent_comalloc allocates, all at once, a set of n_elements - chunks with sizes indicated in the "sizes" array. It returns - an array of pointers to these elements, each of which can be - independently freed, realloc'ed etc. The elements are guaranteed to - be adjacently allocated (this is not guaranteed to occur with - multiple callocs or mallocs), which may also improve cache locality - in some applications. - - The "chunks" argument is optional (i.e., may be null). If it is null - the returned array is itself dynamically allocated and should also - be freed when it is no longer needed. Otherwise, the chunks array - must be of at least n_elements in length. It is filled in with the - pointers to the chunks. - - In either case, independent_comalloc returns this pointer array, or - null if the allocation failed. If n_elements is zero and chunks is - null, it returns a chunk representing an array with zero elements - (which should be freed if not wanted). - - Each element must be individually freed when it is no longer - needed. If you'd like to instead be able to free all at once, you - should instead use a single regular malloc, and assign pointers at - particular offsets in the aggregate space. (In this case though, you - cannot independently free elements.) - - independent_comallac differs from independent_calloc in that each - element may have a different size, and also that it does not - automatically clear elements. - - independent_comalloc can be used to speed up allocation in cases - where several structs or objects must always be allocated at the - same time. For example: - - struct Head { ... } - struct Foot { ... } - - void send_message(char* msg) { - int msglen = strlen(msg); - size_t sizes[3] = { sizeof(struct Head), msglen, sizeof(struct Foot) }; - void* chunks[3]; - if (independent_comalloc(3, sizes, chunks) == 0) - die(); - struct Head* head = (struct Head*)(chunks[0]); - char* body = (char*)(chunks[1]); - struct Foot* foot = (struct Foot*)(chunks[2]); - // ... - } - - In general though, independent_comalloc is worth using only for - larger values of n_elements. For small values, you probably won't - detect enough difference from series of malloc calls to bother. - - Overuse of independent_comalloc can increase overall memory usage, - since it cannot reuse existing noncontiguous small chunks that - might be available for some of the elements. -*/ -void** dlindependent_comalloc(size_t, size_t*, void**); - - -/* - pvalloc(size_t n); - Equivalent to valloc(minimum-page-that-holds(n)), that is, - round up n to nearest pagesize. - */ -void* dlpvalloc(size_t); - -/* - malloc_trim(size_t pad); - - If possible, gives memory back to the system (via negative arguments - to sbrk) if there is unused memory at the `high' end of the malloc - pool or in unused MMAP segments. You can call this after freeing - large blocks of memory to potentially reduce the system-level memory - requirements of a program. However, it cannot guarantee to reduce - memory. Under some allocation patterns, some large free blocks of - memory will be locked between two used chunks, so they cannot be - given back to the system. - - The `pad' argument to malloc_trim represents the amount of free - trailing space to leave untrimmed. If this argument is zero, only - the minimum amount of memory to maintain internal data structures - will be left. Non-zero arguments can be supplied to maintain enough - trailing space to service future expected allocations without having - to re-obtain memory from the system. - - Malloc_trim returns 1 if it actually released any memory, else 0. -*/ -int dlmalloc_trim(size_t); - -/* - malloc_stats(); - Prints on stderr the amount of space obtained from the system (both - via sbrk and mmap), the maximum amount (which may be more than - current if malloc_trim and/or munmap got called), and the current - number of bytes allocated via malloc (or realloc, etc) but not yet - freed. Note that this is the number of bytes allocated, not the - number requested. It will be larger than the number requested - because of alignment and bookkeeping overhead. Because it includes - alignment wastage as being in use, this figure may be greater than - zero even when no user-level chunks are allocated. - - The reported current and maximum system memory can be inaccurate if - a program makes other calls to system memory allocation functions - (normally sbrk) outside of malloc. - - malloc_stats prints only the most commonly interesting statistics. - More information can be obtained by calling mallinfo. -*/ -void dlmalloc_stats(void); - -#endif /* ONLY_MSPACES */ - -/* - malloc_usable_size(void* p); - - Returns the number of bytes you can actually use in - an allocated chunk, which may be more than you requested (although - often not) due to alignment and minimum size constraints. - You can use this many bytes without worrying about - overwriting other allocated objects. This is not a particularly great - programming practice. malloc_usable_size can be more useful in - debugging and assertions, for example: - - p = malloc(n); - assert(malloc_usable_size(p) >= 256); -*/ -size_t dlmalloc_usable_size(void*); - - -#if MSPACES - -/* - mspace is an opaque type representing an independent - region of space that supports mspace_malloc, etc. -*/ -typedef void* mspace; - -/* - create_mspace creates and returns a new independent space with the - given initial capacity, or, if 0, the default granularity size. It - returns null if there is no system memory available to create the - space. If argument locked is non-zero, the space uses a separate - lock to control access. The capacity of the space will grow - dynamically as needed to service mspace_malloc requests. You can - control the sizes of incremental increases of this space by - compiling with a different DEFAULT_GRANULARITY or dynamically - setting with mallopt(M_GRANULARITY, value). -*/ -mspace create_mspace(size_t capacity, int locked); - -/* - destroy_mspace destroys the given space, and attempts to return all - of its memory back to the system, returning the total number of - bytes freed. After destruction, the results of access to all memory - used by the space become undefined. -*/ -size_t destroy_mspace(mspace msp); - -/* - create_mspace_with_base uses the memory supplied as the initial base - of a new mspace. Part (less than 128*sizeof(size_t) bytes) of this - space is used for bookkeeping, so the capacity must be at least this - large. (Otherwise 0 is returned.) When this initial space is - exhausted, additional memory will be obtained from the system. - Destroying this space will deallocate all additionally allocated - space (if possible) but not the initial base. -*/ -mspace create_mspace_with_base(void* base, size_t capacity, int locked); - -/* - mspace_mmap_large_chunks controls whether requests for large chunks - are allocated in their own mmapped regions, separate from others in - this mspace. By default this is enabled, which reduces - fragmentation. However, such chunks are not necessarily released to - the system upon destroy_mspace. Disabling by setting to false may - increase fragmentation, but avoids leakage when relying on - destroy_mspace to release all memory allocated using this space. -*/ -int mspace_mmap_large_chunks(mspace msp, int enable); - - -/* - mspace_malloc behaves as malloc, but operates within - the given space. -*/ -void* mspace_malloc(mspace msp, size_t bytes); - -/* - mspace_free behaves as free, but operates within - the given space. - - If compiled with FOOTERS==1, mspace_free is not actually needed. - free may be called instead of mspace_free because freed chunks from - any space are handled by their originating spaces. -*/ -void mspace_free(mspace msp, void* mem); - -/* - mspace_realloc behaves as realloc, but operates within - the given space. - - If compiled with FOOTERS==1, mspace_realloc is not actually - needed. realloc may be called instead of mspace_realloc because - realloced chunks from any space are handled by their originating - spaces. -*/ -void* mspace_realloc(mspace msp, void* mem, size_t newsize); - -/* - mspace_calloc behaves as calloc, but operates within - the given space. -*/ -void* mspace_calloc(mspace msp, size_t n_elements, size_t elem_size); - -/* - mspace_memalign behaves as memalign, but operates within - the given space. -*/ -void* mspace_memalign(mspace msp, size_t alignment, size_t bytes); - -/* - mspace_independent_calloc behaves as independent_calloc, but - operates within the given space. -*/ -void** mspace_independent_calloc(mspace msp, size_t n_elements, - size_t elem_size, void* chunks[]); - -/* - mspace_independent_comalloc behaves as independent_comalloc, but - operates within the given space. -*/ -void** mspace_independent_comalloc(mspace msp, size_t n_elements, - size_t sizes[], void* chunks[]); - -/* - mspace_footprint() returns the number of bytes obtained from the - system for this space. -*/ -size_t mspace_footprint(mspace msp); - -/* - mspace_max_footprint() returns the peak number of bytes obtained from the - system for this space. -*/ -size_t mspace_max_footprint(mspace msp); - - -#if !NO_MALLINFO -/* - mspace_mallinfo behaves as mallinfo, but reports properties of - the given space. -*/ -struct mallinfo mspace_mallinfo(mspace msp); -#endif /* NO_MALLINFO */ - -/* - malloc_usable_size(void* p) behaves the same as malloc_usable_size; -*/ - size_t mspace_usable_size(void* mem); - -/* - mspace_malloc_stats behaves as malloc_stats, but reports - properties of the given space. -*/ -void mspace_malloc_stats(mspace msp); - -/* - mspace_trim behaves as malloc_trim, but - operates within the given space. -*/ -int mspace_trim(mspace msp, size_t pad); - -/* - An alias for mallopt. -*/ -int mspace_mallopt(int, int); - -#endif /* MSPACES */ - -#ifdef __cplusplus -}; /* end of extern "C" */ -#endif /* __cplusplus */ - -/* - ======================================================================== - To make a fully customizable malloc.h header file, cut everything - above this line, put into file malloc.h, edit to suit, and #include it - on the next line, as well as in programs that use this malloc. - ======================================================================== -*/ - -/* #include "malloc.h" */ - -/*------------------------------ internal #includes ---------------------- */ - -#ifdef WIN32 -#ifndef __GNUC__ -#pragma warning( disable : 4146 ) /* no "unsigned" warnings */ -#endif -#endif /* WIN32 */ - -#include <stdio.h> /* for printing in malloc_stats */ - -#ifndef LACKS_ERRNO_H -#include <errno.h> /* for MALLOC_FAILURE_ACTION */ -#endif /* LACKS_ERRNO_H */ -#if FOOTERS -#include <time.h> /* for magic initialization */ -#endif /* FOOTERS */ -#ifndef LACKS_STDLIB_H -#include <stdlib.h> /* for abort() */ -#endif /* LACKS_STDLIB_H */ -#ifdef DEBUG -#if ABORT_ON_ASSERT_FAILURE -#define assert(x) if(!(x)) ABORT -#else /* ABORT_ON_ASSERT_FAILURE */ -#include <assert.h> -#endif /* ABORT_ON_ASSERT_FAILURE */ -#else /* DEBUG */ -#ifndef assert -#define assert(x) -#endif -#define DEBUG 0 -#endif /* DEBUG */ -#ifndef LACKS_STRING_H -#include <string.h> /* for memset etc */ -#endif /* LACKS_STRING_H */ -#if USE_BUILTIN_FFS -#ifndef LACKS_STRINGS_H -#include <strings.h> /* for ffs */ -#endif /* LACKS_STRINGS_H */ -#endif /* USE_BUILTIN_FFS */ -#if HAVE_MMAP -#ifndef LACKS_SYS_MMAN_H -#include <sys/mman.h> /* for mmap */ -#endif /* LACKS_SYS_MMAN_H */ -#ifndef LACKS_FCNTL_H -#include <fcntl.h> -#endif /* LACKS_FCNTL_H */ -#endif /* HAVE_MMAP */ -#ifndef LACKS_UNISTD_H -#include <unistd.h> /* for sbrk, sysconf */ -#else /* LACKS_UNISTD_H */ -#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) -extern void* sbrk(ptrdiff_t); -#endif /* FreeBSD etc */ -#endif /* LACKS_UNISTD_H */ - -/* Declarations for locking */ -#if USE_LOCKS -#ifndef WIN32 -#include <pthread.h> -#if defined (__SVR4) && defined (__sun) /* solaris */ -#include <thread.h> -#endif /* solaris */ -#else -#ifndef _M_AMD64 -/* These are already defined on AMD64 builds */ -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ -#ifndef __MINGW32__ -LONG __cdecl _InterlockedCompareExchange(LONG volatile *Dest, LONG Exchange, LONG Comp); -LONG __cdecl _InterlockedExchange(LONG volatile *Target, LONG Value); -#endif -#ifdef __cplusplus -} -#endif /* __cplusplus */ -#endif /* _M_AMD64 */ -#ifndef __MINGW32__ -#pragma intrinsic (_InterlockedCompareExchange) -#pragma intrinsic (_InterlockedExchange) -#else - /* --[ start GCC compatibility ]---------------------------------------------- - * Compatibility <intrin_x86.h> header for GCC -- GCC equivalents of intrinsic - * Microsoft Visual C++ functions. Originally developed for the ReactOS - * (<http://www.reactos.org/>) and TinyKrnl (<http://www.tinykrnl.org/>) - * projects. - * - * Copyright (c) 2006 KJK::Hyperion <hackbunny@reactos.com> - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - - /*** Atomic operations ***/ - #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) > 40100 - #undef _ReadWriteBarrier - #define _ReadWriteBarrier() __sync_synchronize() - #else - static __inline__ __attribute__((always_inline)) long __sync_lock_test_and_set(volatile long * const Target, const long Value) - { - long res; - __asm__ __volatile__("xchg%z0 %2, %0" : "=g" (*(Target)), "=r" (res) : "1" (Value)); - return res; - } - static void __inline__ __attribute__((always_inline)) _MemoryBarrier(void) - { - __asm__ __volatile__("" : : : "memory"); - } - #define _ReadWriteBarrier() _MemoryBarrier() - #endif - /* BUGBUG: GCC only supports full barriers */ - static __inline__ __attribute__((always_inline)) long _InterlockedExchange(volatile long * const Target, const long Value) - { - /* NOTE: __sync_lock_test_and_set would be an acquire barrier, so we force a full barrier */ - _ReadWriteBarrier(); - return __sync_lock_test_and_set(Target, Value); - } - /* --[ end GCC compatibility ]---------------------------------------------- */ -#endif -#define interlockedcompareexchange _InterlockedCompareExchange -#define interlockedexchange _InterlockedExchange -#endif /* Win32 */ -#endif /* USE_LOCKS */ - -/* Declarations for bit scanning on win32 */ -#if defined(_MSC_VER) && _MSC_VER>=1300 -#ifndef BitScanForward /* Try to avoid pulling in WinNT.h */ -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ -unsigned char _BitScanForward(unsigned long *index, unsigned long mask); -unsigned char _BitScanReverse(unsigned long *index, unsigned long mask); -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#define BitScanForward _BitScanForward -#define BitScanReverse _BitScanReverse -#pragma intrinsic(_BitScanForward) -#pragma intrinsic(_BitScanReverse) -#endif /* BitScanForward */ -#endif /* defined(_MSC_VER) && _MSC_VER>=1300 */ - -#ifndef WIN32 -#ifndef malloc_getpagesize -# ifdef _SC_PAGESIZE /* some SVR4 systems omit an underscore */ -# ifndef _SC_PAGE_SIZE -# define _SC_PAGE_SIZE _SC_PAGESIZE -# endif -# endif -# ifdef _SC_PAGE_SIZE -# define malloc_getpagesize sysconf(_SC_PAGE_SIZE) -# else -# if defined(BSD) || defined(DGUX) || defined(HAVE_GETPAGESIZE) - extern size_t getpagesize(); -# define malloc_getpagesize getpagesize() -# else -# ifdef WIN32 /* use supplied emulation of getpagesize */ -# define malloc_getpagesize getpagesize() -# else -# ifndef LACKS_SYS_PARAM_H -# include <sys/param.h> -# endif -# ifdef EXEC_PAGESIZE -# define malloc_getpagesize EXEC_PAGESIZE -# else -# ifdef NBPG -# ifndef CLSIZE -# define malloc_getpagesize NBPG -# else -# define malloc_getpagesize (NBPG * CLSIZE) -# endif -# else -# ifdef NBPC -# define malloc_getpagesize NBPC -# else -# ifdef PAGESIZE -# define malloc_getpagesize PAGESIZE -# else /* just guess */ -# define malloc_getpagesize ((size_t)4096U) -# endif -# endif -# endif -# endif -# endif -# endif -# endif -#endif -#endif - - - -/* ------------------- size_t and alignment properties -------------------- */ - -/* The byte and bit size of a size_t */ -#define SIZE_T_SIZE (sizeof(size_t)) -#define SIZE_T_BITSIZE (sizeof(size_t) << 3) - -/* Some constants coerced to size_t */ -/* Annoying but necessary to avoid errors on some platforms */ -#define SIZE_T_ZERO ((size_t)0) -#define SIZE_T_ONE ((size_t)1) -#define SIZE_T_TWO ((size_t)2) -#define SIZE_T_FOUR ((size_t)4) -#define TWO_SIZE_T_SIZES (SIZE_T_SIZE<<1) -#define FOUR_SIZE_T_SIZES (SIZE_T_SIZE<<2) -#define SIX_SIZE_T_SIZES (FOUR_SIZE_T_SIZES+TWO_SIZE_T_SIZES) -#define HALF_MAX_SIZE_T (MAX_SIZE_T / 2U) - -/* The bit mask value corresponding to MALLOC_ALIGNMENT */ -#define CHUNK_ALIGN_MASK (MALLOC_ALIGNMENT - SIZE_T_ONE) - -/* True if address a has acceptable alignment */ -#define is_aligned(A) (((size_t)((A)) & (CHUNK_ALIGN_MASK)) == 0) - -/* the number of bytes to offset an address to align it */ -#define align_offset(A)\ - ((((size_t)(A) & CHUNK_ALIGN_MASK) == 0)? 0 :\ - ((MALLOC_ALIGNMENT - ((size_t)(A) & CHUNK_ALIGN_MASK)) & CHUNK_ALIGN_MASK)) - -/* -------------------------- MMAP preliminaries ------------------------- */ - -/* - If HAVE_MORECORE or HAVE_MMAP are false, we just define calls and - checks to fail so compiler optimizer can delete code rather than - using so many "#if"s. -*/ - - -/* MORECORE and MMAP must return MFAIL on failure */ -#define MFAIL ((void*)(MAX_SIZE_T)) -#define CMFAIL ((char*)(MFAIL)) /* defined for convenience */ - -#if HAVE_MMAP - -#ifndef WIN32 -#define MUNMAP_DEFAULT(a, s) munmap((a), (s)) -#define MMAP_PROT (PROT_READ|PROT_WRITE) -#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) -#define MAP_ANONYMOUS MAP_ANON -#endif /* MAP_ANON */ -#ifdef MAP_ANONYMOUS -#define MMAP_FLAGS (MAP_PRIVATE|MAP_ANONYMOUS) -#define MMAP_DEFAULT(s) mmap(0, (s), MMAP_PROT, MMAP_FLAGS, -1, 0) -#else /* MAP_ANONYMOUS */ -/* - Nearly all versions of mmap support MAP_ANONYMOUS, so the following - is unlikely to be needed, but is supplied just in case. -*/ -#define MMAP_FLAGS (MAP_PRIVATE) -static int dev_zero_fd = -1; /* Cached file descriptor for /dev/zero. */ -#define MMAP_DEFAULT(s) ((dev_zero_fd < 0) ? \ - (dev_zero_fd = open("/dev/zero", O_RDWR), \ - mmap(0, (s), MMAP_PROT, MMAP_FLAGS, dev_zero_fd, 0)) : \ - mmap(0, (s), MMAP_PROT, MMAP_FLAGS, dev_zero_fd, 0)) -#endif /* MAP_ANONYMOUS */ - -#define DIRECT_MMAP_DEFAULT(s) MMAP_DEFAULT(s) - -#else /* WIN32 */ - -/* Win32 MMAP via VirtualAlloc */ -static FORCEINLINE void* win32mmap(size_t size) { - void* ptr = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); - return (ptr != 0)? ptr: MFAIL; -} - -/* For direct MMAP, use MEM_TOP_DOWN to minimize interference */ -static FORCEINLINE void* win32direct_mmap(size_t size) { - void* ptr = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN, - PAGE_READWRITE); - return (ptr != 0)? ptr: MFAIL; -} - -/* This function supports releasing coalesced segments */ -static FORCEINLINE int win32munmap(void* ptr, size_t size) { - MEMORY_BASIC_INFORMATION minfo; - char* cptr = (char*)ptr; - while (size) { - if (VirtualQuery(cptr, &minfo, sizeof(minfo)) == 0) - return -1; - if (minfo.BaseAddress != cptr || minfo.AllocationBase != cptr || - minfo.State != MEM_COMMIT || minfo.RegionSize > size) - return -1; - if (VirtualFree(cptr, 0, MEM_RELEASE) == 0) - return -1; - cptr += minfo.RegionSize; - size -= minfo.RegionSize; - } - return 0; -} - -#define MMAP_DEFAULT(s) win32mmap(s) -#define MUNMAP_DEFAULT(a, s) win32munmap((a), (s)) -#define DIRECT_MMAP_DEFAULT(s) win32direct_mmap(s) -#endif /* WIN32 */ -#endif /* HAVE_MMAP */ - -#if HAVE_MREMAP -#ifndef WIN32 -#define MREMAP_DEFAULT(addr, osz, nsz, mv) mremap((addr), (osz), (nsz), (mv)) -#endif /* WIN32 */ -#endif /* HAVE_MREMAP */ - - -/** - * Define CALL_MORECORE - */ -#if HAVE_MORECORE - #ifdef MORECORE - #define CALL_MORECORE(S) MORECORE(S) - #else /* MORECORE */ - #define CALL_MORECORE(S) MORECORE_DEFAULT(S) - #endif /* MORECORE */ -#else /* HAVE_MORECORE */ - #define CALL_MORECORE(S) MFAIL -#endif /* HAVE_MORECORE */ - -/** - * Define CALL_MMAP/CALL_MUNMAP/CALL_DIRECT_MMAP - */ -#if HAVE_MMAP - #define IS_MMAPPED_BIT (SIZE_T_ONE) - #define USE_MMAP_BIT (SIZE_T_ONE) - - #ifdef MMAP - #define CALL_MMAP(s) MMAP(s) - #else /* MMAP */ - #define CALL_MMAP(s) MMAP_DEFAULT(s) - #endif /* MMAP */ - #ifdef MUNMAP - #define CALL_MUNMAP(a, s) MUNMAP((a), (s)) - #else /* MUNMAP */ - #define CALL_MUNMAP(a, s) MUNMAP_DEFAULT((a), (s)) - #endif /* MUNMAP */ - #ifdef DIRECT_MMAP - #define CALL_DIRECT_MMAP(s) DIRECT_MMAP(s) - #else /* DIRECT_MMAP */ - #define CALL_DIRECT_MMAP(s) DIRECT_MMAP_DEFAULT(s) - #endif /* DIRECT_MMAP */ -#else /* HAVE_MMAP */ - #define IS_MMAPPED_BIT (SIZE_T_ZERO) - #define USE_MMAP_BIT (SIZE_T_ZERO) - - #define MMAP(s) MFAIL - #define MUNMAP(a, s) (-1) - #define DIRECT_MMAP(s) MFAIL - #define CALL_DIRECT_MMAP(s) DIRECT_MMAP(s) - #define CALL_MMAP(s) MMAP(s) - #define CALL_MUNMAP(a, s) MUNMAP((a), (s)) -#endif /* HAVE_MMAP */ - -/** - * Define CALL_MREMAP - */ -#if HAVE_MMAP && HAVE_MREMAP - #ifdef MREMAP - #define CALL_MREMAP(addr, osz, nsz, mv) MREMAP((addr), (osz), (nsz), (mv)) - #else /* MREMAP */ - #define CALL_MREMAP(addr, osz, nsz, mv) MREMAP_DEFAULT((addr), (osz), (nsz), (mv)) - #endif /* MREMAP */ -#else /* HAVE_MMAP && HAVE_MREMAP */ - #define CALL_MREMAP(addr, osz, nsz, mv) MFAIL -#endif /* HAVE_MMAP && HAVE_MREMAP */ - -/* mstate bit set if contiguous morecore disabled or failed */ -#define USE_NONCONTIGUOUS_BIT (4U) - -/* segment bit set in create_mspace_with_base */ -#define EXTERN_BIT (8U) - - -/* --------------------------- Lock preliminaries ------------------------ */ - -/* - When locks are defined, there is one global lock, plus - one per-mspace lock. - - The global lock_ensures that mparams.magic and other unique - mparams values are initialized only once. It also protects - sequences of calls to MORECORE. In many cases sys_alloc requires - two calls, that should not be interleaved with calls by other - threads. This does not protect against direct calls to MORECORE - by other threads not using this lock, so there is still code to - cope the best we can on interference. - - Per-mspace locks surround calls to malloc, free, etc. To enable use - in layered extensions, per-mspace locks are reentrant. - - Because lock-protected regions generally have bounded times, it is - OK to use the supplied simple spinlocks in the custom versions for - x86. - - If USE_LOCKS is > 1, the definitions of lock routines here are - bypassed, in which case you will need to define at least - INITIAL_LOCK, ACQUIRE_LOCK, RELEASE_LOCK and possibly TRY_LOCK - (which is not used in this malloc, but commonly needed in - extensions.) -*/ - -#if USE_LOCKS == 1 - -#if USE_SPIN_LOCKS -#ifndef WIN32 - -/* Custom pthread-style spin locks on x86 and x64 for gcc */ -struct pthread_mlock_t { - volatile unsigned int l; - volatile unsigned int c; - volatile pthread_t threadid; -}; -#define MLOCK_T struct pthread_mlock_t -#define CURRENT_THREAD pthread_self() -#define INITIAL_LOCK(sl) (memset(sl, 0, sizeof(MLOCK_T)), 0) -#define ACQUIRE_LOCK(sl) pthread_acquire_lock(sl) -#define RELEASE_LOCK(sl) pthread_release_lock(sl) -#define TRY_LOCK(sl) pthread_try_lock(sl) -#define SPINS_PER_YIELD 63 - -static MLOCK_T malloc_global_mutex = { 0, 0, 0}; - -static FORCEINLINE int pthread_acquire_lock (MLOCK_T *sl) { - int spins = 0; - volatile unsigned int* lp = &sl->l; - for (;;) { - if (*lp != 0) { - if (sl->threadid == CURRENT_THREAD) { - ++sl->c; - return 0; - } - } - else { - /* place args to cmpxchgl in locals to evade oddities in some gccs */ - int cmp = 0; - int val = 1; - int ret; - __asm__ __volatile__ ("lock; cmpxchgl %1, %2" - : "=a" (ret) - : "r" (val), "m" (*(lp)), "0"(cmp) - : "memory", "cc"); - if (!ret) { - assert(!sl->threadid); - sl->c = 1; - sl->threadid = CURRENT_THREAD; - return 0; - } - if ((++spins & SPINS_PER_YIELD) == 0) { -#if defined (__SVR4) && defined (__sun) /* solaris */ - thr_yield(); -#else -#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__) - sched_yield(); -#else /* no-op yield on unknown systems */ - ; -#endif /* __linux__ || __FreeBSD__ || __APPLE__ */ -#endif /* solaris */ - } - } - } -} - -static FORCEINLINE void pthread_release_lock (MLOCK_T *sl) { - assert(sl->l != 0); - assert(sl->threadid == CURRENT_THREAD); - if (--sl->c == 0) { - volatile unsigned int* lp = &sl->l; - int prev = 0; - int ret; - sl->threadid = 0; - __asm__ __volatile__ ("lock; xchgl %0, %1" - : "=r" (ret) - : "m" (*(lp)), "0"(prev) - : "memory"); - } -} - -static FORCEINLINE int pthread_try_lock (MLOCK_T *sl) { - volatile unsigned int* lp = &sl->l; - if (*lp != 0) { - if (sl->threadid == CURRENT_THREAD) { - ++sl->c; - return 1; - } - } - else { - int cmp = 0; - int val = 1; - int ret; - __asm__ __volatile__ ("lock; cmpxchgl %1, %2" - : "=a" (ret) - : "r" (val), "m" (*(lp)), "0"(cmp) - : "memory", "cc"); - if (!ret) { - assert(!sl->threadid); - sl->c = 1; - sl->threadid = CURRENT_THREAD; - return 1; - } - } - return 0; -} - - -#else /* WIN32 */ -/* Custom win32-style spin locks on x86 and x64 for MSC */ -struct win32_mlock_t -{ - volatile long l; - volatile unsigned int c; - volatile long threadid; -}; - -static inline int return_0(int i) { return 0; } -#define MLOCK_T struct win32_mlock_t -#define CURRENT_THREAD win32_getcurrentthreadid() -#define INITIAL_LOCK(sl) (memset(sl, 0, sizeof(MLOCK_T)), return_0(0)) -#define ACQUIRE_LOCK(sl) win32_acquire_lock(sl) -#define RELEASE_LOCK(sl) win32_release_lock(sl) -#define TRY_LOCK(sl) win32_try_lock(sl) -#define SPINS_PER_YIELD 63 - -static MLOCK_T malloc_global_mutex = { 0, 0, 0}; - -static FORCEINLINE long win32_getcurrentthreadid(void) { -#ifdef _MSC_VER -#if defined(_M_IX86) - long *threadstruct=(long *)__readfsdword(0x18); - long threadid=threadstruct[0x24/sizeof(long)]; - return threadid; -#elif defined(_M_X64) - /* todo */ - return GetCurrentThreadId(); -#else - return GetCurrentThreadId(); -#endif -#else - return GetCurrentThreadId(); -#endif -} - -static FORCEINLINE int win32_acquire_lock (MLOCK_T *sl) { - int spins = 0; - for (;;) { - if (sl->l != 0) { - if (sl->threadid == CURRENT_THREAD) { - ++sl->c; - return 0; - } - } - else { - if (!interlockedexchange(&sl->l, 1)) { - assert(!sl->threadid); - sl->c=CURRENT_THREAD; - sl->threadid = CURRENT_THREAD; - sl->c = 1; - return 0; - } - } - if ((++spins & SPINS_PER_YIELD) == 0) - SleepEx(0, FALSE); - } -} - -static FORCEINLINE void win32_release_lock (MLOCK_T *sl) { - assert(sl->threadid == CURRENT_THREAD); - assert(sl->l != 0); - if (--sl->c == 0) { - sl->threadid = 0; - interlockedexchange (&sl->l, 0); - } -} - -static FORCEINLINE int win32_try_lock (MLOCK_T *sl) { - if(sl->l != 0) { - if (sl->threadid == CURRENT_THREAD) { - ++sl->c; - return 1; - } - } - else { - if (!interlockedexchange(&sl->l, 1)){ - assert(!sl->threadid); - sl->threadid = CURRENT_THREAD; - sl->c = 1; - return 1; - } - } - return 0; -} - -#endif /* WIN32 */ -#else /* USE_SPIN_LOCKS */ - -#ifndef WIN32 -/* pthreads-based locks */ - -#define MLOCK_T pthread_mutex_t -#define CURRENT_THREAD pthread_self() -#define INITIAL_LOCK(sl) pthread_init_lock(sl) -#define ACQUIRE_LOCK(sl) pthread_mutex_lock(sl) -#define RELEASE_LOCK(sl) pthread_mutex_unlock(sl) -#define TRY_LOCK(sl) (!pthread_mutex_trylock(sl)) - -static MLOCK_T malloc_global_mutex = PTHREAD_MUTEX_INITIALIZER; - -/* Cope with old-style linux recursive lock initialization by adding */ -/* skipped internal declaration from pthread.h */ -#ifdef linux -#ifndef PTHREAD_MUTEX_RECURSIVE -extern int pthread_mutexattr_setkind_np __P ((pthread_mutexattr_t *__attr, - int __kind)); -#define PTHREAD_MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE_NP -#define pthread_mutexattr_settype(x,y) pthread_mutexattr_setkind_np(x,y) -#endif -#endif - -static int pthread_init_lock (MLOCK_T *sl) { - pthread_mutexattr_t attr; - if (pthread_mutexattr_init(&attr)) return 1; - if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) return 1; - if (pthread_mutex_init(sl, &attr)) return 1; - if (pthread_mutexattr_destroy(&attr)) return 1; - return 0; -} - -#else /* WIN32 */ -/* Win32 critical sections */ -#define MLOCK_T CRITICAL_SECTION -#define CURRENT_THREAD GetCurrentThreadId() -#define INITIAL_LOCK(s) (!InitializeCriticalSectionAndSpinCount((s), 0x80000000|4000)) -#define ACQUIRE_LOCK(s) (EnterCriticalSection(s), 0) -#define RELEASE_LOCK(s) LeaveCriticalSection(s) -#define TRY_LOCK(s) TryEnterCriticalSection(s) -#define NEED_GLOBAL_LOCK_INIT - -static MLOCK_T malloc_global_mutex; -static volatile long malloc_global_mutex_status; - -/* Use spin loop to initialize global lock */ -static void init_malloc_global_mutex() { - for (;;) { - long stat = malloc_global_mutex_status; - if (stat > 0) - return; - /* transition to < 0 while initializing, then to > 0) */ - if (stat == 0 && - interlockedcompareexchange(&malloc_global_mutex_status, -1, 0) == 0) { - InitializeCriticalSection(&malloc_global_mutex); - interlockedexchange(&malloc_global_mutex_status,1); - return; - } - SleepEx(0, FALSE); - } -} - -#endif /* WIN32 */ -#endif /* USE_SPIN_LOCKS */ -#endif /* USE_LOCKS == 1 */ - -/* ----------------------- User-defined locks ------------------------ */ - -#if USE_LOCKS > 1 -/* Define your own lock implementation here */ -/* #define INITIAL_LOCK(sl) ... */ -/* #define ACQUIRE_LOCK(sl) ... */ -/* #define RELEASE_LOCK(sl) ... */ -/* #define TRY_LOCK(sl) ... */ -/* static MLOCK_T malloc_global_mutex = ... */ -#endif /* USE_LOCKS > 1 */ - -/* ----------------------- Lock-based state ------------------------ */ - -#if USE_LOCKS -#define USE_LOCK_BIT (2U) -#else /* USE_LOCKS */ -#define USE_LOCK_BIT (0U) -#define INITIAL_LOCK(l) -#endif /* USE_LOCKS */ - -#if USE_LOCKS -#define ACQUIRE_MALLOC_GLOBAL_LOCK() ACQUIRE_LOCK(&malloc_global_mutex); -#define RELEASE_MALLOC_GLOBAL_LOCK() RELEASE_LOCK(&malloc_global_mutex); -#else /* USE_LOCKS */ -#define ACQUIRE_MALLOC_GLOBAL_LOCK() -#define RELEASE_MALLOC_GLOBAL_LOCK() -#endif /* USE_LOCKS */ - - -/* ----------------------- Chunk representations ------------------------ */ - -/* - (The following includes lightly edited explanations by Colin Plumb.) - - The malloc_chunk declaration below is misleading (but accurate and - necessary). It declares a "view" into memory allowing access to - necessary fields at known offsets from a given base. - - Chunks of memory are maintained using a `boundary tag' method as - originally described by Knuth. (See the paper by Paul Wilson - ftp://ftp.cs.utexas.edu/pub/garbage/allocsrv.ps for a survey of such - techniques.) Sizes of free chunks are stored both in the front of - each chunk and at the end. This makes consolidating fragmented - chunks into bigger chunks fast. The head fields also hold bits - representing whether chunks are free or in use. - - Here are some pictures to make it clearer. They are "exploded" to - show that the state of a chunk can be thought of as extending from - the high 31 bits of the head field of its header through the - prev_foot and PINUSE_BIT bit of the following chunk header. - - A chunk that's in use looks like: - - chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Size of previous chunk (if P = 0) | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |P| - | Size of this chunk 1| +-+ - mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | | - +- -+ - | | - +- -+ - | : - +- size - sizeof(size_t) available payload bytes -+ - : | - chunk-> +- -+ - | | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |1| - | Size of next chunk (may or may not be in use) | +-+ - mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - - And if it's free, it looks like this: - - chunk-> +- -+ - | User payload (must be in use, or we would have merged!) | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |P| - | Size of this chunk 0| +-+ - mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Next pointer | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Prev pointer | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | : - +- size - sizeof(struct chunk) unused bytes -+ - : | - chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Size of this chunk | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |0| - | Size of next chunk (must be in use, or we would have merged)| +-+ - mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | : - +- User payload -+ - : | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - |0| - +-+ - Note that since we always merge adjacent free chunks, the chunks - adjacent to a free chunk must be in use. - - Given a pointer to a chunk (which can be derived trivially from the - payload pointer) we can, in O(1) time, find out whether the adjacent - chunks are free, and if so, unlink them from the lists that they - are on and merge them with the current chunk. - - Chunks always begin on even word boundaries, so the mem portion - (which is returned to the user) is also on an even word boundary, and - thus at least double-word aligned. - - The P (PINUSE_BIT) bit, stored in the unused low-order bit of the - chunk size (which is always a multiple of two words), is an in-use - bit for the *previous* chunk. If that bit is *clear*, then the - word before the current chunk size contains the previous chunk - size, and can be used to find the front of the previous chunk. - The very first chunk allocated always has this bit set, preventing - access to non-existent (or non-owned) memory. If pinuse is set for - any given chunk, then you CANNOT determine the size of the - previous chunk, and might even get a memory addressing fault when - trying to do so. - - The C (CINUSE_BIT) bit, stored in the unused second-lowest bit of - the chunk size redundantly records whether the current chunk is - inuse. This redundancy enables usage checks within free and realloc, - and reduces indirection when freeing and consolidating chunks. - - Each freshly allocated chunk must have both cinuse and pinuse set. - That is, each allocated chunk borders either a previously allocated - and still in-use chunk, or the base of its memory arena. This is - ensured by making all allocations from the `lowest' part of any - found chunk. Further, no free chunk physically borders another one, - so each free chunk is known to be preceded and followed by either - inuse chunks or the ends of memory. - - Note that the `foot' of the current chunk is actually represented - as the prev_foot of the NEXT chunk. This makes it easier to - deal with alignments etc but can be very confusing when trying - to extend or adapt this code. - - The exceptions to all this are - - 1. The special chunk `top' is the top-most available chunk (i.e., - the one bordering the end of available memory). It is treated - specially. Top is never included in any bin, is used only if - no other chunk is available, and is released back to the - system if it is very large (see M_TRIM_THRESHOLD). In effect, - the top chunk is treated as larger (and thus less well - fitting) than any other available chunk. The top chunk - doesn't update its trailing size field since there is no next - contiguous chunk that would have to index off it. However, - space is still allocated for it (TOP_FOOT_SIZE) to enable - separation or merging when space is extended. - - 3. Chunks allocated via mmap, which have the lowest-order bit - (IS_MMAPPED_BIT) set in their prev_foot fields, and do not set - PINUSE_BIT in their head fields. Because they are allocated - one-by-one, each must carry its own prev_foot field, which is - also used to hold the offset this chunk has within its mmapped - region, which is needed to preserve alignment. Each mmapped - chunk is trailed by the first two fields of a fake next-chunk - for sake of usage checks. - -*/ - -struct malloc_chunk { - size_t prev_foot; /* Size of previous chunk (if free). */ - size_t head; /* Size and inuse bits. */ - struct malloc_chunk* fd; /* double links -- used only if free. */ - struct malloc_chunk* bk; -}; - -typedef struct malloc_chunk mchunk; -typedef struct malloc_chunk* mchunkptr; -typedef struct malloc_chunk* sbinptr; /* The type of bins of chunks */ -typedef unsigned int bindex_t; /* Described below */ -typedef unsigned int binmap_t; /* Described below */ -typedef unsigned int flag_t; /* The type of various bit flag sets */ - -/* ------------------- Chunks sizes and alignments ----------------------- */ - -#define MCHUNK_SIZE (sizeof(mchunk)) - -#if FOOTERS -#define CHUNK_OVERHEAD (TWO_SIZE_T_SIZES) -#else /* FOOTERS */ -#define CHUNK_OVERHEAD (SIZE_T_SIZE) -#endif /* FOOTERS */ - -/* MMapped chunks need a second word of overhead ... */ -#define MMAP_CHUNK_OVERHEAD (TWO_SIZE_T_SIZES) -/* ... and additional padding for fake next-chunk at foot */ -#define MMAP_FOOT_PAD (FOUR_SIZE_T_SIZES) - -/* The smallest size we can malloc is an aligned minimal chunk */ -#define MIN_CHUNK_SIZE\ - ((MCHUNK_SIZE + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK) - -/* conversion from malloc headers to user pointers, and back */ -#define chunk2mem(p) ((void*)((char*)(p) + TWO_SIZE_T_SIZES)) -#define mem2chunk(mem) ((mchunkptr)((char*)(mem) - TWO_SIZE_T_SIZES)) -/* chunk associated with aligned address A */ -#define align_as_chunk(A) (mchunkptr)((A) + align_offset(chunk2mem(A))) - -/* Bounds on request (not chunk) sizes. */ -#define MAX_REQUEST ((-MIN_CHUNK_SIZE) << 2) -#define MIN_REQUEST (MIN_CHUNK_SIZE - CHUNK_OVERHEAD - SIZE_T_ONE) - -/* pad request bytes into a usable size */ -#define pad_request(req) \ - (((req) + CHUNK_OVERHEAD + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK) - -/* pad request, checking for minimum (but not maximum) */ -#define request2size(req) \ - (((req) < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(req)) - - -/* ------------------ Operations on head and foot fields ----------------- */ - -/* - The head field of a chunk is or'ed with PINUSE_BIT when previous - adjacent chunk in use, and or'ed with CINUSE_BIT if this chunk is in - use. If the chunk was obtained with mmap, the prev_foot field has - IS_MMAPPED_BIT set, otherwise holding the offset of the base of the - mmapped region to the base of the chunk. - - FLAG4_BIT is not used by this malloc, but might be useful in extensions. -*/ - -#define PINUSE_BIT (SIZE_T_ONE) -#define CINUSE_BIT (SIZE_T_TWO) -#define FLAG4_BIT (SIZE_T_FOUR) -#define INUSE_BITS (PINUSE_BIT|CINUSE_BIT) -#define FLAG_BITS (PINUSE_BIT|CINUSE_BIT|FLAG4_BIT) - -/* Head value for fenceposts */ -#define FENCEPOST_HEAD (INUSE_BITS|SIZE_T_SIZE) - -/* extraction of fields from head words */ -#define cinuse(p) ((p)->head & CINUSE_BIT) -#define pinuse(p) ((p)->head & PINUSE_BIT) -#define chunksize(p) ((p)->head & ~(FLAG_BITS)) - -#define clear_pinuse(p) ((p)->head &= ~PINUSE_BIT) -#define clear_cinuse(p) ((p)->head &= ~CINUSE_BIT) - -/* Treat space at ptr +/- offset as a chunk */ -#define chunk_plus_offset(p, s) ((mchunkptr)(((char*)(p)) + (s))) -#define chunk_minus_offset(p, s) ((mchunkptr)(((char*)(p)) - (s))) - -/* Ptr to next or previous physical malloc_chunk. */ -#define next_chunk(p) ((mchunkptr)( ((char*)(p)) + ((p)->head & ~FLAG_BITS))) -#define prev_chunk(p) ((mchunkptr)( ((char*)(p)) - ((p)->prev_foot) )) - -/* extract next chunk's pinuse bit */ -#define next_pinuse(p) ((next_chunk(p)->head) & PINUSE_BIT) - -/* Get/set size at footer */ -#define get_foot(p, s) (((mchunkptr)((char*)(p) + (s)))->prev_foot) -#define set_foot(p, s) (((mchunkptr)((char*)(p) + (s)))->prev_foot = (s)) - -/* Set size, pinuse bit, and foot */ -#define set_size_and_pinuse_of_free_chunk(p, s)\ - ((p)->head = (s|PINUSE_BIT), set_foot(p, s)) - -/* Set size, pinuse bit, foot, and clear next pinuse */ -#define set_free_with_pinuse(p, s, n)\ - (clear_pinuse(n), set_size_and_pinuse_of_free_chunk(p, s)) - -#define is_mmapped(p)\ - (!((p)->head & PINUSE_BIT) && ((p)->prev_foot & IS_MMAPPED_BIT)) - -/* Get the internal overhead associated with chunk p */ -#define overhead_for(p)\ - (is_mmapped(p)? MMAP_CHUNK_OVERHEAD : CHUNK_OVERHEAD) - -/* Return true if malloced space is not necessarily cleared */ -#if MMAP_CLEARS -#define calloc_must_clear(p) (!is_mmapped(p)) -#else /* MMAP_CLEARS */ -#define calloc_must_clear(p) (1) -#endif /* MMAP_CLEARS */ - -/* ---------------------- Overlaid data structures ----------------------- */ - -/* - When chunks are not in use, they are treated as nodes of either - lists or trees. - - "Small" chunks are stored in circular doubly-linked lists, and look - like this: - - chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Size of previous chunk | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - `head:' | Size of chunk, in bytes |P| - mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Forward pointer to next chunk in list | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Back pointer to previous chunk in list | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Unused space (may be 0 bytes long) . - . . - . | -nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - `foot:' | Size of chunk, in bytes | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - - Larger chunks are kept in a form of bitwise digital trees (aka - tries) keyed on chunksizes. Because malloc_tree_chunks are only for - free chunks greater than 256 bytes, their size doesn't impose any - constraints on user chunk sizes. Each node looks like: - - chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Size of previous chunk | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - `head:' | Size of chunk, in bytes |P| - mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Forward pointer to next chunk of same size | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Back pointer to previous chunk of same size | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Pointer to left child (child[0]) | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Pointer to right child (child[1]) | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Pointer to parent | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | bin index of this chunk | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Unused space . - . | -nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - `foot:' | Size of chunk, in bytes | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - - Each tree holding treenodes is a tree of unique chunk sizes. Chunks - of the same size are arranged in a circularly-linked list, with only - the oldest chunk (the next to be used, in our FIFO ordering) - actually in the tree. (Tree members are distinguished by a non-null - parent pointer.) If a chunk with the same size as an existing node - is inserted, it is linked off the existing node using pointers that - work in the same way as fd/bk pointers of small chunks. - - Each tree contains a power of 2 sized range of chunk sizes (the - smallest is 0x100 <= x < 0x180), which is divided in half at each - tree level, with the chunks in the smaller half of the range (0x100 - <= x < 0x140 for the top nose) in the left subtree and the larger - half (0x140 <= x < 0x180) in the right subtree. This is, of course, - done by inspecting individual bits. - - Using these rules, each node's left subtree contains all smaller - sizes than its right subtree. However, the node at the root of each - subtree has no particular ordering relationship to either. (The - dividing line between the subtree sizes is based on trie relation.) - If we remove the last chunk of a given size from the interior of the - tree, we need to replace it with a leaf node. The tree ordering - rules permit a node to be replaced by any leaf below it. - - The smallest chunk in a tree (a common operation in a best-fit - allocator) can be found by walking a path to the leftmost leaf in - the tree. Unlike a usual binary tree, where we follow left child - pointers until we reach a null, here we follow the right child - pointer any time the left one is null, until we reach a leaf with - both child pointers null. The smallest chunk in the tree will be - somewhere along that path. - - The worst case number of steps to add, find, or remove a node is - bounded by the number of bits differentiating chunks within - bins. Under current bin calculations, this ranges from 6 up to 21 - (for 32 bit sizes) or up to 53 (for 64 bit sizes). The typical case - is of course much better. -*/ - -struct malloc_tree_chunk { - /* The first four fields must be compatible with malloc_chunk */ - size_t prev_foot; - size_t head; - struct malloc_tree_chunk* fd; - struct malloc_tree_chunk* bk; - - struct malloc_tree_chunk* child[2]; - struct malloc_tree_chunk* parent; - bindex_t index; -}; - -typedef struct malloc_tree_chunk tchunk; -typedef struct malloc_tree_chunk* tchunkptr; -typedef struct malloc_tree_chunk* tbinptr; /* The type of bins of trees */ - -/* A little helper macro for trees */ -#define leftmost_child(t) ((t)->child[0] != 0? (t)->child[0] : (t)->child[1]) - -/* ----------------------------- Segments -------------------------------- */ - -/* - Each malloc space may include non-contiguous segments, held in a - list headed by an embedded malloc_segment record representing the - top-most space. Segments also include flags holding properties of - the space. Large chunks that are directly allocated by mmap are not - included in this list. They are instead independently created and - destroyed without otherwise keeping track of them. - - Segment management mainly comes into play for spaces allocated by - MMAP. Any call to MMAP might or might not return memory that is - adjacent to an existing segment. MORECORE normally contiguously - extends the current space, so this space is almost always adjacent, - which is simpler and faster to deal with. (This is why MORECORE is - used preferentially to MMAP when both are available -- see - sys_alloc.) When allocating using MMAP, we don't use any of the - hinting mechanisms (inconsistently) supported in various - implementations of unix mmap, or distinguish reserving from - committing memory. Instead, we just ask for space, and exploit - contiguity when we get it. It is probably possible to do - better than this on some systems, but no general scheme seems - to be significantly better. - - Management entails a simpler variant of the consolidation scheme - used for chunks to reduce fragmentation -- new adjacent memory is - normally prepended or appended to an existing segment. However, - there are limitations compared to chunk consolidation that mostly - reflect the fact that segment processing is relatively infrequent - (occurring only when getting memory from system) and that we - don't expect to have huge numbers of segments: - - * Segments are not indexed, so traversal requires linear scans. (It - would be possible to index these, but is not worth the extra - overhead and complexity for most programs on most platforms.) - * New segments are only appended to old ones when holding top-most - memory; if they cannot be prepended to others, they are held in - different segments. - - Except for the top-most segment of an mstate, each segment record - is kept at the tail of its segment. Segments are added by pushing - segment records onto the list headed by &mstate.seg for the - containing mstate. - - Segment flags control allocation/merge/deallocation policies: - * If EXTERN_BIT set, then we did not allocate this segment, - and so should not try to deallocate or merge with others. - (This currently holds only for the initial segment passed - into create_mspace_with_base.) - * If IS_MMAPPED_BIT set, the segment may be merged with - other surrounding mmapped segments and trimmed/de-allocated - using munmap. - * If neither bit is set, then the segment was obtained using - MORECORE so can be merged with surrounding MORECORE'd segments - and deallocated/trimmed using MORECORE with negative arguments. -*/ - -struct malloc_segment { - char* base; /* base address */ - size_t size; /* allocated size */ - struct malloc_segment* next; /* ptr to next segment */ - flag_t sflags; /* mmap and extern flag */ -}; - -#define is_mmapped_segment(S) ((S)->sflags & IS_MMAPPED_BIT) -#define is_extern_segment(S) ((S)->sflags & EXTERN_BIT) - -typedef struct malloc_segment msegment; -typedef struct malloc_segment* msegmentptr; - -/* ---------------------------- malloc_state ----------------------------- */ - -/* - A malloc_state holds all of the bookkeeping for a space. - The main fields are: - - Top - The topmost chunk of the currently active segment. Its size is - cached in topsize. The actual size of topmost space is - topsize+TOP_FOOT_SIZE, which includes space reserved for adding - fenceposts and segment records if necessary when getting more - space from the system. The size at which to autotrim top is - cached from mparams in trim_check, except that it is disabled if - an autotrim fails. - - Designated victim (dv) - This is the preferred chunk for servicing small requests that - don't have exact fits. It is normally the chunk split off most - recently to service another small request. Its size is cached in - dvsize. The link fields of this chunk are not maintained since it - is not kept in a bin. - - SmallBins - An array of bin headers for free chunks. These bins hold chunks - with sizes less than MIN_LARGE_SIZE bytes. Each bin contains - chunks of all the same size, spaced 8 bytes apart. To simplify - use in double-linked lists, each bin header acts as a malloc_chunk - pointing to the real first node, if it exists (else pointing to - itself). This avoids special-casing for headers. But to avoid - waste, we allocate only the fd/bk pointers of bins, and then use - repositioning tricks to treat these as the fields of a chunk. - - TreeBins - Treebins are pointers to the roots of trees holding a range of - sizes. There are 2 equally spaced treebins for each power of two - from TREE_SHIFT to TREE_SHIFT+16. The last bin holds anything - larger. - - Bin maps - There is one bit map for small bins ("smallmap") and one for - treebins ("treemap). Each bin sets its bit when non-empty, and - clears the bit when empty. Bit operations are then used to avoid - bin-by-bin searching -- nearly all "search" is done without ever - looking at bins that won't be selected. The bit maps - conservatively use 32 bits per map word, even if on 64bit system. - For a good description of some of the bit-based techniques used - here, see Henry S. Warren Jr's book "Hacker's Delight" (and - supplement at http://hackersdelight.org/). Many of these are - intended to reduce the branchiness of paths through malloc etc, as - well as to reduce the number of memory locations read or written. - - Segments - A list of segments headed by an embedded malloc_segment record - representing the initial space. - - Address check support - The least_addr field is the least address ever obtained from - MORECORE or MMAP. Attempted frees and reallocs of any address less - than this are trapped (unless INSECURE is defined). - - Magic tag - A cross-check field that should always hold same value as mparams.magic. - - Flags - Bits recording whether to use MMAP, locks, or contiguous MORECORE - - Statistics - Each space keeps track of current and maximum system memory - obtained via MORECORE or MMAP. - - Trim support - Fields holding the amount of unused topmost memory that should trigger - timing, and a counter to force periodic scanning to release unused - non-topmost segments. - - Locking - If USE_LOCKS is defined, the "mutex" lock is acquired and released - around every public call using this mspace. - - Extension support - A void* pointer and a size_t field that can be used to help implement - extensions to this malloc. -*/ - -/* Bin types, widths and sizes */ -#define NSMALLBINS (32U) -#define NTREEBINS (32U) -#define SMALLBIN_SHIFT (3U) -#define SMALLBIN_WIDTH (SIZE_T_ONE << SMALLBIN_SHIFT) -#define TREEBIN_SHIFT (8U) -#define MIN_LARGE_SIZE (SIZE_T_ONE << TREEBIN_SHIFT) -#define MAX_SMALL_SIZE (MIN_LARGE_SIZE - SIZE_T_ONE) -#define MAX_SMALL_REQUEST (MAX_SMALL_SIZE - CHUNK_ALIGN_MASK - CHUNK_OVERHEAD) - -struct malloc_state { - binmap_t smallmap; - binmap_t treemap; - size_t dvsize; - size_t topsize; - char* least_addr; - mchunkptr dv; - mchunkptr top; - size_t trim_check; - size_t release_checks; - size_t magic; - mchunkptr smallbins[(NSMALLBINS+1)*2]; - tbinptr treebins[NTREEBINS]; - size_t footprint; - size_t max_footprint; - flag_t mflags; -#if USE_LOCKS - MLOCK_T mutex; /* locate lock among fields that rarely change */ -#endif /* USE_LOCKS */ - msegment seg; - void* extp; /* Unused but available for extensions */ - size_t exts; -}; - -typedef struct malloc_state* mstate; - -/* ------------- Global malloc_state and malloc_params ------------------- */ - -/* - malloc_params holds global properties, including those that can be - dynamically set using mallopt. There is a single instance, mparams, - initialized in init_mparams. Note that the non-zeroness of "magic" - also serves as an initialization flag. -*/ - -struct malloc_params { - volatile size_t magic; - size_t page_size; - size_t granularity; - size_t mmap_threshold; - size_t trim_threshold; - flag_t default_mflags; -}; - -static struct malloc_params mparams; - -/* Ensure mparams initialized */ -#define ensure_initialization() ((void)(mparams.magic != 0 || init_mparams())) - -#if !ONLY_MSPACES - -/* The global malloc_state used for all non-"mspace" calls */ -static struct malloc_state _gm_; -#define gm (&_gm_) -#define is_global(M) ((M) == &_gm_) - -#endif /* !ONLY_MSPACES */ - -#define is_initialized(M) ((M)->top != 0) - -/* -------------------------- system alloc setup ------------------------- */ - -/* Operations on mflags */ - -#define use_lock(M) ((M)->mflags & USE_LOCK_BIT) -#define enable_lock(M) ((M)->mflags |= USE_LOCK_BIT) -#define disable_lock(M) ((M)->mflags &= ~USE_LOCK_BIT) - -#define use_mmap(M) ((M)->mflags & USE_MMAP_BIT) -#define enable_mmap(M) ((M)->mflags |= USE_MMAP_BIT) -#define disable_mmap(M) ((M)->mflags &= ~USE_MMAP_BIT) - -#define use_noncontiguous(M) ((M)->mflags & USE_NONCONTIGUOUS_BIT) -#define disable_contiguous(M) ((M)->mflags |= USE_NONCONTIGUOUS_BIT) - -#define set_lock(M,L)\ - ((M)->mflags = (L)?\ - ((M)->mflags | USE_LOCK_BIT) :\ - ((M)->mflags & ~USE_LOCK_BIT)) - -/* page-align a size */ -#define page_align(S)\ - (((S) + (mparams.page_size - SIZE_T_ONE)) & ~(mparams.page_size - SIZE_T_ONE)) - -/* granularity-align a size */ -#define granularity_align(S)\ - (((S) + (mparams.granularity - SIZE_T_ONE))\ - & ~(mparams.granularity - SIZE_T_ONE)) - - -/* For mmap, use granularity alignment on windows, else page-align */ -#ifdef WIN32 -#define mmap_align(S) granularity_align(S) -#else -#define mmap_align(S) page_align(S) -#endif - -/* For sys_alloc, enough padding to ensure can malloc request on success */ -#define SYS_ALLOC_PADDING (TOP_FOOT_SIZE + MALLOC_ALIGNMENT) - -#define is_page_aligned(S)\ - (((size_t)(S) & (mparams.page_size - SIZE_T_ONE)) == 0) -#define is_granularity_aligned(S)\ - (((size_t)(S) & (mparams.granularity - SIZE_T_ONE)) == 0) - -/* True if segment S holds address A */ -#define segment_holds(S, A)\ - ((char*)(A) >= S->base && (char*)(A) < S->base + S->size) - -/* Return segment holding given address */ -static msegmentptr segment_holding(mstate m, char* addr) { - msegmentptr sp = &m->seg; - for (;;) { - if (addr >= sp->base && addr < sp->base + sp->size) - return sp; - if ((sp = sp->next) == 0) - return 0; - } -} - -/* Return true if segment contains a segment link */ -static int has_segment_link(mstate m, msegmentptr ss) { - msegmentptr sp = &m->seg; - for (;;) { - if ((char*)sp >= ss->base && (char*)sp < ss->base + ss->size) - return 1; - if ((sp = sp->next) == 0) - return 0; - } -} - -#ifndef MORECORE_CANNOT_TRIM -#define should_trim(M,s) ((s) > (M)->trim_check) -#else /* MORECORE_CANNOT_TRIM */ -#define should_trim(M,s) (0) -#endif /* MORECORE_CANNOT_TRIM */ - -/* - TOP_FOOT_SIZE is padding at the end of a segment, including space - that may be needed to place segment records and fenceposts when new - noncontiguous segments are added. -*/ -#define TOP_FOOT_SIZE\ - (align_offset(chunk2mem(0))+pad_request(sizeof(struct malloc_segment))+MIN_CHUNK_SIZE) - - -/* ------------------------------- Hooks -------------------------------- */ - -/* - PREACTION should be defined to return 0 on success, and nonzero on - failure. If you are not using locking, you can redefine these to do - anything you like. -*/ - -#if USE_LOCKS - -#define PREACTION(M) ((use_lock(M))? ACQUIRE_LOCK(&(M)->mutex) : 0) -#define POSTACTION(M) { if (use_lock(M)) RELEASE_LOCK(&(M)->mutex); } -#else /* USE_LOCKS */ - -#ifndef PREACTION -#define PREACTION(M) (0) -#endif /* PREACTION */ - -#ifndef POSTACTION -#define POSTACTION(M) -#endif /* POSTACTION */ - -#endif /* USE_LOCKS */ - -/* - CORRUPTION_ERROR_ACTION is triggered upon detected bad addresses. - USAGE_ERROR_ACTION is triggered on detected bad frees and - reallocs. The argument p is an address that might have triggered the - fault. It is ignored by the two predefined actions, but might be - useful in custom actions that try to help diagnose errors. -*/ - -#if PROCEED_ON_ERROR - -/* A count of the number of corruption errors causing resets */ -int malloc_corruption_error_count; - -/* default corruption action */ -static void reset_on_error(mstate m); - -#define CORRUPTION_ERROR_ACTION(m) reset_on_error(m) -#define USAGE_ERROR_ACTION(m, p) - -#else /* PROCEED_ON_ERROR */ - -#ifndef CORRUPTION_ERROR_ACTION -#define CORRUPTION_ERROR_ACTION(m) ABORT -#endif /* CORRUPTION_ERROR_ACTION */ - -#ifndef USAGE_ERROR_ACTION -#define USAGE_ERROR_ACTION(m,p) ABORT -#endif /* USAGE_ERROR_ACTION */ - -#endif /* PROCEED_ON_ERROR */ - -/* -------------------------- Debugging setup ---------------------------- */ - -#if ! DEBUG - -#define check_free_chunk(M,P) -#define check_inuse_chunk(M,P) -#define check_malloced_chunk(M,P,N) -#define check_mmapped_chunk(M,P) -#define check_malloc_state(M) -#define check_top_chunk(M,P) - -#else /* DEBUG */ -#define check_free_chunk(M,P) do_check_free_chunk(M,P) -#define check_inuse_chunk(M,P) do_check_inuse_chunk(M,P) -#define check_top_chunk(M,P) do_check_top_chunk(M,P) -#define check_malloced_chunk(M,P,N) do_check_malloced_chunk(M,P,N) -#define check_mmapped_chunk(M,P) do_check_mmapped_chunk(M,P) -#define check_malloc_state(M) do_check_malloc_state(M) - -static void do_check_any_chunk(mstate m, mchunkptr p); -static void do_check_top_chunk(mstate m, mchunkptr p); -static void do_check_mmapped_chunk(mstate m, mchunkptr p); -static void do_check_inuse_chunk(mstate m, mchunkptr p); -static void do_check_free_chunk(mstate m, mchunkptr p); -static void do_check_malloced_chunk(mstate m, void* mem, size_t s); -static void do_check_tree(mstate m, tchunkptr t); -static void do_check_treebin(mstate m, bindex_t i); -static void do_check_smallbin(mstate m, bindex_t i); -static void do_check_malloc_state(mstate m); -static int bin_find(mstate m, mchunkptr x); -static size_t traverse_and_check(mstate m); -#endif /* DEBUG */ - -/* ---------------------------- Indexing Bins ---------------------------- */ - -#define is_small(s) (((s) >> SMALLBIN_SHIFT) < NSMALLBINS) -#define small_index(s) ((s) >> SMALLBIN_SHIFT) -#define small_index2size(i) ((i) << SMALLBIN_SHIFT) -#define MIN_SMALL_INDEX (small_index(MIN_CHUNK_SIZE)) - -/* addressing by index. See above about smallbin repositioning */ -#define smallbin_at(M, i) ((sbinptr)((char*)&((M)->smallbins[(i)<<1]))) -#define treebin_at(M,i) (&((M)->treebins[i])) - -/* assign tree index for size S to variable I. Use x86 asm if possible */ -#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) -#define compute_tree_index(S, I)\ -{\ - unsigned int X = S >> TREEBIN_SHIFT;\ - if (X == 0)\ - I = 0;\ - else if (X > 0xFFFF)\ - I = NTREEBINS-1;\ - else {\ - unsigned int K;\ - __asm__("bsrl\t%1, %0\n\t" : "=r" (K) : "rm" (X));\ - I = (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\ - }\ -} - -#elif defined (__INTEL_COMPILER) -#define compute_tree_index(S, I)\ -{\ - size_t X = S >> TREEBIN_SHIFT;\ - if (X == 0)\ - I = 0;\ - else if (X > 0xFFFF)\ - I = NTREEBINS-1;\ - else {\ - unsigned int K = _bit_scan_reverse (X); \ - I = (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\ - }\ -} - -#elif defined(_MSC_VER) && _MSC_VER>=1300 -#define compute_tree_index(S, I)\ -{\ - size_t X = S >> TREEBIN_SHIFT;\ - if (X == 0)\ - I = 0;\ - else if (X > 0xFFFF)\ - I = NTREEBINS-1;\ - else {\ - unsigned int K;\ - _BitScanReverse((DWORD *) &K, X);\ - I = (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\ - }\ -} - -#else /* GNUC */ -#define compute_tree_index(S, I)\ -{\ - size_t X = S >> TREEBIN_SHIFT;\ - if (X == 0)\ - I = 0;\ - else if (X > 0xFFFF)\ - I = NTREEBINS-1;\ - else {\ - unsigned int Y = (unsigned int)X;\ - unsigned int N = ((Y - 0x100) >> 16) & 8;\ - unsigned int K = (((Y <<= N) - 0x1000) >> 16) & 4;\ - N += K;\ - N += K = (((Y <<= K) - 0x4000) >> 16) & 2;\ - K = 14 - N + ((Y <<= K) >> 15);\ - I = (K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1));\ - }\ -} -#endif /* GNUC */ - -/* Bit representing maximum resolved size in a treebin at i */ -#define bit_for_tree_index(i) \ - (i == NTREEBINS-1)? (SIZE_T_BITSIZE-1) : (((i) >> 1) + TREEBIN_SHIFT - 2) - -/* Shift placing maximum resolved bit in a treebin at i as sign bit */ -#define leftshift_for_tree_index(i) \ - ((i == NTREEBINS-1)? 0 : \ - ((SIZE_T_BITSIZE-SIZE_T_ONE) - (((i) >> 1) + TREEBIN_SHIFT - 2))) - -/* The size of the smallest chunk held in bin with index i */ -#define minsize_for_tree_index(i) \ - ((SIZE_T_ONE << (((i) >> 1) + TREEBIN_SHIFT)) | \ - (((size_t)((i) & SIZE_T_ONE)) << (((i) >> 1) + TREEBIN_SHIFT - 1))) - - -/* ------------------------ Operations on bin maps ----------------------- */ - -/* bit corresponding to given index */ -#define idx2bit(i) ((binmap_t)(1) << (i)) - -/* Mark/Clear bits with given index */ -#define mark_smallmap(M,i) ((M)->smallmap |= idx2bit(i)) -#define clear_smallmap(M,i) ((M)->smallmap &= ~idx2bit(i)) -#define smallmap_is_marked(M,i) ((M)->smallmap & idx2bit(i)) - -#define mark_treemap(M,i) ((M)->treemap |= idx2bit(i)) -#define clear_treemap(M,i) ((M)->treemap &= ~idx2bit(i)) -#define treemap_is_marked(M,i) ((M)->treemap & idx2bit(i)) - -/* isolate the least set bit of a bitmap */ -#define least_bit(x) ((x) & -(x)) - -/* mask with all bits to left of least bit of x on */ -#define left_bits(x) ((x<<1) | -(x<<1)) - -/* mask with all bits to left of or equal to least bit of x on */ -#define same_or_left_bits(x) ((x) | -(x)) - -/* index corresponding to given bit. Use x86 asm if possible */ - -#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) -#define compute_bit2idx(X, I)\ -{\ - unsigned int J;\ - __asm__("bsfl\t%1, %0\n\t" : "=r" (J) : "rm" (X));\ - I = (bindex_t)J;\ -} - -#elif defined (__INTEL_COMPILER) -#define compute_bit2idx(X, I)\ -{\ - unsigned int J;\ - J = _bit_scan_forward (X); \ - I = (bindex_t)J;\ -} - -#elif defined(_MSC_VER) && _MSC_VER>=1300 -#define compute_bit2idx(X, I)\ -{\ - unsigned int J;\ - _BitScanForward((DWORD *) &J, X);\ - I = (bindex_t)J;\ -} - -#elif USE_BUILTIN_FFS -#define compute_bit2idx(X, I) I = ffs(X)-1 - -#else -#define compute_bit2idx(X, I)\ -{\ - unsigned int Y = X - 1;\ - unsigned int K = Y >> (16-4) & 16;\ - unsigned int N = K; Y >>= K;\ - N += K = Y >> (8-3) & 8; Y >>= K;\ - N += K = Y >> (4-2) & 4; Y >>= K;\ - N += K = Y >> (2-1) & 2; Y >>= K;\ - N += K = Y >> (1-0) & 1; Y >>= K;\ - I = (bindex_t)(N + Y);\ -} -#endif /* GNUC */ - - -/* ----------------------- Runtime Check Support ------------------------- */ - -/* - For security, the main invariant is that malloc/free/etc never - writes to a static address other than malloc_state, unless static - malloc_state itself has been corrupted, which cannot occur via - malloc (because of these checks). In essence this means that we - believe all pointers, sizes, maps etc held in malloc_state, but - check all of those linked or offsetted from other embedded data - structures. These checks are interspersed with main code in a way - that tends to minimize their run-time cost. - - When FOOTERS is defined, in addition to range checking, we also - verify footer fields of inuse chunks, which can be used guarantee - that the mstate controlling malloc/free is intact. This is a - streamlined version of the approach described by William Robertson - et al in "Run-time Detection of Heap-based Overflows" LISA'03 - http://www.usenix.org/events/lisa03/tech/robertson.html The footer - of an inuse chunk holds the xor of its mstate and a random seed, - that is checked upon calls to free() and realloc(). This is - (probablistically) unguessable from outside the program, but can be - computed by any code successfully malloc'ing any chunk, so does not - itself provide protection against code that has already broken - security through some other means. Unlike Robertson et al, we - always dynamically check addresses of all offset chunks (previous, - next, etc). This turns out to be cheaper than relying on hashes. -*/ - -#if !INSECURE -/* Check if address a is at least as high as any from MORECORE or MMAP */ -#define ok_address(M, a) ((char*)(a) >= (M)->least_addr) -/* Check if address of next chunk n is higher than base chunk p */ -#define ok_next(p, n) ((char*)(p) < (char*)(n)) -/* Check if p has its cinuse bit on */ -#define ok_cinuse(p) cinuse(p) -/* Check if p has its pinuse bit on */ -#define ok_pinuse(p) pinuse(p) - -#else /* !INSECURE */ -#define ok_address(M, a) (1) -#define ok_next(b, n) (1) -#define ok_cinuse(p) (1) -#define ok_pinuse(p) (1) -#endif /* !INSECURE */ - -#if (FOOTERS && !INSECURE) -/* Check if (alleged) mstate m has expected magic field */ -#define ok_magic(M) ((M)->magic == mparams.magic) -#else /* (FOOTERS && !INSECURE) */ -#define ok_magic(M) (1) -#endif /* (FOOTERS && !INSECURE) */ - - -/* In gcc, use __builtin_expect to minimize impact of checks */ -#if !INSECURE -#if defined(__GNUC__) && __GNUC__ >= 3 -#define RTCHECK(e) __builtin_expect(e, 1) -#else /* GNUC */ -#define RTCHECK(e) (e) -#endif /* GNUC */ -#else /* !INSECURE */ -#define RTCHECK(e) (1) -#endif /* !INSECURE */ - -/* macros to set up inuse chunks with or without footers */ - -#if !FOOTERS - -#define mark_inuse_foot(M,p,s) - -/* Set cinuse bit and pinuse bit of next chunk */ -#define set_inuse(M,p,s)\ - ((p)->head = (((p)->head & PINUSE_BIT)|s|CINUSE_BIT),\ - ((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT) - -/* Set cinuse and pinuse of this chunk and pinuse of next chunk */ -#define set_inuse_and_pinuse(M,p,s)\ - ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\ - ((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT) - -/* Set size, cinuse and pinuse bit of this chunk */ -#define set_size_and_pinuse_of_inuse_chunk(M, p, s)\ - ((p)->head = (s|PINUSE_BIT|CINUSE_BIT)) - -#else /* FOOTERS */ - -/* Set foot of inuse chunk to be xor of mstate and seed */ -#define mark_inuse_foot(M,p,s)\ - (((mchunkptr)((char*)(p) + (s)))->prev_foot = ((size_t)(M) ^ mparams.magic)) - -#define get_mstate_for(p)\ - ((mstate)(((mchunkptr)((char*)(p) +\ - (chunksize(p))))->prev_foot ^ mparams.magic)) - -#define set_inuse(M,p,s)\ - ((p)->head = (((p)->head & PINUSE_BIT)|s|CINUSE_BIT),\ - (((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT), \ - mark_inuse_foot(M,p,s)) - -#define set_inuse_and_pinuse(M,p,s)\ - ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\ - (((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT),\ - mark_inuse_foot(M,p,s)) - -#define set_size_and_pinuse_of_inuse_chunk(M, p, s)\ - ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\ - mark_inuse_foot(M, p, s)) - -#endif /* !FOOTERS */ - -/* ---------------------------- setting mparams -------------------------- */ - -/* Initialize mparams */ -static int init_mparams(void) { -#ifdef NEED_GLOBAL_LOCK_INIT - if (malloc_global_mutex_status <= 0) - init_malloc_global_mutex(); -#endif - - ACQUIRE_MALLOC_GLOBAL_LOCK(); - if (mparams.magic == 0) { - size_t magic; - size_t psize; - size_t gsize; - -#ifndef WIN32 - psize = malloc_getpagesize; - gsize = ((DEFAULT_GRANULARITY != 0)? DEFAULT_GRANULARITY : psize); -#else /* WIN32 */ - { - SYSTEM_INFO system_info; - GetSystemInfo(&system_info); - psize = system_info.dwPageSize; - gsize = ((DEFAULT_GRANULARITY != 0)? - DEFAULT_GRANULARITY : system_info.dwAllocationGranularity); - } -#endif /* WIN32 */ - - /* Sanity-check configuration: - size_t must be unsigned and as wide as pointer type. - ints must be at least 4 bytes. - alignment must be at least 8. - Alignment, min chunk size, and page size must all be powers of 2. - */ - if ((sizeof(size_t) != sizeof(char*)) || - (MAX_SIZE_T < MIN_CHUNK_SIZE) || - (sizeof(int) < 4) || - (MALLOC_ALIGNMENT < (size_t)8U) || - ((MALLOC_ALIGNMENT & (MALLOC_ALIGNMENT-SIZE_T_ONE)) != 0) || - ((MCHUNK_SIZE & (MCHUNK_SIZE-SIZE_T_ONE)) != 0) || - ((gsize & (gsize-SIZE_T_ONE)) != 0) || - ((psize & (psize-SIZE_T_ONE)) != 0)) - ABORT; - - mparams.granularity = gsize; - mparams.page_size = psize; - mparams.mmap_threshold = DEFAULT_MMAP_THRESHOLD; - mparams.trim_threshold = DEFAULT_TRIM_THRESHOLD; -#if MORECORE_CONTIGUOUS - mparams.default_mflags = USE_LOCK_BIT|USE_MMAP_BIT; -#else /* MORECORE_CONTIGUOUS */ - mparams.default_mflags = USE_LOCK_BIT|USE_MMAP_BIT|USE_NONCONTIGUOUS_BIT; -#endif /* MORECORE_CONTIGUOUS */ - -#if !ONLY_MSPACES - /* Set up lock for main malloc area */ - gm->mflags = mparams.default_mflags; - (void)INITIAL_LOCK(&gm->mutex); -#endif - -#if (FOOTERS && !INSECURE) - { -#if USE_DEV_RANDOM - int fd; - unsigned char buf[sizeof(size_t)]; - /* Try to use /dev/urandom, else fall back on using time */ - if ((fd = open("/dev/urandom", O_RDONLY)) >= 0 && - read(fd, buf, sizeof(buf)) == sizeof(buf)) { - magic = *((size_t *) buf); - close(fd); - } - else -#endif /* USE_DEV_RANDOM */ -#ifdef WIN32 - magic = (size_t)(GetTickCount() ^ (size_t)0x55555555U); -#else - magic = (size_t)(time(0) ^ (size_t)0x55555555U); -#endif - magic |= (size_t)8U; /* ensure nonzero */ - magic &= ~(size_t)7U; /* improve chances of fault for bad values */ - } -#else /* (FOOTERS && !INSECURE) */ - magic = (size_t)0x58585858U; -#endif /* (FOOTERS && !INSECURE) */ - - mparams.magic = magic; - } - - RELEASE_MALLOC_GLOBAL_LOCK(); - return 1; -} - -/* support for mallopt */ -static int change_mparam(int param_number, int value) { - size_t val = (value == -1)? MAX_SIZE_T : (size_t)value; - ensure_initialization(); - switch(param_number) { - case M_TRIM_THRESHOLD: - mparams.trim_threshold = val; - return 1; - case M_GRANULARITY: - if (val >= mparams.page_size && ((val & (val-1)) == 0)) { - mparams.granularity = val; - return 1; - } - else - return 0; - case M_MMAP_THRESHOLD: - mparams.mmap_threshold = val; - return 1; - default: - return 0; - } -} - -#if DEBUG -/* ------------------------- Debugging Support --------------------------- */ - -/* Check properties of any chunk, whether free, inuse, mmapped etc */ -static void do_check_any_chunk(mstate m, mchunkptr p) { - assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD)); - assert(ok_address(m, p)); -} - -/* Check properties of top chunk */ -static void do_check_top_chunk(mstate m, mchunkptr p) { - msegmentptr sp = segment_holding(m, (char*)p); - size_t sz = p->head & ~INUSE_BITS; /* third-lowest bit can be set! */ - assert(sp != 0); - assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD)); - assert(ok_address(m, p)); - assert(sz == m->topsize); - assert(sz > 0); - assert(sz == ((sp->base + sp->size) - (char*)p) - TOP_FOOT_SIZE); - assert(pinuse(p)); - assert(!pinuse(chunk_plus_offset(p, sz))); -} - -/* Check properties of (inuse) mmapped chunks */ -static void do_check_mmapped_chunk(mstate m, mchunkptr p) { - size_t sz = chunksize(p); - size_t len = (sz + (p->prev_foot & ~IS_MMAPPED_BIT) + MMAP_FOOT_PAD); - assert(is_mmapped(p)); - assert(use_mmap(m)); - assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD)); - assert(ok_address(m, p)); - assert(!is_small(sz)); - assert((len & (mparams.page_size-SIZE_T_ONE)) == 0); - assert(chunk_plus_offset(p, sz)->head == FENCEPOST_HEAD); - assert(chunk_plus_offset(p, sz+SIZE_T_SIZE)->head == 0); -} - -/* Check properties of inuse chunks */ -static void do_check_inuse_chunk(mstate m, mchunkptr p) { - do_check_any_chunk(m, p); - assert(cinuse(p)); - assert(next_pinuse(p)); - /* If not pinuse and not mmapped, previous chunk has OK offset */ - assert(is_mmapped(p) || pinuse(p) || next_chunk(prev_chunk(p)) == p); - if (is_mmapped(p)) - do_check_mmapped_chunk(m, p); -} - -/* Check properties of free chunks */ -static void do_check_free_chunk(mstate m, mchunkptr p) { - size_t sz = chunksize(p); - mchunkptr next = chunk_plus_offset(p, sz); - do_check_any_chunk(m, p); - assert(!cinuse(p)); - assert(!next_pinuse(p)); - assert (!is_mmapped(p)); - if (p != m->dv && p != m->top) { - if (sz >= MIN_CHUNK_SIZE) { - assert((sz & CHUNK_ALIGN_MASK) == 0); - assert(is_aligned(chunk2mem(p))); - assert(next->prev_foot == sz); - assert(pinuse(p)); - assert (next == m->top || cinuse(next)); - assert(p->fd->bk == p); - assert(p->bk->fd == p); - } - else /* markers are always of size SIZE_T_SIZE */ - assert(sz == SIZE_T_SIZE); - } -} - -/* Check properties of malloced chunks at the point they are malloced */ -static void do_check_malloced_chunk(mstate m, void* mem, size_t s) { - if (mem != 0) { - mchunkptr p = mem2chunk(mem); - size_t sz = p->head & ~(PINUSE_BIT|CINUSE_BIT); - do_check_inuse_chunk(m, p); - assert((sz & CHUNK_ALIGN_MASK) == 0); - assert(sz >= MIN_CHUNK_SIZE); - assert(sz >= s); - /* unless mmapped, size is less than MIN_CHUNK_SIZE more than request */ - assert(is_mmapped(p) || sz < (s + MIN_CHUNK_SIZE)); - } -} - -/* Check a tree and its subtrees. */ -static void do_check_tree(mstate m, tchunkptr t) { - tchunkptr head = 0; - tchunkptr u = t; - bindex_t tindex = t->index; - size_t tsize = chunksize(t); - bindex_t idx; - compute_tree_index(tsize, idx); - assert(tindex == idx); - assert(tsize >= MIN_LARGE_SIZE); - assert(tsize >= minsize_for_tree_index(idx)); - assert((idx == NTREEBINS-1) || (tsize < minsize_for_tree_index((idx+1)))); - - do { /* traverse through chain of same-sized nodes */ - do_check_any_chunk(m, ((mchunkptr)u)); - assert(u->index == tindex); - assert(chunksize(u) == tsize); - assert(!cinuse(u)); - assert(!next_pinuse(u)); - assert(u->fd->bk == u); - assert(u->bk->fd == u); - if (u->parent == 0) { - assert(u->child[0] == 0); - assert(u->child[1] == 0); - } - else { - assert(head == 0); /* only one node on chain has parent */ - head = u; - assert(u->parent != u); - assert (u->parent->child[0] == u || - u->parent->child[1] == u || - *((tbinptr*)(u->parent)) == u); - if (u->child[0] != 0) { - assert(u->child[0]->parent == u); - assert(u->child[0] != u); - do_check_tree(m, u->child[0]); - } - if (u->child[1] != 0) { - assert(u->child[1]->parent == u); - assert(u->child[1] != u); - do_check_tree(m, u->child[1]); - } - if (u->child[0] != 0 && u->child[1] != 0) { - assert(chunksize(u->child[0]) < chunksize(u->child[1])); - } - } - u = u->fd; - } while (u != t); - assert(head != 0); -} - -/* Check all the chunks in a treebin. */ -static void do_check_treebin(mstate m, bindex_t i) { - tbinptr* tb = treebin_at(m, i); - tchunkptr t = *tb; - int empty = (m->treemap & (1U << i)) == 0; - if (t == 0) - assert(empty); - if (!empty) - do_check_tree(m, t); -} - -/* Check all the chunks in a smallbin. */ -static void do_check_smallbin(mstate m, bindex_t i) { - sbinptr b = smallbin_at(m, i); - mchunkptr p = b->bk; - unsigned int empty = (m->smallmap & (1U << i)) == 0; - if (p == b) - assert(empty); - if (!empty) { - for (; p != b; p = p->bk) { - size_t size = chunksize(p); - mchunkptr q; - /* each chunk claims to be free */ - do_check_free_chunk(m, p); - /* chunk belongs in bin */ - assert(small_index(size) == i); - assert(p->bk == b || chunksize(p->bk) == chunksize(p)); - /* chunk is followed by an inuse chunk */ - q = next_chunk(p); - if (q->head != FENCEPOST_HEAD) - do_check_inuse_chunk(m, q); - } - } -} - -/* Find x in a bin. Used in other check functions. */ -static int bin_find(mstate m, mchunkptr x) { - size_t size = chunksize(x); - if (is_small(size)) { - bindex_t sidx = small_index(size); - sbinptr b = smallbin_at(m, sidx); - if (smallmap_is_marked(m, sidx)) { - mchunkptr p = b; - do { - if (p == x) - return 1; - } while ((p = p->fd) != b); - } - } - else { - bindex_t tidx; - compute_tree_index(size, tidx); - if (treemap_is_marked(m, tidx)) { - tchunkptr t = *treebin_at(m, tidx); - size_t sizebits = size << leftshift_for_tree_index(tidx); - while (t != 0 && chunksize(t) != size) { - t = t->child[(sizebits >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1]; - sizebits <<= 1; - } - if (t != 0) { - tchunkptr u = t; - do { - if (u == (tchunkptr)x) - return 1; - } while ((u = u->fd) != t); - } - } - } - return 0; -} - -/* Traverse each chunk and check it; return total */ -static size_t traverse_and_check(mstate m) { - size_t sum = 0; - if (is_initialized(m)) { - msegmentptr s = &m->seg; - sum += m->topsize + TOP_FOOT_SIZE; - while (s != 0) { - mchunkptr q = align_as_chunk(s->base); - mchunkptr lastq = 0; - assert(pinuse(q)); - while (segment_holds(s, q) && - q != m->top && q->head != FENCEPOST_HEAD) { - sum += chunksize(q); - if (cinuse(q)) { - assert(!bin_find(m, q)); - do_check_inuse_chunk(m, q); - } - else { - assert(q == m->dv || bin_find(m, q)); - assert(lastq == 0 || cinuse(lastq)); /* Not 2 consecutive free */ - do_check_free_chunk(m, q); - } - lastq = q; - q = next_chunk(q); - } - s = s->next; - } - } - return sum; -} - -/* Check all properties of malloc_state. */ -static void do_check_malloc_state(mstate m) { - bindex_t i; - size_t total; - /* check bins */ - for (i = 0; i < NSMALLBINS; ++i) - do_check_smallbin(m, i); - for (i = 0; i < NTREEBINS; ++i) - do_check_treebin(m, i); - - if (m->dvsize != 0) { /* check dv chunk */ - do_check_any_chunk(m, m->dv); - assert(m->dvsize == chunksize(m->dv)); - assert(m->dvsize >= MIN_CHUNK_SIZE); - assert(bin_find(m, m->dv) == 0); - } - - if (m->top != 0) { /* check top chunk */ - do_check_top_chunk(m, m->top); - /*assert(m->topsize == chunksize(m->top)); redundant */ - assert(m->topsize > 0); - assert(bin_find(m, m->top) == 0); - } - - total = traverse_and_check(m); - assert(total <= m->footprint); - assert(m->footprint <= m->max_footprint); -} -#endif /* DEBUG */ - -/* ----------------------------- statistics ------------------------------ */ - -#if !NO_MALLINFO -static struct mallinfo internal_mallinfo(mstate m) { - struct mallinfo nm = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - ensure_initialization(); - if (!PREACTION(m)) { - check_malloc_state(m); - if (is_initialized(m)) { - size_t nfree = SIZE_T_ONE; /* top always free */ - size_t mfree = m->topsize + TOP_FOOT_SIZE; - size_t sum = mfree; - msegmentptr s = &m->seg; - while (s != 0) { - mchunkptr q = align_as_chunk(s->base); - while (segment_holds(s, q) && - q != m->top && q->head != FENCEPOST_HEAD) { - size_t sz = chunksize(q); - sum += sz; - if (!cinuse(q)) { - mfree += sz; - ++nfree; - } - q = next_chunk(q); - } - s = s->next; - } - - nm.arena = sum; - nm.ordblks = nfree; - nm.hblkhd = m->footprint - sum; - nm.usmblks = m->max_footprint; - nm.uordblks = m->footprint - mfree; - nm.fordblks = mfree; - nm.keepcost = m->topsize; - } - - POSTACTION(m); - } - return nm; -} -#endif /* !NO_MALLINFO */ - -static void internal_malloc_stats(mstate m) { - ensure_initialization(); - if (!PREACTION(m)) { - size_t maxfp = 0; - size_t fp = 0; - size_t used = 0; - check_malloc_state(m); - if (is_initialized(m)) { - msegmentptr s = &m->seg; - maxfp = m->max_footprint; - fp = m->footprint; - used = fp - (m->topsize + TOP_FOOT_SIZE); - - while (s != 0) { - mchunkptr q = align_as_chunk(s->base); - while (segment_holds(s, q) && - q != m->top && q->head != FENCEPOST_HEAD) { - if (!cinuse(q)) - used -= chunksize(q); - q = next_chunk(q); - } - s = s->next; - } - } - - fprintf(stderr, "max system bytes = %10lu\n", (unsigned long)(maxfp)); - fprintf(stderr, "system bytes = %10lu\n", (unsigned long)(fp)); - fprintf(stderr, "in use bytes = %10lu\n", (unsigned long)(used)); - - POSTACTION(m); - } -} - -/* ----------------------- Operations on smallbins ----------------------- */ - -/* - Various forms of linking and unlinking are defined as macros. Even - the ones for trees, which are very long but have very short typical - paths. This is ugly but reduces reliance on inlining support of - compilers. -*/ - -/* Link a free chunk into a smallbin */ -#define insert_small_chunk(M, P, S) {\ - bindex_t I = small_index(S);\ - mchunkptr B = smallbin_at(M, I);\ - mchunkptr F = B;\ - assert(S >= MIN_CHUNK_SIZE);\ - if (!smallmap_is_marked(M, I))\ - mark_smallmap(M, I);\ - else if (RTCHECK(ok_address(M, B->fd)))\ - F = B->fd;\ - else {\ - CORRUPTION_ERROR_ACTION(M);\ - }\ - B->fd = P;\ - F->bk = P;\ - P->fd = F;\ - P->bk = B;\ -} - -/* Unlink a chunk from a smallbin */ -#define unlink_small_chunk(M, P, S) {\ - mchunkptr F = P->fd;\ - mchunkptr B = P->bk;\ - bindex_t I = small_index(S);\ - assert(P != B);\ - assert(P != F);\ - assert(chunksize(P) == small_index2size(I));\ - if (F == B)\ - clear_smallmap(M, I);\ - else if (RTCHECK((F == smallbin_at(M,I) || ok_address(M, F)) &&\ - (B == smallbin_at(M,I) || ok_address(M, B)))) {\ - F->bk = B;\ - B->fd = F;\ - }\ - else {\ - CORRUPTION_ERROR_ACTION(M);\ - }\ -} - -/* Unlink the first chunk from a smallbin */ -#define unlink_first_small_chunk(M, B, P, I) {\ - mchunkptr F = P->fd;\ - assert(P != B);\ - assert(P != F);\ - assert(chunksize(P) == small_index2size(I));\ - if (B == F)\ - clear_smallmap(M, I);\ - else if (RTCHECK(ok_address(M, F))) {\ - B->fd = F;\ - F->bk = B;\ - }\ - else {\ - CORRUPTION_ERROR_ACTION(M);\ - }\ -} - - - -/* Replace dv node, binning the old one */ -/* Used only when dvsize known to be small */ -#define replace_dv(M, P, S) {\ - size_t DVS = M->dvsize;\ - if (DVS != 0) {\ - mchunkptr DV = M->dv;\ - assert(is_small(DVS));\ - insert_small_chunk(M, DV, DVS);\ - }\ - M->dvsize = S;\ - M->dv = P;\ -} - -/* ------------------------- Operations on trees ------------------------- */ - -/* Insert chunk into tree */ -#define insert_large_chunk(M, X, S) {\ - tbinptr* H;\ - bindex_t I;\ - compute_tree_index(S, I);\ - H = treebin_at(M, I);\ - X->index = I;\ - X->child[0] = X->child[1] = 0;\ - if (!treemap_is_marked(M, I)) {\ - mark_treemap(M, I);\ - *H = X;\ - X->parent = (tchunkptr)H;\ - X->fd = X->bk = X;\ - }\ - else {\ - tchunkptr T = *H;\ - size_t K = S << leftshift_for_tree_index(I);\ - for (;;) {\ - if (chunksize(T) != S) {\ - tchunkptr* C = &(T->child[(K >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1]);\ - K <<= 1;\ - if (*C != 0)\ - T = *C;\ - else if (RTCHECK(ok_address(M, C))) {\ - *C = X;\ - X->parent = T;\ - X->fd = X->bk = X;\ - break;\ - }\ - else {\ - CORRUPTION_ERROR_ACTION(M);\ - break;\ - }\ - }\ - else {\ - tchunkptr F = T->fd;\ - if (RTCHECK(ok_address(M, T) && ok_address(M, F))) {\ - T->fd = F->bk = X;\ - X->fd = F;\ - X->bk = T;\ - X->parent = 0;\ - break;\ - }\ - else {\ - CORRUPTION_ERROR_ACTION(M);\ - break;\ - }\ - }\ - }\ - }\ -} - -/* - Unlink steps: - - 1. If x is a chained node, unlink it from its same-sized fd/bk links - and choose its bk node as its replacement. - 2. If x was the last node of its size, but not a leaf node, it must - be replaced with a leaf node (not merely one with an open left or - right), to make sure that lefts and rights of descendants - correspond properly to bit masks. We use the rightmost descendant - of x. We could use any other leaf, but this is easy to locate and - tends to counteract removal of leftmosts elsewhere, and so keeps - paths shorter than minimally guaranteed. This doesn't loop much - because on average a node in a tree is near the bottom. - 3. If x is the base of a chain (i.e., has parent links) relink - x's parent and children to x's replacement (or null if none). -*/ - -#define unlink_large_chunk(M, X) {\ - tchunkptr XP = X->parent;\ - tchunkptr R;\ - if (X->bk != X) {\ - tchunkptr F = X->fd;\ - R = X->bk;\ - if (RTCHECK(ok_address(M, F))) {\ - F->bk = R;\ - R->fd = F;\ - }\ - else {\ - CORRUPTION_ERROR_ACTION(M);\ - }\ - }\ - else {\ - tchunkptr* RP;\ - if (((R = *(RP = &(X->child[1]))) != 0) ||\ - ((R = *(RP = &(X->child[0]))) != 0)) {\ - tchunkptr* CP;\ - while ((*(CP = &(R->child[1])) != 0) ||\ - (*(CP = &(R->child[0])) != 0)) {\ - R = *(RP = CP);\ - }\ - if (RTCHECK(ok_address(M, RP)))\ - *RP = 0;\ - else {\ - CORRUPTION_ERROR_ACTION(M);\ - }\ - }\ - }\ - if (XP != 0) {\ - tbinptr* H = treebin_at(M, X->index);\ - if (X == *H) {\ - if ((*H = R) == 0) \ - clear_treemap(M, X->index);\ - }\ - else if (RTCHECK(ok_address(M, XP))) {\ - if (XP->child[0] == X) \ - XP->child[0] = R;\ - else \ - XP->child[1] = R;\ - }\ - else\ - CORRUPTION_ERROR_ACTION(M);\ - if (R != 0) {\ - if (RTCHECK(ok_address(M, R))) {\ - tchunkptr C0, C1;\ - R->parent = XP;\ - if ((C0 = X->child[0]) != 0) {\ - if (RTCHECK(ok_address(M, C0))) {\ - R->child[0] = C0;\ - C0->parent = R;\ - }\ - else\ - CORRUPTION_ERROR_ACTION(M);\ - }\ - if ((C1 = X->child[1]) != 0) {\ - if (RTCHECK(ok_address(M, C1))) {\ - R->child[1] = C1;\ - C1->parent = R;\ - }\ - else\ - CORRUPTION_ERROR_ACTION(M);\ - }\ - }\ - else\ - CORRUPTION_ERROR_ACTION(M);\ - }\ - }\ -} - -/* Relays to large vs small bin operations */ - -#define insert_chunk(M, P, S)\ - if (is_small(S)) insert_small_chunk(M, P, S)\ - else { tchunkptr TP = (tchunkptr)(P); insert_large_chunk(M, TP, S); } - -#define unlink_chunk(M, P, S)\ - if (is_small(S)) unlink_small_chunk(M, P, S)\ - else { tchunkptr TP = (tchunkptr)(P); unlink_large_chunk(M, TP); } - - -/* Relays to internal calls to malloc/free from realloc, memalign etc */ - -#if ONLY_MSPACES -#define internal_malloc(m, b) mspace_malloc(m, b) -#define internal_free(m, mem) mspace_free(m,mem); -#else /* ONLY_MSPACES */ -#if MSPACES -#define internal_malloc(m, b)\ - (m == gm)? dlmalloc(b) : mspace_malloc(m, b) -#define internal_free(m, mem)\ - if (m == gm) dlfree(mem); else mspace_free(m,mem); -#else /* MSPACES */ -#define internal_malloc(m, b) dlmalloc(b) -#define internal_free(m, mem) dlfree(mem) -#endif /* MSPACES */ -#endif /* ONLY_MSPACES */ - -/* ----------------------- Direct-mmapping chunks ----------------------- */ - -/* - Directly mmapped chunks are set up with an offset to the start of - the mmapped region stored in the prev_foot field of the chunk. This - allows reconstruction of the required argument to MUNMAP when freed, - and also allows adjustment of the returned chunk to meet alignment - requirements (especially in memalign). There is also enough space - allocated to hold a fake next chunk of size SIZE_T_SIZE to maintain - the PINUSE bit so frees can be checked. -*/ - -/* Malloc using mmap */ -static void* mmap_alloc(mstate m, size_t nb) { - size_t mmsize = mmap_align(nb + SIX_SIZE_T_SIZES + CHUNK_ALIGN_MASK); - if (mmsize > nb) { /* Check for wrap around 0 */ - char* mm = (char*)(CALL_DIRECT_MMAP(mmsize)); - if (mm != CMFAIL) { - size_t offset = align_offset(chunk2mem(mm)); - size_t psize = mmsize - offset - MMAP_FOOT_PAD; - mchunkptr p = (mchunkptr)(mm + offset); - p->prev_foot = offset | IS_MMAPPED_BIT; - (p)->head = (psize|CINUSE_BIT); - mark_inuse_foot(m, p, psize); - chunk_plus_offset(p, psize)->head = FENCEPOST_HEAD; - chunk_plus_offset(p, psize+SIZE_T_SIZE)->head = 0; - - if (mm < m->least_addr) - m->least_addr = mm; - if ((m->footprint += mmsize) > m->max_footprint) - m->max_footprint = m->footprint; - assert(is_aligned(chunk2mem(p))); - check_mmapped_chunk(m, p); - return chunk2mem(p); - } - } - return 0; -} - -/* Realloc using mmap */ -static mchunkptr mmap_resize(mstate m, mchunkptr oldp, size_t nb) { - size_t oldsize = chunksize(oldp); - if (is_small(nb)) /* Can't shrink mmap regions below small size */ - return 0; - /* Keep old chunk if big enough but not too big */ - if (oldsize >= nb + SIZE_T_SIZE && - (oldsize - nb) <= (mparams.granularity << 1)) - return oldp; - else { - size_t offset = oldp->prev_foot & ~IS_MMAPPED_BIT; - size_t oldmmsize = oldsize + offset + MMAP_FOOT_PAD; - size_t newmmsize = mmap_align(nb + SIX_SIZE_T_SIZES + CHUNK_ALIGN_MASK); - char* cp = (char*)CALL_MREMAP((char*)oldp - offset, - oldmmsize, newmmsize, 1); - if (cp != CMFAIL) { - mchunkptr newp = (mchunkptr)(cp + offset); - size_t psize = newmmsize - offset - MMAP_FOOT_PAD; - newp->head = (psize|CINUSE_BIT); - mark_inuse_foot(m, newp, psize); - chunk_plus_offset(newp, psize)->head = FENCEPOST_HEAD; - chunk_plus_offset(newp, psize+SIZE_T_SIZE)->head = 0; - - if (cp < m->least_addr) - m->least_addr = cp; - if ((m->footprint += newmmsize - oldmmsize) > m->max_footprint) - m->max_footprint = m->footprint; - check_mmapped_chunk(m, newp); - return newp; - } - } - return 0; -} - -/* -------------------------- mspace management -------------------------- */ - -/* Initialize top chunk and its size */ -static void init_top(mstate m, mchunkptr p, size_t psize) { - /* Ensure alignment */ - size_t offset = align_offset(chunk2mem(p)); - p = (mchunkptr)((char*)p + offset); - psize -= offset; - - m->top = p; - m->topsize = psize; - p->head = psize | PINUSE_BIT; - /* set size of fake trailing chunk holding overhead space only once */ - chunk_plus_offset(p, psize)->head = TOP_FOOT_SIZE; - m->trim_check = mparams.trim_threshold; /* reset on each update */ -} - -/* Initialize bins for a new mstate that is otherwise zeroed out */ -static void init_bins(mstate m) { - /* Establish circular links for smallbins */ - bindex_t i; - for (i = 0; i < NSMALLBINS; ++i) { - sbinptr bin = smallbin_at(m,i); - bin->fd = bin->bk = bin; - } -} - -#if PROCEED_ON_ERROR - -/* default corruption action */ -static void reset_on_error(mstate m) { - int i; - ++malloc_corruption_error_count; - /* Reinitialize fields to forget about all memory */ - m->smallbins = m->treebins = 0; - m->dvsize = m->topsize = 0; - m->seg.base = 0; - m->seg.size = 0; - m->seg.next = 0; - m->top = m->dv = 0; - for (i = 0; i < NTREEBINS; ++i) - *treebin_at(m, i) = 0; - init_bins(m); -} -#endif /* PROCEED_ON_ERROR */ - -/* Allocate chunk and prepend remainder with chunk in successor base. */ -static void* prepend_alloc(mstate m, char* newbase, char* oldbase, - size_t nb) { - mchunkptr p = align_as_chunk(newbase); - mchunkptr oldfirst = align_as_chunk(oldbase); - size_t psize = (char*)oldfirst - (char*)p; - mchunkptr q = chunk_plus_offset(p, nb); - size_t qsize = psize - nb; - set_size_and_pinuse_of_inuse_chunk(m, p, nb); - - assert((char*)oldfirst > (char*)q); - assert(pinuse(oldfirst)); - assert(qsize >= MIN_CHUNK_SIZE); - - /* consolidate remainder with first chunk of old base */ - if (oldfirst == m->top) { - size_t tsize = m->topsize += qsize; - m->top = q; - q->head = tsize | PINUSE_BIT; - check_top_chunk(m, q); - } - else if (oldfirst == m->dv) { - size_t dsize = m->dvsize += qsize; - m->dv = q; - set_size_and_pinuse_of_free_chunk(q, dsize); - } - else { - if (!cinuse(oldfirst)) { - size_t nsize = chunksize(oldfirst); - unlink_chunk(m, oldfirst, nsize); - oldfirst = chunk_plus_offset(oldfirst, nsize); - qsize += nsize; - } - set_free_with_pinuse(q, qsize, oldfirst); - insert_chunk(m, q, qsize); - check_free_chunk(m, q); - } - - check_malloced_chunk(m, chunk2mem(p), nb); - return chunk2mem(p); -} - -/* Add a segment to hold a new noncontiguous region */ -static void add_segment(mstate m, char* tbase, size_t tsize, flag_t mmapped) { - /* Determine locations and sizes of segment, fenceposts, old top */ - char* old_top = (char*)m->top; - msegmentptr oldsp = segment_holding(m, old_top); - char* old_end = oldsp->base + oldsp->size; - size_t ssize = pad_request(sizeof(struct malloc_segment)); - char* rawsp = old_end - (ssize + FOUR_SIZE_T_SIZES + CHUNK_ALIGN_MASK); - size_t offset = align_offset(chunk2mem(rawsp)); - char* asp = rawsp + offset; - char* csp = (asp < (old_top + MIN_CHUNK_SIZE))? old_top : asp; - mchunkptr sp = (mchunkptr)csp; - msegmentptr ss = (msegmentptr)(chunk2mem(sp)); - mchunkptr tnext = chunk_plus_offset(sp, ssize); - mchunkptr p = tnext; - int nfences = 0; - - /* reset top to new space */ - init_top(m, (mchunkptr)tbase, tsize - TOP_FOOT_SIZE); - - /* Set up segment record */ - assert(is_aligned(ss)); - set_size_and_pinuse_of_inuse_chunk(m, sp, ssize); - *ss = m->seg; /* Push current record */ - m->seg.base = tbase; - m->seg.size = tsize; - m->seg.sflags = mmapped; - m->seg.next = ss; - - /* Insert trailing fenceposts */ - for (;;) { - mchunkptr nextp = chunk_plus_offset(p, SIZE_T_SIZE); - p->head = FENCEPOST_HEAD; - ++nfences; - if ((char*)(&(nextp->head)) < old_end) - p = nextp; - else - break; - } - assert(nfences >= 2); - - /* Insert the rest of old top into a bin as an ordinary free chunk */ - if (csp != old_top) { - mchunkptr q = (mchunkptr)old_top; - size_t psize = csp - old_top; - mchunkptr tn = chunk_plus_offset(q, psize); - set_free_with_pinuse(q, psize, tn); - insert_chunk(m, q, psize); - } - - check_top_chunk(m, m->top); -} - -/* -------------------------- System allocation -------------------------- */ - -/* Get memory from system using MORECORE or MMAP */ -static void* sys_alloc(mstate m, size_t nb) { - char* tbase = CMFAIL; - size_t tsize = 0; - flag_t mmap_flag = 0; - - ensure_initialization(); - - /* Directly map large chunks */ - if (use_mmap(m) && nb >= mparams.mmap_threshold) { - void* mem = mmap_alloc(m, nb); - if (mem != 0) - return mem; - } - - /* - Try getting memory in any of three ways (in most-preferred to - least-preferred order): - 1. A call to MORECORE that can normally contiguously extend memory. - (disabled if not MORECORE_CONTIGUOUS or not HAVE_MORECORE or - main space is mmapped or a previous contiguous call failed) - 2. A call to MMAP new space (disabled if not HAVE_MMAP). - Note that under the default settings, if MORECORE is unable to - fulfill a request, and HAVE_MMAP is true, then mmap is - used as a noncontiguous system allocator. This is a useful backup - strategy for systems with holes in address spaces -- in this case - sbrk cannot contiguously expand the heap, but mmap may be able to - find space. - 3. A call to MORECORE that cannot usually contiguously extend memory. - (disabled if not HAVE_MORECORE) - - In all cases, we need to request enough bytes from system to ensure - we can malloc nb bytes upon success, so pad with enough space for - top_foot, plus alignment-pad to make sure we don't lose bytes if - not on boundary, and round this up to a granularity unit. - */ - - if (MORECORE_CONTIGUOUS && !use_noncontiguous(m)) { - char* br = CMFAIL; - msegmentptr ss = (m->top == 0)? 0 : segment_holding(m, (char*)m->top); - size_t asize = 0; - ACQUIRE_MALLOC_GLOBAL_LOCK(); - - if (ss == 0) { /* First time through or recovery */ - char* base = (char*)CALL_MORECORE(0); - if (base != CMFAIL) { - asize = granularity_align(nb + SYS_ALLOC_PADDING); - /* Adjust to end on a page boundary */ - if (!is_page_aligned(base)) - asize += (page_align((size_t)base) - (size_t)base); - /* Can't call MORECORE if size is negative when treated as signed */ - if (asize < HALF_MAX_SIZE_T && - (br = (char*)(CALL_MORECORE(asize))) == base) { - tbase = base; - tsize = asize; - } - } - } - else { - /* Subtract out existing available top space from MORECORE request. */ - asize = granularity_align(nb - m->topsize + SYS_ALLOC_PADDING); - /* Use mem here only if it did continuously extend old space */ - if (asize < HALF_MAX_SIZE_T && - (br = (char*)(CALL_MORECORE(asize))) == ss->base+ss->size) { - tbase = br; - tsize = asize; - } - } - - if (tbase == CMFAIL) { /* Cope with partial failure */ - if (br != CMFAIL) { /* Try to use/extend the space we did get */ - if (asize < HALF_MAX_SIZE_T && - asize < nb + SYS_ALLOC_PADDING) { - size_t esize = granularity_align(nb + SYS_ALLOC_PADDING - asize); - if (esize < HALF_MAX_SIZE_T) { - char* end = (char*)CALL_MORECORE(esize); - if (end != CMFAIL) - asize += esize; - else { /* Can't use; try to release */ - (void) CALL_MORECORE(-asize); - br = CMFAIL; - } - } - } - } - if (br != CMFAIL) { /* Use the space we did get */ - tbase = br; - tsize = asize; - } - else - disable_contiguous(m); /* Don't try contiguous path in the future */ - } - - RELEASE_MALLOC_GLOBAL_LOCK(); - } - - if (HAVE_MMAP && tbase == CMFAIL) { /* Try MMAP */ - size_t rsize = granularity_align(nb + SYS_ALLOC_PADDING); - if (rsize > nb) { /* Fail if wraps around zero */ - char* mp = (char*)(CALL_MMAP(rsize)); - if (mp != CMFAIL) { - tbase = mp; - tsize = rsize; - mmap_flag = IS_MMAPPED_BIT; - } - } - } - - if (HAVE_MORECORE && tbase == CMFAIL) { /* Try noncontiguous MORECORE */ - size_t asize = granularity_align(nb + SYS_ALLOC_PADDING); - if (asize < HALF_MAX_SIZE_T) { - char* br = CMFAIL; - char* end = CMFAIL; - ACQUIRE_MALLOC_GLOBAL_LOCK(); - br = (char*)(CALL_MORECORE(asize)); - end = (char*)(CALL_MORECORE(0)); - RELEASE_MALLOC_GLOBAL_LOCK(); - if (br != CMFAIL && end != CMFAIL && br < end) { - size_t ssize = end - br; - if (ssize > nb + TOP_FOOT_SIZE) { - tbase = br; - tsize = ssize; - } - } - } - } - - if (tbase != CMFAIL) { - - if ((m->footprint += tsize) > m->max_footprint) - m->max_footprint = m->footprint; - - if (!is_initialized(m)) { /* first-time initialization */ - m->seg.base = m->least_addr = tbase; - m->seg.size = tsize; - m->seg.sflags = mmap_flag; - m->magic = mparams.magic; - m->release_checks = MAX_RELEASE_CHECK_RATE; - init_bins(m); -#if !ONLY_MSPACES - if (is_global(m)) - init_top(m, (mchunkptr)tbase, tsize - TOP_FOOT_SIZE); - else -#endif - { - /* Offset top by embedded malloc_state */ - mchunkptr mn = next_chunk(mem2chunk(m)); - init_top(m, mn, (size_t)((tbase + tsize) - (char*)mn) -TOP_FOOT_SIZE); - } - } - - else { - /* Try to merge with an existing segment */ - msegmentptr sp = &m->seg; - /* Only consider most recent segment if traversal suppressed */ - while (sp != 0 && tbase != sp->base + sp->size) - sp = (NO_SEGMENT_TRAVERSAL) ? 0 : sp->next; - if (sp != 0 && - !is_extern_segment(sp) && - (sp->sflags & IS_MMAPPED_BIT) == mmap_flag && - segment_holds(sp, m->top)) { /* append */ - sp->size += tsize; - init_top(m, m->top, m->topsize + tsize); - } - else { - if (tbase < m->least_addr) - m->least_addr = tbase; - sp = &m->seg; - while (sp != 0 && sp->base != tbase + tsize) - sp = (NO_SEGMENT_TRAVERSAL) ? 0 : sp->next; - if (sp != 0 && - !is_extern_segment(sp) && - (sp->sflags & IS_MMAPPED_BIT) == mmap_flag) { - char* oldbase = sp->base; - sp->base = tbase; - sp->size += tsize; - return prepend_alloc(m, tbase, oldbase, nb); - } - else - add_segment(m, tbase, tsize, mmap_flag); - } - } - - if (nb < m->topsize) { /* Allocate from new or extended top space */ - size_t rsize = m->topsize -= nb; - mchunkptr p = m->top; - mchunkptr r = m->top = chunk_plus_offset(p, nb); - r->head = rsize | PINUSE_BIT; - set_size_and_pinuse_of_inuse_chunk(m, p, nb); - check_top_chunk(m, m->top); - check_malloced_chunk(m, chunk2mem(p), nb); - return chunk2mem(p); - } - } - - MALLOC_FAILURE_ACTION; - return 0; -} - -/* ----------------------- system deallocation -------------------------- */ - -/* Unmap and unlink any mmapped segments that don't contain used chunks */ -static size_t release_unused_segments(mstate m) { - size_t released = 0; - int nsegs = 0; - msegmentptr pred = &m->seg; - msegmentptr sp = pred->next; - while (sp != 0) { - char* base = sp->base; - size_t size = sp->size; - msegmentptr next = sp->next; - ++nsegs; - if (is_mmapped_segment(sp) && !is_extern_segment(sp)) { - mchunkptr p = align_as_chunk(base); - size_t psize = chunksize(p); - /* Can unmap if first chunk holds entire segment and not pinned */ - if (!cinuse(p) && (char*)p + psize >= base + size - TOP_FOOT_SIZE) { - tchunkptr tp = (tchunkptr)p; - assert(segment_holds(sp, (char*)sp)); - if (p == m->dv) { - m->dv = 0; - m->dvsize = 0; - } - else { - unlink_large_chunk(m, tp); - } - if (CALL_MUNMAP(base, size) == 0) { - released += size; - m->footprint -= size; - /* unlink obsoleted record */ - sp = pred; - sp->next = next; - } - else { /* back out if cannot unmap */ - insert_large_chunk(m, tp, psize); - } - } - } - if (NO_SEGMENT_TRAVERSAL) /* scan only first segment */ - break; - pred = sp; - sp = next; - } - /* Reset check counter */ - m->release_checks = ((nsegs > MAX_RELEASE_CHECK_RATE)? - nsegs : MAX_RELEASE_CHECK_RATE); - return released; -} - -static int sys_trim(mstate m, size_t pad) { - size_t released = 0; - ensure_initialization(); - if (pad < MAX_REQUEST && is_initialized(m)) { - pad += TOP_FOOT_SIZE; /* ensure enough room for segment overhead */ - - if (m->topsize > pad) { - /* Shrink top space in granularity-size units, keeping at least one */ - size_t unit = mparams.granularity; - size_t extra = ((m->topsize - pad + (unit - SIZE_T_ONE)) / unit - - SIZE_T_ONE) * unit; - msegmentptr sp = segment_holding(m, (char*)m->top); - - if (!is_extern_segment(sp)) { - if (is_mmapped_segment(sp)) { - if (HAVE_MMAP && - sp->size >= extra && - !has_segment_link(m, sp)) { /* can't shrink if pinned */ - size_t newsize = sp->size - extra; - /* Prefer mremap, fall back to munmap */ - if ((CALL_MREMAP(sp->base, sp->size, newsize, 0) != MFAIL) || - (CALL_MUNMAP(sp->base + newsize, extra) == 0)) { - released = extra; - } - } - } - else if (HAVE_MORECORE) { - if (extra >= HALF_MAX_SIZE_T) /* Avoid wrapping negative */ - extra = (HALF_MAX_SIZE_T) + SIZE_T_ONE - unit; - ACQUIRE_MALLOC_GLOBAL_LOCK(); - { - /* Make sure end of memory is where we last set it. */ - char* old_br = (char*)(CALL_MORECORE(0)); - if (old_br == sp->base + sp->size) { - char* rel_br = (char*)(CALL_MORECORE(-extra)); - char* new_br = (char*)(CALL_MORECORE(0)); - if (rel_br != CMFAIL && new_br < old_br) - released = old_br - new_br; - } - } - RELEASE_MALLOC_GLOBAL_LOCK(); - } - } - - if (released != 0) { - sp->size -= released; - m->footprint -= released; - init_top(m, m->top, m->topsize - released); - check_top_chunk(m, m->top); - } - } - - /* Unmap any unused mmapped segments */ - if (HAVE_MMAP) - released += release_unused_segments(m); - - /* On failure, disable autotrim to avoid repeated failed future calls */ - if (released == 0 && m->topsize > m->trim_check) - m->trim_check = MAX_SIZE_T; - } - - return (released != 0)? 1 : 0; -} - - -/* ---------------------------- malloc support --------------------------- */ - -/* allocate a large request from the best fitting chunk in a treebin */ -static void* tmalloc_large(mstate m, size_t nb) { - tchunkptr v = 0; - size_t rsize = -nb; /* Unsigned negation */ - tchunkptr t; - bindex_t idx; - compute_tree_index(nb, idx); - if ((t = *treebin_at(m, idx)) != 0) { - /* Traverse tree for this bin looking for node with size == nb */ - size_t sizebits = nb << leftshift_for_tree_index(idx); - tchunkptr rst = 0; /* The deepest untaken right subtree */ - for (;;) { - tchunkptr rt; - size_t trem = chunksize(t) - nb; - if (trem < rsize) { - v = t; - if ((rsize = trem) == 0) - break; - } - rt = t->child[1]; - t = t->child[(sizebits >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1]; - if (rt != 0 && rt != t) - rst = rt; - if (t == 0) { - t = rst; /* set t to least subtree holding sizes > nb */ - break; - } - sizebits <<= 1; - } - } - if (t == 0 && v == 0) { /* set t to root of next non-empty treebin */ - binmap_t leftbits = left_bits(idx2bit(idx)) & m->treemap; - if (leftbits != 0) { - bindex_t i; - binmap_t leastbit = least_bit(leftbits); - compute_bit2idx(leastbit, i); - t = *treebin_at(m, i); - } - } - - while (t != 0) { /* find smallest of tree or subtree */ - size_t trem = chunksize(t) - nb; - if (trem < rsize) { - rsize = trem; - v = t; - } - t = leftmost_child(t); - } - - /* If dv is a better fit, return 0 so malloc will use it */ - if (v != 0 && rsize < (size_t)(m->dvsize - nb)) { - if (RTCHECK(ok_address(m, v))) { /* split */ - mchunkptr r = chunk_plus_offset(v, nb); - assert(chunksize(v) == rsize + nb); - if (RTCHECK(ok_next(v, r))) { - unlink_large_chunk(m, v); - if (rsize < MIN_CHUNK_SIZE) - set_inuse_and_pinuse(m, v, (rsize + nb)); - else { - set_size_and_pinuse_of_inuse_chunk(m, v, nb); - set_size_and_pinuse_of_free_chunk(r, rsize); - insert_chunk(m, r, rsize); - } - return chunk2mem(v); - } - } - CORRUPTION_ERROR_ACTION(m); - } - return 0; -} - -/* allocate a small request from the best fitting chunk in a treebin */ -static void* tmalloc_small(mstate m, size_t nb) { - tchunkptr t, v; - size_t rsize; - bindex_t i; - binmap_t leastbit = least_bit(m->treemap); - compute_bit2idx(leastbit, i); - v = t = *treebin_at(m, i); - rsize = chunksize(t) - nb; - - while ((t = leftmost_child(t)) != 0) { - size_t trem = chunksize(t) - nb; - if (trem < rsize) { - rsize = trem; - v = t; - } - } - - if (RTCHECK(ok_address(m, v))) { - mchunkptr r = chunk_plus_offset(v, nb); - assert(chunksize(v) == rsize + nb); - if (RTCHECK(ok_next(v, r))) { - unlink_large_chunk(m, v); - if (rsize < MIN_CHUNK_SIZE) - set_inuse_and_pinuse(m, v, (rsize + nb)); - else { - set_size_and_pinuse_of_inuse_chunk(m, v, nb); - set_size_and_pinuse_of_free_chunk(r, rsize); - replace_dv(m, r, rsize); - } - return chunk2mem(v); - } - } - - CORRUPTION_ERROR_ACTION(m); - return 0; -} - -/* --------------------------- realloc support --------------------------- */ - -static void* internal_realloc(mstate m, void* oldmem, size_t bytes) { - if (bytes >= MAX_REQUEST) { - MALLOC_FAILURE_ACTION; - return 0; - } - if (!PREACTION(m)) { - mchunkptr oldp = mem2chunk(oldmem); - size_t oldsize = chunksize(oldp); - mchunkptr next = chunk_plus_offset(oldp, oldsize); - mchunkptr newp = 0; - void* extra = 0; - - /* Try to either shrink or extend into top. Else malloc-copy-free */ - - if (RTCHECK(ok_address(m, oldp) && ok_cinuse(oldp) && - ok_next(oldp, next) && ok_pinuse(next))) { - size_t nb = request2size(bytes); - if (is_mmapped(oldp)) - newp = mmap_resize(m, oldp, nb); - else if (oldsize >= nb) { /* already big enough */ - size_t rsize = oldsize - nb; - newp = oldp; - if (rsize >= MIN_CHUNK_SIZE) { - mchunkptr remainder = chunk_plus_offset(newp, nb); - set_inuse(m, newp, nb); - set_inuse(m, remainder, rsize); - extra = chunk2mem(remainder); - } - } - else if (next == m->top && oldsize + m->topsize > nb) { - /* Expand into top */ - size_t newsize = oldsize + m->topsize; - size_t newtopsize = newsize - nb; - mchunkptr newtop = chunk_plus_offset(oldp, nb); - set_inuse(m, oldp, nb); - newtop->head = newtopsize |PINUSE_BIT; - m->top = newtop; - m->topsize = newtopsize; - newp = oldp; - } - } - else { - USAGE_ERROR_ACTION(m, oldmem); - POSTACTION(m); - return 0; - } - - POSTACTION(m); - - if (newp != 0) { - if (extra != 0) { - internal_free(m, extra); - } - check_inuse_chunk(m, newp); - return chunk2mem(newp); - } - else { - void* newmem = internal_malloc(m, bytes); - if (newmem != 0) { - size_t oc = oldsize - overhead_for(oldp); - memcpy(newmem, oldmem, (oc < bytes)? oc : bytes); - internal_free(m, oldmem); - } - return newmem; - } - } - return 0; -} - -/* --------------------------- memalign support -------------------------- */ - -static void* internal_memalign(mstate m, size_t alignment, size_t bytes) { - if (alignment <= MALLOC_ALIGNMENT) /* Can just use malloc */ - return internal_malloc(m, bytes); - if (alignment < MIN_CHUNK_SIZE) /* must be at least a minimum chunk size */ - alignment = MIN_CHUNK_SIZE; - if ((alignment & (alignment-SIZE_T_ONE)) != 0) {/* Ensure a power of 2 */ - size_t a = MALLOC_ALIGNMENT << 1; - while (a < alignment) a <<= 1; - alignment = a; - } - - if (bytes >= MAX_REQUEST - alignment) { - if (m != 0) { /* Test isn't needed but avoids compiler warning */ - MALLOC_FAILURE_ACTION; - } - } - else { - size_t nb = request2size(bytes); - size_t req = nb + alignment + MIN_CHUNK_SIZE - CHUNK_OVERHEAD; - char* mem = (char*)internal_malloc(m, req); - if (mem != 0) { - void* leader = 0; - void* trailer = 0; - mchunkptr p = mem2chunk(mem); - - if (PREACTION(m)) return 0; - if ((((size_t)(mem)) % alignment) != 0) { /* misaligned */ - /* - Find an aligned spot inside chunk. Since we need to give - back leading space in a chunk of at least MIN_CHUNK_SIZE, if - the first calculation places us at a spot with less than - MIN_CHUNK_SIZE leader, we can move to the next aligned spot. - We've allocated enough total room so that this is always - possible. - */ - char* br = (char*)mem2chunk((size_t)(((size_t)(mem + - alignment - - SIZE_T_ONE)) & - -alignment)); - char* pos = ((size_t)(br - (char*)(p)) >= MIN_CHUNK_SIZE)? - br : br+alignment; - mchunkptr newp = (mchunkptr)pos; - size_t leadsize = pos - (char*)(p); - size_t newsize = chunksize(p) - leadsize; - - if (is_mmapped(p)) { /* For mmapped chunks, just adjust offset */ - newp->prev_foot = p->prev_foot + leadsize; - newp->head = (newsize|CINUSE_BIT); - } - else { /* Otherwise, give back leader, use the rest */ - set_inuse(m, newp, newsize); - set_inuse(m, p, leadsize); - leader = chunk2mem(p); - } - p = newp; - } - - /* Give back spare room at the end */ - if (!is_mmapped(p)) { - size_t size = chunksize(p); - if (size > nb + MIN_CHUNK_SIZE) { - size_t remainder_size = size - nb; - mchunkptr remainder = chunk_plus_offset(p, nb); - set_inuse(m, p, nb); - set_inuse(m, remainder, remainder_size); - trailer = chunk2mem(remainder); - } - } - - assert (chunksize(p) >= nb); - assert((((size_t)(chunk2mem(p))) % alignment) == 0); - check_inuse_chunk(m, p); - POSTACTION(m); - if (leader != 0) { - internal_free(m, leader); - } - if (trailer != 0) { - internal_free(m, trailer); - } - return chunk2mem(p); - } - } - return 0; -} - -/* ------------------------ comalloc/coalloc support --------------------- */ - -static void** ialloc(mstate m, - size_t n_elements, - size_t* sizes, - int opts, - void* chunks[]) { - /* - This provides common support for independent_X routines, handling - all of the combinations that can result. - - The opts arg has: - bit 0 set if all elements are same size (using sizes[0]) - bit 1 set if elements should be zeroed - */ - - size_t element_size; /* chunksize of each element, if all same */ - size_t contents_size; /* total size of elements */ - size_t array_size; /* request size of pointer array */ - void* mem; /* malloced aggregate space */ - mchunkptr p; /* corresponding chunk */ - size_t remainder_size; /* remaining bytes while splitting */ - void** marray; /* either "chunks" or malloced ptr array */ - mchunkptr array_chunk; /* chunk for malloced ptr array */ - flag_t was_enabled; /* to disable mmap */ - size_t size; - size_t i; - - ensure_initialization(); - /* compute array length, if needed */ - if (chunks != 0) { - if (n_elements == 0) - return chunks; /* nothing to do */ - marray = chunks; - array_size = 0; - } - else { - /* if empty req, must still return chunk representing empty array */ - if (n_elements == 0) - return (void**)internal_malloc(m, 0); - marray = 0; - array_size = request2size(n_elements * (sizeof(void*))); - } - - /* compute total element size */ - if (opts & 0x1) { /* all-same-size */ - element_size = request2size(*sizes); - contents_size = n_elements * element_size; - } - else { /* add up all the sizes */ - element_size = 0; - contents_size = 0; - for (i = 0; i != n_elements; ++i) - contents_size += request2size(sizes[i]); - } - - size = contents_size + array_size; - - /* - Allocate the aggregate chunk. First disable direct-mmapping so - malloc won't use it, since we would not be able to later - free/realloc space internal to a segregated mmap region. - */ - was_enabled = use_mmap(m); - disable_mmap(m); - mem = internal_malloc(m, size - CHUNK_OVERHEAD); - if (was_enabled) - enable_mmap(m); - if (mem == 0) - return 0; - - if (PREACTION(m)) return 0; - p = mem2chunk(mem); - remainder_size = chunksize(p); - - assert(!is_mmapped(p)); - - if (opts & 0x2) { /* optionally clear the elements */ - memset((size_t*)mem, 0, remainder_size - SIZE_T_SIZE - array_size); - } - - /* If not provided, allocate the pointer array as final part of chunk */ - if (marray == 0) { - size_t array_chunk_size; - array_chunk = chunk_plus_offset(p, contents_size); - array_chunk_size = remainder_size - contents_size; - marray = (void**) (chunk2mem(array_chunk)); - set_size_and_pinuse_of_inuse_chunk(m, array_chunk, array_chunk_size); - remainder_size = contents_size; - } - - /* split out elements */ - for (i = 0; ; ++i) { - marray[i] = chunk2mem(p); - if (i != n_elements-1) { - if (element_size != 0) - size = element_size; - else - size = request2size(sizes[i]); - remainder_size -= size; - set_size_and_pinuse_of_inuse_chunk(m, p, size); - p = chunk_plus_offset(p, size); - } - else { /* the final element absorbs any overallocation slop */ - set_size_and_pinuse_of_inuse_chunk(m, p, remainder_size); - break; - } - } - -#if DEBUG - if (marray != chunks) { - /* final element must have exactly exhausted chunk */ - if (element_size != 0) { - assert(remainder_size == element_size); - } - else { - assert(remainder_size == request2size(sizes[i])); - } - check_inuse_chunk(m, mem2chunk(marray)); - } - for (i = 0; i != n_elements; ++i) - check_inuse_chunk(m, mem2chunk(marray[i])); - -#endif /* DEBUG */ - - POSTACTION(m); - return marray; -} - - -/* -------------------------- public routines ---------------------------- */ - -#if !ONLY_MSPACES - -void* dlmalloc(size_t bytes) { - /* - Basic algorithm: - If a small request (< 256 bytes minus per-chunk overhead): - 1. If one exists, use a remainderless chunk in associated smallbin. - (Remainderless means that there are too few excess bytes to - represent as a chunk.) - 2. If it is big enough, use the dv chunk, which is normally the - chunk adjacent to the one used for the most recent small request. - 3. If one exists, split the smallest available chunk in a bin, - saving remainder in dv. - 4. If it is big enough, use the top chunk. - 5. If available, get memory from system and use it - Otherwise, for a large request: - 1. Find the smallest available binned chunk that fits, and use it - if it is better fitting than dv chunk, splitting if necessary. - 2. If better fitting than any binned chunk, use the dv chunk. - 3. If it is big enough, use the top chunk. - 4. If request size >= mmap threshold, try to directly mmap this chunk. - 5. If available, get memory from system and use it - - The ugly goto's here ensure that postaction occurs along all paths. - */ - -#if USE_LOCKS - ensure_initialization(); /* initialize in sys_alloc if not using locks */ -#endif - - if (!PREACTION(gm)) { - void* mem; - size_t nb; - if (bytes <= MAX_SMALL_REQUEST) { - bindex_t idx; - binmap_t smallbits; - nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes); - idx = small_index(nb); - smallbits = gm->smallmap >> idx; - - if ((smallbits & 0x3U) != 0) { /* Remainderless fit to a smallbin. */ - mchunkptr b, p; - idx += ~smallbits & 1; /* Uses next bin if idx empty */ - b = smallbin_at(gm, idx); - p = b->fd; - assert(chunksize(p) == small_index2size(idx)); - unlink_first_small_chunk(gm, b, p, idx); - set_inuse_and_pinuse(gm, p, small_index2size(idx)); - mem = chunk2mem(p); - check_malloced_chunk(gm, mem, nb); - goto postaction; - } - - else if (nb > gm->dvsize) { - if (smallbits != 0) { /* Use chunk in next nonempty smallbin */ - mchunkptr b, p, r; - size_t rsize; - bindex_t i; - binmap_t leftbits = (smallbits << idx) & left_bits(idx2bit(idx)); - binmap_t leastbit = least_bit(leftbits); - compute_bit2idx(leastbit, i); - b = smallbin_at(gm, i); - p = b->fd; - assert(chunksize(p) == small_index2size(i)); - unlink_first_small_chunk(gm, b, p, i); - rsize = small_index2size(i) - nb; - /* Fit here cannot be remainderless if 4byte sizes */ - if (SIZE_T_SIZE != 4 && rsize < MIN_CHUNK_SIZE) - set_inuse_and_pinuse(gm, p, small_index2size(i)); - else { - set_size_and_pinuse_of_inuse_chunk(gm, p, nb); - r = chunk_plus_offset(p, nb); - set_size_and_pinuse_of_free_chunk(r, rsize); - replace_dv(gm, r, rsize); - } - mem = chunk2mem(p); - check_malloced_chunk(gm, mem, nb); - goto postaction; - } - - else if (gm->treemap != 0 && (mem = tmalloc_small(gm, nb)) != 0) { - check_malloced_chunk(gm, mem, nb); - goto postaction; - } - } - } - else if (bytes >= MAX_REQUEST) - nb = MAX_SIZE_T; /* Too big to allocate. Force failure (in sys alloc) */ - else { - nb = pad_request(bytes); - if (gm->treemap != 0 && (mem = tmalloc_large(gm, nb)) != 0) { - check_malloced_chunk(gm, mem, nb); - goto postaction; - } - } - - if (nb <= gm->dvsize) { - size_t rsize = gm->dvsize - nb; - mchunkptr p = gm->dv; - if (rsize >= MIN_CHUNK_SIZE) { /* split dv */ - mchunkptr r = gm->dv = chunk_plus_offset(p, nb); - gm->dvsize = rsize; - set_size_and_pinuse_of_free_chunk(r, rsize); - set_size_and_pinuse_of_inuse_chunk(gm, p, nb); - } - else { /* exhaust dv */ - size_t dvs = gm->dvsize; - gm->dvsize = 0; - gm->dv = 0; - set_inuse_and_pinuse(gm, p, dvs); - } - mem = chunk2mem(p); - check_malloced_chunk(gm, mem, nb); - goto postaction; - } - - else if (nb < gm->topsize) { /* Split top */ - size_t rsize = gm->topsize -= nb; - mchunkptr p = gm->top; - mchunkptr r = gm->top = chunk_plus_offset(p, nb); - r->head = rsize | PINUSE_BIT; - set_size_and_pinuse_of_inuse_chunk(gm, p, nb); - mem = chunk2mem(p); - check_top_chunk(gm, gm->top); - check_malloced_chunk(gm, mem, nb); - goto postaction; - } - - mem = sys_alloc(gm, nb); - - postaction: - POSTACTION(gm); - return mem; - } - - return 0; -} - -void dlfree(void* mem) { - /* - Consolidate freed chunks with preceding or succeeding bordering - free chunks, if they exist, and then place in a bin. Intermixed - with special cases for top, dv, mmapped chunks, and usage errors. - */ - - if (mem != 0) { - mchunkptr p = mem2chunk(mem); -#if FOOTERS - mstate fm = get_mstate_for(p); - if (!ok_magic(fm)) { - USAGE_ERROR_ACTION(fm, p); - return; - } -#else /* FOOTERS */ -#define fm gm -#endif /* FOOTERS */ - if (!PREACTION(fm)) { - check_inuse_chunk(fm, p); - if (RTCHECK(ok_address(fm, p) && ok_cinuse(p))) { - size_t psize = chunksize(p); - mchunkptr next = chunk_plus_offset(p, psize); - if (!pinuse(p)) { - size_t prevsize = p->prev_foot; - if ((prevsize & IS_MMAPPED_BIT) != 0) { - prevsize &= ~IS_MMAPPED_BIT; - psize += prevsize + MMAP_FOOT_PAD; - if (CALL_MUNMAP((char*)p - prevsize, psize) == 0) - fm->footprint -= psize; - goto postaction; - } - else { - mchunkptr prev = chunk_minus_offset(p, prevsize); - psize += prevsize; - p = prev; - if (RTCHECK(ok_address(fm, prev))) { /* consolidate backward */ - if (p != fm->dv) { - unlink_chunk(fm, p, prevsize); - } - else if ((next->head & INUSE_BITS) == INUSE_BITS) { - fm->dvsize = psize; - set_free_with_pinuse(p, psize, next); - goto postaction; - } - } - else - goto erroraction; - } - } - - if (RTCHECK(ok_next(p, next) && ok_pinuse(next))) { - if (!cinuse(next)) { /* consolidate forward */ - if (next == fm->top) { - size_t tsize = fm->topsize += psize; - fm->top = p; - p->head = tsize | PINUSE_BIT; - if (p == fm->dv) { - fm->dv = 0; - fm->dvsize = 0; - } - if (should_trim(fm, tsize)) - sys_trim(fm, 0); - goto postaction; - } - else if (next == fm->dv) { - size_t dsize = fm->dvsize += psize; - fm->dv = p; - set_size_and_pinuse_of_free_chunk(p, dsize); - goto postaction; - } - else { - size_t nsize = chunksize(next); - psize += nsize; - unlink_chunk(fm, next, nsize); - set_size_and_pinuse_of_free_chunk(p, psize); - if (p == fm->dv) { - fm->dvsize = psize; - goto postaction; - } - } - } - else - set_free_with_pinuse(p, psize, next); - - if (is_small(psize)) { - insert_small_chunk(fm, p, psize); - check_free_chunk(fm, p); - } - else { - tchunkptr tp = (tchunkptr)p; - insert_large_chunk(fm, tp, psize); - check_free_chunk(fm, p); - if (--fm->release_checks == 0) - release_unused_segments(fm); - } - goto postaction; - } - } - erroraction: - USAGE_ERROR_ACTION(fm, p); - postaction: - POSTACTION(fm); - } - } -#if !FOOTERS -#undef fm -#endif /* FOOTERS */ -} - -void* dlcalloc(size_t n_elements, size_t elem_size) { - void* mem; - size_t req = 0; - if (n_elements != 0) { - req = n_elements * elem_size; - if (((n_elements | elem_size) & ~(size_t)0xffff) && - (req / n_elements != elem_size)) - req = MAX_SIZE_T; /* force downstream failure on overflow */ - } - mem = dlmalloc(req); - if (mem != 0 && calloc_must_clear(mem2chunk(mem))) - memset(mem, 0, req); - return mem; -} - -void* dlrealloc(void* oldmem, size_t bytes) { - if (oldmem == 0) - return dlmalloc(bytes); -#ifdef REALLOC_ZERO_BYTES_FREES - if (bytes == 0) { - dlfree(oldmem); - return 0; - } -#endif /* REALLOC_ZERO_BYTES_FREES */ - else { -#if ! FOOTERS - mstate m = gm; -#else /* FOOTERS */ - mstate m = get_mstate_for(mem2chunk(oldmem)); - if (!ok_magic(m)) { - USAGE_ERROR_ACTION(m, oldmem); - return 0; - } -#endif /* FOOTERS */ - return internal_realloc(m, oldmem, bytes); - } -} - -void* dlmemalign(size_t alignment, size_t bytes) { - return internal_memalign(gm, alignment, bytes); -} - -void** dlindependent_calloc(size_t n_elements, size_t elem_size, - void* chunks[]) { - size_t sz = elem_size; /* serves as 1-element array */ - return ialloc(gm, n_elements, &sz, 3, chunks); -} - -void** dlindependent_comalloc(size_t n_elements, size_t sizes[], - void* chunks[]) { - return ialloc(gm, n_elements, sizes, 0, chunks); -} - -void* dlvalloc(size_t bytes) { - size_t pagesz; - ensure_initialization(); - pagesz = mparams.page_size; - return dlmemalign(pagesz, bytes); -} - -void* dlpvalloc(size_t bytes) { - size_t pagesz; - ensure_initialization(); - pagesz = mparams.page_size; - return dlmemalign(pagesz, (bytes + pagesz - SIZE_T_ONE) & ~(pagesz - SIZE_T_ONE)); -} - -int dlmalloc_trim(size_t pad) { - ensure_initialization(); - int result = 0; - if (!PREACTION(gm)) { - result = sys_trim(gm, pad); - POSTACTION(gm); - } - return result; -} - -size_t dlmalloc_footprint(void) { - return gm->footprint; -} - -size_t dlmalloc_max_footprint(void) { - return gm->max_footprint; -} - -#if !NO_MALLINFO -struct mallinfo dlmallinfo(void) { - return internal_mallinfo(gm); -} -#endif /* NO_MALLINFO */ - -void dlmalloc_stats() { - internal_malloc_stats(gm); -} - -int dlmallopt(int param_number, int value) { - return change_mparam(param_number, value); -} - -#endif /* !ONLY_MSPACES */ - -size_t dlmalloc_usable_size(void* mem) { - if (mem != 0) { - mchunkptr p = mem2chunk(mem); - if (cinuse(p)) - return chunksize(p) - overhead_for(p); - } - return 0; -} - -/* ----------------------------- user mspaces ---------------------------- */ - -#if MSPACES - -static mstate init_user_mstate(char* tbase, size_t tsize) { - size_t msize = pad_request(sizeof(struct malloc_state)); - mchunkptr mn; - mchunkptr msp = align_as_chunk(tbase); - mstate m = (mstate)(chunk2mem(msp)); - memset(m, 0, msize); - (void)INITIAL_LOCK(&m->mutex); - msp->head = (msize|PINUSE_BIT|CINUSE_BIT); - m->seg.base = m->least_addr = tbase; - m->seg.size = m->footprint = m->max_footprint = tsize; - m->magic = mparams.magic; - m->release_checks = MAX_RELEASE_CHECK_RATE; - m->mflags = mparams.default_mflags; - m->extp = 0; - m->exts = 0; - disable_contiguous(m); - init_bins(m); - mn = next_chunk(mem2chunk(m)); - init_top(m, mn, (size_t)((tbase + tsize) - (char*)mn) - TOP_FOOT_SIZE); - check_top_chunk(m, m->top); - return m; -} - -mspace create_mspace(size_t capacity, int locked) { - mstate m = 0; - size_t msize; - ensure_initialization(); - msize = pad_request(sizeof(struct malloc_state)); - if (capacity < (size_t) -(msize + TOP_FOOT_SIZE + mparams.page_size)) { - size_t rs = ((capacity == 0)? mparams.granularity : - (capacity + TOP_FOOT_SIZE + msize)); - size_t tsize = granularity_align(rs); - char* tbase = (char*)(CALL_MMAP(tsize)); - if (tbase != CMFAIL) { - m = init_user_mstate(tbase, tsize); - m->seg.sflags = IS_MMAPPED_BIT; - set_lock(m, locked); - } - } - return (mspace)m; -} - -mspace create_mspace_with_base(void* base, size_t capacity, int locked) { - mstate m = 0; - size_t msize; - ensure_initialization(); - msize = pad_request(sizeof(struct malloc_state)); - if (capacity > msize + TOP_FOOT_SIZE && - capacity < (size_t) -(msize + TOP_FOOT_SIZE + mparams.page_size)) { - m = init_user_mstate((char*)base, capacity); - m->seg.sflags = EXTERN_BIT; - set_lock(m, locked); - } - return (mspace)m; -} - -int mspace_mmap_large_chunks(mspace msp, int enable) { - int ret = 0; - mstate ms = (mstate)msp; - if (!PREACTION(ms)) { - if (use_mmap(ms)) - ret = 1; - if (enable) - enable_mmap(ms); - else - disable_mmap(ms); - POSTACTION(ms); - } - return ret; -} - -size_t destroy_mspace(mspace msp) { - size_t freed = 0; - mstate ms = (mstate)msp; - if (ok_magic(ms)) { - msegmentptr sp = &ms->seg; - while (sp != 0) { - char* base = sp->base; - size_t size = sp->size; - flag_t flag = sp->sflags; - sp = sp->next; - if ((flag & IS_MMAPPED_BIT) && !(flag & EXTERN_BIT) && - CALL_MUNMAP(base, size) == 0) - freed += size; - } - } - else { - USAGE_ERROR_ACTION(ms,ms); - } - return freed; -} - -/* - mspace versions of routines are near-clones of the global - versions. This is not so nice but better than the alternatives. -*/ - - -void* mspace_malloc(mspace msp, size_t bytes) { - mstate ms = (mstate)msp; - if (!ok_magic(ms)) { - USAGE_ERROR_ACTION(ms,ms); - return 0; - } - if (!PREACTION(ms)) { - void* mem; - size_t nb; - if (bytes <= MAX_SMALL_REQUEST) { - bindex_t idx; - binmap_t smallbits; - nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes); - idx = small_index(nb); - smallbits = ms->smallmap >> idx; - - if ((smallbits & 0x3U) != 0) { /* Remainderless fit to a smallbin. */ - mchunkptr b, p; - idx += ~smallbits & 1; /* Uses next bin if idx empty */ - b = smallbin_at(ms, idx); - p = b->fd; - assert(chunksize(p) == small_index2size(idx)); - unlink_first_small_chunk(ms, b, p, idx); - set_inuse_and_pinuse(ms, p, small_index2size(idx)); - mem = chunk2mem(p); - check_malloced_chunk(ms, mem, nb); - goto postaction; - } - - else if (nb > ms->dvsize) { - if (smallbits != 0) { /* Use chunk in next nonempty smallbin */ - mchunkptr b, p, r; - size_t rsize; - bindex_t i; - binmap_t leftbits = (smallbits << idx) & left_bits(idx2bit(idx)); - binmap_t leastbit = least_bit(leftbits); - compute_bit2idx(leastbit, i); - b = smallbin_at(ms, i); - p = b->fd; - assert(chunksize(p) == small_index2size(i)); - unlink_first_small_chunk(ms, b, p, i); - rsize = small_index2size(i) - nb; - /* Fit here cannot be remainderless if 4byte sizes */ - if (SIZE_T_SIZE != 4 && rsize < MIN_CHUNK_SIZE) - set_inuse_and_pinuse(ms, p, small_index2size(i)); - else { - set_size_and_pinuse_of_inuse_chunk(ms, p, nb); - r = chunk_plus_offset(p, nb); - set_size_and_pinuse_of_free_chunk(r, rsize); - replace_dv(ms, r, rsize); - } - mem = chunk2mem(p); - check_malloced_chunk(ms, mem, nb); - goto postaction; - } - - else if (ms->treemap != 0 && (mem = tmalloc_small(ms, nb)) != 0) { - check_malloced_chunk(ms, mem, nb); - goto postaction; - } - } - } - else if (bytes >= MAX_REQUEST) - nb = MAX_SIZE_T; /* Too big to allocate. Force failure (in sys alloc) */ - else { - nb = pad_request(bytes); - if (ms->treemap != 0 && (mem = tmalloc_large(ms, nb)) != 0) { - check_malloced_chunk(ms, mem, nb); - goto postaction; - } - } - - if (nb <= ms->dvsize) { - size_t rsize = ms->dvsize - nb; - mchunkptr p = ms->dv; - if (rsize >= MIN_CHUNK_SIZE) { /* split dv */ - mchunkptr r = ms->dv = chunk_plus_offset(p, nb); - ms->dvsize = rsize; - set_size_and_pinuse_of_free_chunk(r, rsize); - set_size_and_pinuse_of_inuse_chunk(ms, p, nb); - } - else { /* exhaust dv */ - size_t dvs = ms->dvsize; - ms->dvsize = 0; - ms->dv = 0; - set_inuse_and_pinuse(ms, p, dvs); - } - mem = chunk2mem(p); - check_malloced_chunk(ms, mem, nb); - goto postaction; - } - - else if (nb < ms->topsize) { /* Split top */ - size_t rsize = ms->topsize -= nb; - mchunkptr p = ms->top; - mchunkptr r = ms->top = chunk_plus_offset(p, nb); - r->head = rsize | PINUSE_BIT; - set_size_and_pinuse_of_inuse_chunk(ms, p, nb); - mem = chunk2mem(p); - check_top_chunk(ms, ms->top); - check_malloced_chunk(ms, mem, nb); - goto postaction; - } - - mem = sys_alloc(ms, nb); - - postaction: - POSTACTION(ms); - return mem; - } - - return 0; -} - -void mspace_free(mspace msp, void* mem) { - if (mem != 0) { - mchunkptr p = mem2chunk(mem); -#if FOOTERS - mstate fm = get_mstate_for(p); -#else /* FOOTERS */ - mstate fm = (mstate)msp; -#endif /* FOOTERS */ - if (!ok_magic(fm)) { - USAGE_ERROR_ACTION(fm, p); - return; - } - if (!PREACTION(fm)) { - check_inuse_chunk(fm, p); - if (RTCHECK(ok_address(fm, p) && ok_cinuse(p))) { - size_t psize = chunksize(p); - mchunkptr next = chunk_plus_offset(p, psize); - if (!pinuse(p)) { - size_t prevsize = p->prev_foot; - if ((prevsize & IS_MMAPPED_BIT) != 0) { - prevsize &= ~IS_MMAPPED_BIT; - psize += prevsize + MMAP_FOOT_PAD; - if (CALL_MUNMAP((char*)p - prevsize, psize) == 0) - fm->footprint -= psize; - goto postaction; - } - else { - mchunkptr prev = chunk_minus_offset(p, prevsize); - psize += prevsize; - p = prev; - if (RTCHECK(ok_address(fm, prev))) { /* consolidate backward */ - if (p != fm->dv) { - unlink_chunk(fm, p, prevsize); - } - else if ((next->head & INUSE_BITS) == INUSE_BITS) { - fm->dvsize = psize; - set_free_with_pinuse(p, psize, next); - goto postaction; - } - } - else - goto erroraction; - } - } - - if (RTCHECK(ok_next(p, next) && ok_pinuse(next))) { - if (!cinuse(next)) { /* consolidate forward */ - if (next == fm->top) { - size_t tsize = fm->topsize += psize; - fm->top = p; - p->head = tsize | PINUSE_BIT; - if (p == fm->dv) { - fm->dv = 0; - fm->dvsize = 0; - } - if (should_trim(fm, tsize)) - sys_trim(fm, 0); - goto postaction; - } - else if (next == fm->dv) { - size_t dsize = fm->dvsize += psize; - fm->dv = p; - set_size_and_pinuse_of_free_chunk(p, dsize); - goto postaction; - } - else { - size_t nsize = chunksize(next); - psize += nsize; - unlink_chunk(fm, next, nsize); - set_size_and_pinuse_of_free_chunk(p, psize); - if (p == fm->dv) { - fm->dvsize = psize; - goto postaction; - } - } - } - else - set_free_with_pinuse(p, psize, next); - - if (is_small(psize)) { - insert_small_chunk(fm, p, psize); - check_free_chunk(fm, p); - } - else { - tchunkptr tp = (tchunkptr)p; - insert_large_chunk(fm, tp, psize); - check_free_chunk(fm, p); - if (--fm->release_checks == 0) - release_unused_segments(fm); - } - goto postaction; - } - } - erroraction: - USAGE_ERROR_ACTION(fm, p); - postaction: - POSTACTION(fm); - } - } -} - -void* mspace_calloc(mspace msp, size_t n_elements, size_t elem_size) { - void* mem; - size_t req = 0; - mstate ms = (mstate)msp; - if (!ok_magic(ms)) { - USAGE_ERROR_ACTION(ms,ms); - return 0; - } - if (n_elements != 0) { - req = n_elements * elem_size; - if (((n_elements | elem_size) & ~(size_t)0xffff) && - (req / n_elements != elem_size)) - req = MAX_SIZE_T; /* force downstream failure on overflow */ - } - mem = internal_malloc(ms, req); - if (mem != 0 && calloc_must_clear(mem2chunk(mem))) - memset(mem, 0, req); - return mem; -} - -void* mspace_realloc(mspace msp, void* oldmem, size_t bytes) { - if (oldmem == 0) - return mspace_malloc(msp, bytes); -#ifdef REALLOC_ZERO_BYTES_FREES - if (bytes == 0) { - mspace_free(msp, oldmem); - return 0; - } -#endif /* REALLOC_ZERO_BYTES_FREES */ - else { -#if FOOTERS - mchunkptr p = mem2chunk(oldmem); - mstate ms = get_mstate_for(p); -#else /* FOOTERS */ - mstate ms = (mstate)msp; -#endif /* FOOTERS */ - if (!ok_magic(ms)) { - USAGE_ERROR_ACTION(ms,ms); - return 0; - } - return internal_realloc(ms, oldmem, bytes); - } -} - -void* mspace_memalign(mspace msp, size_t alignment, size_t bytes) { - mstate ms = (mstate)msp; - if (!ok_magic(ms)) { - USAGE_ERROR_ACTION(ms,ms); - return 0; - } - return internal_memalign(ms, alignment, bytes); -} - -void** mspace_independent_calloc(mspace msp, size_t n_elements, - size_t elem_size, void* chunks[]) { - size_t sz = elem_size; /* serves as 1-element array */ - mstate ms = (mstate)msp; - if (!ok_magic(ms)) { - USAGE_ERROR_ACTION(ms,ms); - return 0; - } - return ialloc(ms, n_elements, &sz, 3, chunks); -} - -void** mspace_independent_comalloc(mspace msp, size_t n_elements, - size_t sizes[], void* chunks[]) { - mstate ms = (mstate)msp; - if (!ok_magic(ms)) { - USAGE_ERROR_ACTION(ms,ms); - return 0; - } - return ialloc(ms, n_elements, sizes, 0, chunks); -} - -int mspace_trim(mspace msp, size_t pad) { - int result = 0; - mstate ms = (mstate)msp; - if (ok_magic(ms)) { - if (!PREACTION(ms)) { - result = sys_trim(ms, pad); - POSTACTION(ms); - } - } - else { - USAGE_ERROR_ACTION(ms,ms); - } - return result; -} - -void mspace_malloc_stats(mspace msp) { - mstate ms = (mstate)msp; - if (ok_magic(ms)) { - internal_malloc_stats(ms); - } - else { - USAGE_ERROR_ACTION(ms,ms); - } -} - -size_t mspace_footprint(mspace msp) { - size_t result = 0; - mstate ms = (mstate)msp; - if (ok_magic(ms)) { - result = ms->footprint; - } - else { - USAGE_ERROR_ACTION(ms,ms); - } - return result; -} - - -size_t mspace_max_footprint(mspace msp) { - size_t result = 0; - mstate ms = (mstate)msp; - if (ok_magic(ms)) { - result = ms->max_footprint; - } - else { - USAGE_ERROR_ACTION(ms,ms); - } - return result; -} - - -#if !NO_MALLINFO -struct mallinfo mspace_mallinfo(mspace msp) { - mstate ms = (mstate)msp; - if (!ok_magic(ms)) { - USAGE_ERROR_ACTION(ms,ms); - } - return internal_mallinfo(ms); -} -#endif /* NO_MALLINFO */ - -size_t mspace_usable_size(void* mem) { - if (mem != 0) { - mchunkptr p = mem2chunk(mem); - if (cinuse(p)) - return chunksize(p) - overhead_for(p); - } - return 0; -} - -int mspace_mallopt(int param_number, int value) { - return change_mparam(param_number, value); -} - -#endif /* MSPACES */ - -/* -------------------- Alternative MORECORE functions ------------------- */ - -/* - Guidelines for creating a custom version of MORECORE: - - * For best performance, MORECORE should allocate in multiples of pagesize. - * MORECORE may allocate more memory than requested. (Or even less, - but this will usually result in a malloc failure.) - * MORECORE must not allocate memory when given argument zero, but - instead return one past the end address of memory from previous - nonzero call. - * For best performance, consecutive calls to MORECORE with positive - arguments should return increasing addresses, indicating that - space has been contiguously extended. - * Even though consecutive calls to MORECORE need not return contiguous - addresses, it must be OK for malloc'ed chunks to span multiple - regions in those cases where they do happen to be contiguous. - * MORECORE need not handle negative arguments -- it may instead - just return MFAIL when given negative arguments. - Negative arguments are always multiples of pagesize. MORECORE - must not misinterpret negative args as large positive unsigned - args. You can suppress all such calls from even occurring by defining - MORECORE_CANNOT_TRIM, - - As an example alternative MORECORE, here is a custom allocator - kindly contributed for pre-OSX macOS. It uses virtually but not - necessarily physically contiguous non-paged memory (locked in, - present and won't get swapped out). You can use it by uncommenting - this section, adding some #includes, and setting up the appropriate - defines above: - - #define MORECORE osMoreCore - - There is also a shutdown routine that should somehow be called for - cleanup upon program exit. - - #define MAX_POOL_ENTRIES 100 - #define MINIMUM_MORECORE_SIZE (64 * 1024U) - static int next_os_pool; - void *our_os_pools[MAX_POOL_ENTRIES]; - - void *osMoreCore(int size) - { - void *ptr = 0; - static void *sbrk_top = 0; - - if (size > 0) - { - if (size < MINIMUM_MORECORE_SIZE) - size = MINIMUM_MORECORE_SIZE; - if (CurrentExecutionLevel() == kTaskLevel) - ptr = PoolAllocateResident(size + RM_PAGE_SIZE, 0); - if (ptr == 0) - { - return (void *) MFAIL; - } - // save ptrs so they can be freed during cleanup - our_os_pools[next_os_pool] = ptr; - next_os_pool++; - ptr = (void *) ((((size_t) ptr) + RM_PAGE_MASK) & ~RM_PAGE_MASK); - sbrk_top = (char *) ptr + size; - return ptr; - } - else if (size < 0) - { - // we don't currently support shrink behavior - return (void *) MFAIL; - } - else - { - return sbrk_top; - } - } - - // cleanup any allocated memory pools - // called as last thing before shutting down driver - - void osCleanupMem(void) - { - void **ptr; - - for (ptr = our_os_pools; ptr < &our_os_pools[MAX_POOL_ENTRIES]; ptr++) - if (*ptr) - { - PoolDeallocate(*ptr); - *ptr = 0; - } - } - -*/ - - -/* ----------------------------------------------------------------------- -History: - V2.8.4 (not yet released) - * Add mspace_mmap_large_chunks; thanks to Jean Brouwers - * Fix insufficient sys_alloc padding when using 16byte alignment - * Fix bad error check in mspace_footprint - * Adaptations for ptmalloc, courtesy of Wolfram Gloger. - * Reentrant spin locks, courtesy of Earl Chew and others - * Win32 improvements, courtesy of Niall Douglas and Earl Chew - * Add NO_SEGMENT_TRAVERSAL and MAX_RELEASE_CHECK_RATE options - * Extension hook in malloc_state - * Various small adjustments to reduce warnings on some compilers - * Various configuration extensions/changes for more platforms. Thanks - to all who contributed these. - - V2.8.3 Thu Sep 22 11:16:32 2005 Doug Lea (dl at gee) - * Add max_footprint functions - * Ensure all appropriate literals are size_t - * Fix conditional compilation problem for some #define settings - * Avoid concatenating segments with the one provided - in create_mspace_with_base - * Rename some variables to avoid compiler shadowing warnings - * Use explicit lock initialization. - * Better handling of sbrk interference. - * Simplify and fix segment insertion, trimming and mspace_destroy - * Reinstate REALLOC_ZERO_BYTES_FREES option from 2.7.x - * Thanks especially to Dennis Flanagan for help on these. - - V2.8.2 Sun Jun 12 16:01:10 2005 Doug Lea (dl at gee) - * Fix memalign brace error. - - V2.8.1 Wed Jun 8 16:11:46 2005 Doug Lea (dl at gee) - * Fix improper #endif nesting in C++ - * Add explicit casts needed for C++ - - V2.8.0 Mon May 30 14:09:02 2005 Doug Lea (dl at gee) - * Use trees for large bins - * Support mspaces - * Use segments to unify sbrk-based and mmap-based system allocation, - removing need for emulation on most platforms without sbrk. - * Default safety checks - * Optional footer checks. Thanks to William Robertson for the idea. - * Internal code refactoring - * Incorporate suggestions and platform-specific changes. - Thanks to Dennis Flanagan, Colin Plumb, Niall Douglas, - Aaron Bachmann, Emery Berger, and others. - * Speed up non-fastbin processing enough to remove fastbins. - * Remove useless cfree() to avoid conflicts with other apps. - * Remove internal memcpy, memset. Compilers handle builtins better. - * Remove some options that no one ever used and rename others. - - V2.7.2 Sat Aug 17 09:07:30 2002 Doug Lea (dl at gee) - * Fix malloc_state bitmap array misdeclaration - - V2.7.1 Thu Jul 25 10:58:03 2002 Doug Lea (dl at gee) - * Allow tuning of FIRST_SORTED_BIN_SIZE - * Use PTR_UINT as type for all ptr->int casts. Thanks to John Belmonte. - * Better detection and support for non-contiguousness of MORECORE. - Thanks to Andreas Mueller, Conal Walsh, and Wolfram Gloger - * Bypass most of malloc if no frees. Thanks To Emery Berger. - * Fix freeing of old top non-contiguous chunk im sysmalloc. - * Raised default trim and map thresholds to 256K. - * Fix mmap-related #defines. Thanks to Lubos Lunak. - * Fix copy macros; added LACKS_FCNTL_H. Thanks to Neal Walfield. - * Branch-free bin calculation - * Default trim and mmap thresholds now 256K. - - V2.7.0 Sun Mar 11 14:14:06 2001 Doug Lea (dl at gee) - * Introduce independent_comalloc and independent_calloc. - Thanks to Michael Pachos for motivation and help. - * Make optional .h file available - * Allow > 2GB requests on 32bit systems. - * new WIN32 sbrk, mmap, munmap, lock code from <Walter@GeNeSys-e.de>. - Thanks also to Andreas Mueller <a.mueller at paradatec.de>, - and Anonymous. - * Allow override of MALLOC_ALIGNMENT (Thanks to Ruud Waij for - helping test this.) - * memalign: check alignment arg - * realloc: don't try to shift chunks backwards, since this - leads to more fragmentation in some programs and doesn't - seem to help in any others. - * Collect all cases in malloc requiring system memory into sysmalloc - * Use mmap as backup to sbrk - * Place all internal state in malloc_state - * Introduce fastbins (although similar to 2.5.1) - * Many minor tunings and cosmetic improvements - * Introduce USE_PUBLIC_MALLOC_WRAPPERS, USE_MALLOC_LOCK - * Introduce MALLOC_FAILURE_ACTION, MORECORE_CONTIGUOUS - Thanks to Tony E. Bennett <tbennett@nvidia.com> and others. - * Include errno.h to support default failure action. - - V2.6.6 Sun Dec 5 07:42:19 1999 Doug Lea (dl at gee) - * return null for negative arguments - * Added Several WIN32 cleanups from Martin C. Fong <mcfong at yahoo.com> - * Add 'LACKS_SYS_PARAM_H' for those systems without 'sys/param.h' - (e.g. WIN32 platforms) - * Cleanup header file inclusion for WIN32 platforms - * Cleanup code to avoid Microsoft Visual C++ compiler complaints - * Add 'USE_DL_PREFIX' to quickly allow co-existence with existing - memory allocation routines - * Set 'malloc_getpagesize' for WIN32 platforms (needs more work) - * Use 'assert' rather than 'ASSERT' in WIN32 code to conform to - usage of 'assert' in non-WIN32 code - * Improve WIN32 'sbrk()' emulation's 'findRegion()' routine to - avoid infinite loop - * Always call 'fREe()' rather than 'free()' - - V2.6.5 Wed Jun 17 15:57:31 1998 Doug Lea (dl at gee) - * Fixed ordering problem with boundary-stamping - - V2.6.3 Sun May 19 08:17:58 1996 Doug Lea (dl at gee) - * Added pvalloc, as recommended by H.J. Liu - * Added 64bit pointer support mainly from Wolfram Gloger - * Added anonymously donated WIN32 sbrk emulation - * Malloc, calloc, getpagesize: add optimizations from Raymond Nijssen - * malloc_extend_top: fix mask error that caused wastage after - foreign sbrks - * Add linux mremap support code from HJ Liu - - V2.6.2 Tue Dec 5 06:52:55 1995 Doug Lea (dl at gee) - * Integrated most documentation with the code. - * Add support for mmap, with help from - Wolfram Gloger (Gloger@lrz.uni-muenchen.de). - * Use last_remainder in more cases. - * Pack bins using idea from colin@nyx10.cs.du.edu - * Use ordered bins instead of best-fit threshold - * Eliminate block-local decls to simplify tracing and debugging. - * Support another case of realloc via move into top - * Fix error occurring when initial sbrk_base not word-aligned. - * Rely on page size for units instead of SBRK_UNIT to - avoid surprises about sbrk alignment conventions. - * Add mallinfo, mallopt. Thanks to Raymond Nijssen - (raymond@es.ele.tue.nl) for the suggestion. - * Add `pad' argument to malloc_trim and top_pad mallopt parameter. - * More precautions for cases where other routines call sbrk, - courtesy of Wolfram Gloger (Gloger@lrz.uni-muenchen.de). - * Added macros etc., allowing use in linux libc from - H.J. Lu (hjl@gnu.ai.mit.edu) - * Inverted this history list - - V2.6.1 Sat Dec 2 14:10:57 1995 Doug Lea (dl at gee) - * Re-tuned and fixed to behave more nicely with V2.6.0 changes. - * Removed all preallocation code since under current scheme - the work required to undo bad preallocations exceeds - the work saved in good cases for most test programs. - * No longer use return list or unconsolidated bins since - no scheme using them consistently outperforms those that don't - given above changes. - * Use best fit for very large chunks to prevent some worst-cases. - * Added some support for debugging - - V2.6.0 Sat Nov 4 07:05:23 1995 Doug Lea (dl at gee) - * Removed footers when chunks are in use. Thanks to - Paul Wilson (wilson@cs.texas.edu) for the suggestion. - - V2.5.4 Wed Nov 1 07:54:51 1995 Doug Lea (dl at gee) - * Added malloc_trim, with help from Wolfram Gloger - (wmglo@Dent.MED.Uni-Muenchen.DE). - - V2.5.3 Tue Apr 26 10:16:01 1994 Doug Lea (dl at g) - - V2.5.2 Tue Apr 5 16:20:40 1994 Doug Lea (dl at g) - * realloc: try to expand in both directions - * malloc: swap order of clean-bin strategy; - * realloc: only conditionally expand backwards - * Try not to scavenge used bins - * Use bin counts as a guide to preallocation - * Occasionally bin return list chunks in first scan - * Add a few optimizations from colin@nyx10.cs.du.edu - - V2.5.1 Sat Aug 14 15:40:43 1993 Doug Lea (dl at g) - * faster bin computation & slightly different binning - * merged all consolidations to one part of malloc proper - (eliminating old malloc_find_space & malloc_clean_bin) - * Scan 2 returns chunks (not just 1) - * Propagate failure in realloc if malloc returns 0 - * Add stuff to allow compilation on non-ANSI compilers - from kpv@research.att.com - - V2.5 Sat Aug 7 07:41:59 1993 Doug Lea (dl at g.oswego.edu) - * removed potential for odd address access in prev_chunk - * removed dependency on getpagesize.h - * misc cosmetics and a bit more internal documentation - * anticosmetics: mangled names in macros to evade debugger strangeness - * tested on sparc, hp-700, dec-mips, rs6000 - with gcc & native cc (hp, dec only) allowing - Detlefs & Zorn comparison study (in SIGPLAN Notices.) - - Trial version Fri Aug 28 13:14:29 1992 Doug Lea (dl at g.oswego.edu) - * Based loosely on libg++-1.2X malloc. (It retains some of the overall - structure of old version, but most details differ.) - -*/
diff --git a/compat/nedmalloc/nedmalloc.c b/compat/nedmalloc/nedmalloc.c deleted file mode 100644 index 145255d..0000000 --- a/compat/nedmalloc/nedmalloc.c +++ /dev/null
@@ -1,954 +0,0 @@ -/* Alternative malloc implementation for multiple threads without -lock contention based on dlmalloc. (C) 2005-2006 Niall Douglas - -Boost Software License - Version 1.0 - August 17th, 2003 - -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: - -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -*/ - -#ifdef _MSC_VER -/* Enable full aliasing on MSVC */ -/*#pragma optimize("a", on)*/ -#endif - -#pragma GCC diagnostic ignored "-Wunused-parameter" - -/*#define FULLSANITYCHECKS*/ - -#include "nedmalloc.h" -#if defined(WIN32) - #include <malloc.h> -#endif -#define MSPACES 1 -#define ONLY_MSPACES 1 -#ifndef USE_LOCKS - #define USE_LOCKS 1 -#endif -#define FOOTERS 1 /* Need to enable footers so frees lock the right mspace */ -#undef DEBUG /* dlmalloc wants DEBUG either 0 or 1 */ -#ifdef _DEBUG - #define DEBUG 1 -#else - #define DEBUG 0 -#endif -#ifdef NDEBUG /* Disable assert checking on release builds */ - #undef DEBUG -#endif -/* The default of 64Kb means we spend too much time kernel-side */ -#ifndef DEFAULT_GRANULARITY -#define DEFAULT_GRANULARITY (1*1024*1024) -#endif -/*#define USE_SPIN_LOCKS 0*/ - - -/*#define FORCEINLINE*/ -#include "malloc.c.h" -#ifdef NDEBUG /* Disable assert checking on release builds */ - #undef DEBUG -#endif - -/* The maximum concurrent threads in a pool possible */ -#ifndef MAXTHREADSINPOOL -#define MAXTHREADSINPOOL 16 -#endif -/* The maximum number of threadcaches which can be allocated */ -#ifndef THREADCACHEMAXCACHES -#define THREADCACHEMAXCACHES 256 -#endif -/* The maximum size to be allocated from the thread cache */ -#ifndef THREADCACHEMAX -#define THREADCACHEMAX 8192 -#endif -#if 0 -/* The number of cache entries for finer grained bins. This is (topbitpos(THREADCACHEMAX)-4)*2 */ -#define THREADCACHEMAXBINS ((13-4)*2) -#else -/* The number of cache entries. This is (topbitpos(THREADCACHEMAX)-4) */ -#define THREADCACHEMAXBINS (13-4) -#endif -/* Point at which the free space in a thread cache is garbage collected */ -#ifndef THREADCACHEMAXFREESPACE -#define THREADCACHEMAXFREESPACE (512*1024) -#endif - - -#ifdef WIN32 - #define TLSVAR DWORD - #define TLSALLOC(k) (*(k)=TlsAlloc(), TLS_OUT_OF_INDEXES==*(k)) - #define TLSFREE(k) (!TlsFree(k)) - #define TLSGET(k) TlsGetValue(k) - #define TLSSET(k, a) (!TlsSetValue(k, a)) - #ifdef DEBUG -static LPVOID ChkedTlsGetValue(DWORD idx) -{ - LPVOID ret=TlsGetValue(idx); - assert(S_OK==GetLastError()); - return ret; -} - #undef TLSGET - #define TLSGET(k) ChkedTlsGetValue(k) - #endif -#else - #define TLSVAR pthread_key_t - #define TLSALLOC(k) pthread_key_create(k, 0) - #define TLSFREE(k) pthread_key_delete(k) - #define TLSGET(k) pthread_getspecific(k) - #define TLSSET(k, a) pthread_setspecific(k, a) -#endif - -#if 0 -/* Only enable if testing with valgrind. Causes misoperation */ -#define mspace_malloc(p, s) malloc(s) -#define mspace_realloc(p, m, s) realloc(m, s) -#define mspace_calloc(p, n, s) calloc(n, s) -#define mspace_free(p, m) free(m) -#endif - - -#if defined(__cplusplus) -#if !defined(NO_NED_NAMESPACE) -namespace nedalloc { -#else -extern "C" { -#endif -#endif - -size_t nedblksize(void *mem) THROWSPEC -{ -#if 0 - /* Only enable if testing with valgrind. Causes misoperation */ - return THREADCACHEMAX; -#else - if(mem) - { - mchunkptr p=mem2chunk(mem); - assert(cinuse(p)); /* If this fails, someone tried to free a block twice */ - if(cinuse(p)) - return chunksize(p)-overhead_for(p); - } - return 0; -#endif -} - -void nedsetvalue(void *v) THROWSPEC { nedpsetvalue(0, v); } -void * nedmalloc(size_t size) THROWSPEC { return nedpmalloc(0, size); } -void * nedcalloc(size_t no, size_t size) THROWSPEC { return nedpcalloc(0, no, size); } -void * nedrealloc(void *mem, size_t size) THROWSPEC { return nedprealloc(0, mem, size); } -void nedfree(void *mem) THROWSPEC { nedpfree(0, mem); } -void * nedmemalign(size_t alignment, size_t bytes) THROWSPEC { return nedpmemalign(0, alignment, bytes); } -#if !NO_MALLINFO -struct mallinfo nedmallinfo(void) THROWSPEC { return nedpmallinfo(0); } -#endif -int nedmallopt(int parno, int value) THROWSPEC { return nedpmallopt(0, parno, value); } -int nedmalloc_trim(size_t pad) THROWSPEC { return nedpmalloc_trim(0, pad); } -void nedmalloc_stats(void) THROWSPEC { nedpmalloc_stats(0); } -size_t nedmalloc_footprint(void) THROWSPEC { return nedpmalloc_footprint(0); } -void **nedindependent_calloc(size_t elemsno, size_t elemsize, void **chunks) THROWSPEC { return nedpindependent_calloc(0, elemsno, elemsize, chunks); } -void **nedindependent_comalloc(size_t elems, size_t *sizes, void **chunks) THROWSPEC { return nedpindependent_comalloc(0, elems, sizes, chunks); } - -struct threadcacheblk_t; -typedef struct threadcacheblk_t threadcacheblk; -struct threadcacheblk_t -{ /* Keep less than 16 bytes on 32 bit systems and 32 bytes on 64 bit systems */ -#ifdef FULLSANITYCHECKS - unsigned int magic; -#endif - unsigned int lastUsed, size; - threadcacheblk *next, *prev; -}; -typedef struct threadcache_t -{ -#ifdef FULLSANITYCHECKS - unsigned int magic1; -#endif - int mymspace; /* Last mspace entry this thread used */ - long threadid; - unsigned int mallocs, frees, successes; - size_t freeInCache; /* How much free space is stored in this cache */ - threadcacheblk *bins[(THREADCACHEMAXBINS+1)*2]; -#ifdef FULLSANITYCHECKS - unsigned int magic2; -#endif -} threadcache; -struct nedpool_t -{ - MLOCK_T mutex; - void *uservalue; - int threads; /* Max entries in m to use */ - threadcache *caches[THREADCACHEMAXCACHES]; - TLSVAR mycache; /* Thread cache for this thread. 0 for unset, negative for use mspace-1 directly, otherwise is cache-1 */ - mstate m[MAXTHREADSINPOOL+1]; /* mspace entries for this pool */ -}; -static nedpool syspool; - -static FORCEINLINE unsigned int size2binidx(size_t _size) THROWSPEC -{ /* 8=1000 16=10000 20=10100 24=11000 32=100000 48=110000 4096=1000000000000 */ - unsigned int topbit, size=(unsigned int)(_size>>4); - /* 16=1 20=1 24=1 32=10 48=11 64=100 96=110 128=1000 4096=100000000 */ - -#if defined(__GNUC__) - topbit = sizeof(size)*__CHAR_BIT__ - 1 - __builtin_clz(size); -#elif defined(_MSC_VER) && _MSC_VER>=1300 - { - unsigned long bsrTopBit; - - _BitScanReverse(&bsrTopBit, size); - - topbit = bsrTopBit; - } -#else -#if 0 - union { - unsigned asInt[2]; - double asDouble; - }; - int n; - - asDouble = (double)size + 0.5; - topbit = (asInt[!FOX_BIGENDIAN] >> 20) - 1023; -#else - { - unsigned int x=size; - x = x | (x >> 1); - x = x | (x >> 2); - x = x | (x >> 4); - x = x | (x >> 8); - x = x | (x >>16); - x = ~x; - x = x - ((x >> 1) & 0x55555555); - x = (x & 0x33333333) + ((x >> 2) & 0x33333333); - x = (x + (x >> 4)) & 0x0F0F0F0F; - x = x + (x << 8); - x = x + (x << 16); - topbit=31 - (x >> 24); - } -#endif -#endif - return topbit; -} - - -#ifdef FULLSANITYCHECKS -static void tcsanitycheck(threadcacheblk **ptr) THROWSPEC -{ - assert((ptr[0] && ptr[1]) || (!ptr[0] && !ptr[1])); - if(ptr[0] && ptr[1]) - { - assert(nedblksize(ptr[0])>=sizeof(threadcacheblk)); - assert(nedblksize(ptr[1])>=sizeof(threadcacheblk)); - assert(*(unsigned int *) "NEDN"==ptr[0]->magic); - assert(*(unsigned int *) "NEDN"==ptr[1]->magic); - assert(!ptr[0]->prev); - assert(!ptr[1]->next); - if(ptr[0]==ptr[1]) - { - assert(!ptr[0]->next); - assert(!ptr[1]->prev); - } - } -} -static void tcfullsanitycheck(threadcache *tc) THROWSPEC -{ - threadcacheblk **tcbptr=tc->bins; - int n; - for(n=0; n<=THREADCACHEMAXBINS; n++, tcbptr+=2) - { - threadcacheblk *b, *ob=0; - tcsanitycheck(tcbptr); - for(b=tcbptr[0]; b; ob=b, b=b->next) - { - assert(*(unsigned int *) "NEDN"==b->magic); - assert(!ob || ob->next==b); - assert(!ob || b->prev==ob); - } - } -} -#endif - -static NOINLINE void RemoveCacheEntries(nedpool *p, threadcache *tc, unsigned int age) THROWSPEC -{ -#ifdef FULLSANITYCHECKS - tcfullsanitycheck(tc); -#endif - if(tc->freeInCache) - { - threadcacheblk **tcbptr=tc->bins; - int n; - for(n=0; n<=THREADCACHEMAXBINS; n++, tcbptr+=2) - { - threadcacheblk **tcb=tcbptr+1; /* come from oldest end of list */ - /*tcsanitycheck(tcbptr);*/ - for(; *tcb && tc->frees-(*tcb)->lastUsed>=age; ) - { - threadcacheblk *f=*tcb; - size_t blksize=f->size; /*nedblksize(f);*/ - assert(blksize<=nedblksize(f)); - assert(blksize); -#ifdef FULLSANITYCHECKS - assert(*(unsigned int *) "NEDN"==(*tcb)->magic); -#endif - *tcb=(*tcb)->prev; - if(*tcb) - (*tcb)->next=0; - else - *tcbptr=0; - tc->freeInCache-=blksize; - assert((long) tc->freeInCache>=0); - mspace_free(0, f); - /*tcsanitycheck(tcbptr);*/ - } - } - } -#ifdef FULLSANITYCHECKS - tcfullsanitycheck(tc); -#endif -} -static void DestroyCaches(nedpool *p) THROWSPEC -{ - { - threadcache *tc; - int n; - for(n=0; n<THREADCACHEMAXCACHES; n++) - { - if((tc=p->caches[n])) - { - tc->frees++; - RemoveCacheEntries(p, tc, 0); - assert(!tc->freeInCache); - tc->mymspace=-1; - tc->threadid=0; - mspace_free(0, tc); - p->caches[n]=0; - } - } - } -} - -static NOINLINE threadcache *AllocCache(nedpool *p) THROWSPEC -{ - threadcache *tc=0; - int n, end; - ACQUIRE_LOCK(&p->mutex); - for(n=0; n<THREADCACHEMAXCACHES && p->caches[n]; n++); - if(THREADCACHEMAXCACHES==n) - { /* List exhausted, so disable for this thread */ - RELEASE_LOCK(&p->mutex); - return 0; - } - tc=p->caches[n]=(threadcache *) mspace_calloc(p->m[0], 1, sizeof(threadcache)); - if(!tc) - { - RELEASE_LOCK(&p->mutex); - return 0; - } -#ifdef FULLSANITYCHECKS - tc->magic1=*(unsigned int *)"NEDMALC1"; - tc->magic2=*(unsigned int *)"NEDMALC2"; -#endif - tc->threadid=(long)(size_t)CURRENT_THREAD; - for(end=0; p->m[end]; end++); - tc->mymspace=tc->threadid % end; - RELEASE_LOCK(&p->mutex); - if(TLSSET(p->mycache, (void *)(size_t)(n+1))) abort(); - return tc; -} - -static void *threadcache_malloc(nedpool *p, threadcache *tc, size_t *size) THROWSPEC -{ - void *ret=0; - unsigned int bestsize; - unsigned int idx=size2binidx(*size); - size_t blksize=0; - threadcacheblk *blk, **binsptr; -#ifdef FULLSANITYCHECKS - tcfullsanitycheck(tc); -#endif - /* Calculate best fit bin size */ - bestsize=1<<(idx+4); -#if 0 - /* Finer grained bin fit */ - idx<<=1; - if(*size>bestsize) - { - idx++; - bestsize+=bestsize>>1; - } - if(*size>bestsize) - { - idx++; - bestsize=1<<(4+(idx>>1)); - } -#else - if(*size>bestsize) - { - idx++; - bestsize<<=1; - } -#endif - assert(bestsize>=*size); - if(*size<bestsize) *size=bestsize; - assert(*size<=THREADCACHEMAX); - assert(idx<=THREADCACHEMAXBINS); - binsptr=&tc->bins[idx*2]; - /* Try to match close, but move up a bin if necessary */ - blk=*binsptr; - if(!blk || blk->size<*size) - { /* Bump it up a bin */ - if(idx<THREADCACHEMAXBINS) - { - idx++; - binsptr+=2; - blk=*binsptr; - } - } - if(blk) - { - blksize=blk->size; /*nedblksize(blk);*/ - assert(nedblksize(blk)>=blksize); - assert(blksize>=*size); - if(blk->next) - blk->next->prev=0; - *binsptr=blk->next; - if(!*binsptr) - binsptr[1]=0; -#ifdef FULLSANITYCHECKS - blk->magic=0; -#endif - assert(binsptr[0]!=blk && binsptr[1]!=blk); - assert(nedblksize(blk)>=sizeof(threadcacheblk) && nedblksize(blk)<=THREADCACHEMAX+CHUNK_OVERHEAD); - /*printf("malloc: %p, %p, %p, %lu\n", p, tc, blk, (long) size);*/ - ret=(void *) blk; - } - ++tc->mallocs; - if(ret) - { - assert(blksize>=*size); - ++tc->successes; - tc->freeInCache-=blksize; - assert((long) tc->freeInCache>=0); - } -#if defined(DEBUG) && 0 - if(!(tc->mallocs & 0xfff)) - { - printf("*** threadcache=%u, mallocs=%u (%f), free=%u (%f), freeInCache=%u\n", (unsigned int) tc->threadid, tc->mallocs, - (float) tc->successes/tc->mallocs, tc->frees, (float) tc->successes/tc->frees, (unsigned int) tc->freeInCache); - } -#endif -#ifdef FULLSANITYCHECKS - tcfullsanitycheck(tc); -#endif - return ret; -} -static NOINLINE void ReleaseFreeInCache(nedpool *p, threadcache *tc, int mymspace) THROWSPEC -{ - unsigned int age=THREADCACHEMAXFREESPACE/8192; - /*ACQUIRE_LOCK(&p->m[mymspace]->mutex);*/ - while(age && tc->freeInCache>=THREADCACHEMAXFREESPACE) - { - RemoveCacheEntries(p, tc, age); - /*printf("*** Removing cache entries older than %u (%u)\n", age, (unsigned int) tc->freeInCache);*/ - age>>=1; - } - /*RELEASE_LOCK(&p->m[mymspace]->mutex);*/ -} -static void threadcache_free(nedpool *p, threadcache *tc, int mymspace, void *mem, size_t size) THROWSPEC -{ - unsigned int bestsize; - unsigned int idx=size2binidx(size); - threadcacheblk **binsptr, *tck=(threadcacheblk *) mem; - assert(size>=sizeof(threadcacheblk) && size<=THREADCACHEMAX+CHUNK_OVERHEAD); -#ifdef DEBUG - { /* Make sure this is a valid memory block */ - mchunkptr p = mem2chunk(mem); - mstate fm = get_mstate_for(p); - if (!ok_magic(fm)) { - USAGE_ERROR_ACTION(fm, p); - return; - } - } -#endif -#ifdef FULLSANITYCHECKS - tcfullsanitycheck(tc); -#endif - /* Calculate best fit bin size */ - bestsize=1<<(idx+4); -#if 0 - /* Finer grained bin fit */ - idx<<=1; - if(size>bestsize) - { - unsigned int biggerbestsize=bestsize+bestsize<<1; - if(size>=biggerbestsize) - { - idx++; - bestsize=biggerbestsize; - } - } -#endif - if(bestsize!=size) /* dlmalloc can round up, so we round down to preserve indexing */ - size=bestsize; - binsptr=&tc->bins[idx*2]; - assert(idx<=THREADCACHEMAXBINS); - if(tck==*binsptr) - { - fprintf(stderr, "Attempt to free already freed memory block %p - aborting!\n", (void *)tck); - abort(); - } -#ifdef FULLSANITYCHECKS - tck->magic=*(unsigned int *) "NEDN"; -#endif - tck->lastUsed=++tc->frees; - tck->size=(unsigned int) size; - tck->next=*binsptr; - tck->prev=0; - if(tck->next) - tck->next->prev=tck; - else - binsptr[1]=tck; - assert(!*binsptr || (*binsptr)->size==tck->size); - *binsptr=tck; - assert(tck==tc->bins[idx*2]); - assert(tc->bins[idx*2+1]==tck || binsptr[0]->next->prev==tck); - /*printf("free: %p, %p, %p, %lu\n", p, tc, mem, (long) size);*/ - tc->freeInCache+=size; -#ifdef FULLSANITYCHECKS - tcfullsanitycheck(tc); -#endif -#if 1 - if(tc->freeInCache>=THREADCACHEMAXFREESPACE) - ReleaseFreeInCache(p, tc, mymspace); -#endif -} - - - - -static NOINLINE int InitPool(nedpool *p, size_t capacity, int threads) THROWSPEC -{ /* threads is -1 for system pool */ - ensure_initialization(); - ACQUIRE_MALLOC_GLOBAL_LOCK(); - if(p->threads) goto done; - if(INITIAL_LOCK(&p->mutex)) goto err; - if(TLSALLOC(&p->mycache)) goto err; - if(!(p->m[0]=(mstate) create_mspace(capacity, 1))) goto err; - p->m[0]->extp=p; - p->threads=(threads<1 || threads>MAXTHREADSINPOOL) ? MAXTHREADSINPOOL : threads; -done: - RELEASE_MALLOC_GLOBAL_LOCK(); - return 1; -err: - if(threads<0) - abort(); /* If you can't allocate for system pool, we're screwed */ - DestroyCaches(p); - if(p->m[0]) - { - destroy_mspace(p->m[0]); - p->m[0]=0; - } - if(p->mycache) - { - if(TLSFREE(p->mycache)) abort(); - p->mycache=0; - } - RELEASE_MALLOC_GLOBAL_LOCK(); - return 0; -} -static NOINLINE mstate FindMSpace(nedpool *p, threadcache *tc, int *lastUsed, size_t size) THROWSPEC -{ /* Gets called when thread's last used mspace is in use. The strategy - is to run through the list of all available mspaces looking for an - unlocked one and if we fail, we create a new one so long as we don't - exceed p->threads */ - int n, end; - for(n=end=*lastUsed+1; p->m[n]; end=++n) - { - if(TRY_LOCK(&p->m[n]->mutex)) goto found; - } - for(n=0; n<*lastUsed && p->m[n]; n++) - { - if(TRY_LOCK(&p->m[n]->mutex)) goto found; - } - if(end<p->threads) - { - mstate temp; - if(!(temp=(mstate) create_mspace(size, 1))) - goto badexit; - /* Now we're ready to modify the lists, we lock */ - ACQUIRE_LOCK(&p->mutex); - while(p->m[end] && end<p->threads) - end++; - if(end>=p->threads) - { /* Drat, must destroy it now */ - RELEASE_LOCK(&p->mutex); - destroy_mspace((mspace) temp); - goto badexit; - } - /* We really want to make sure this goes into memory now but we - have to be careful of breaking aliasing rules, so write it twice */ - { - volatile struct malloc_state **_m=(volatile struct malloc_state **) &p->m[end]; - *_m=(p->m[end]=temp); - } - ACQUIRE_LOCK(&p->m[end]->mutex); - /*printf("Created mspace idx %d\n", end);*/ - RELEASE_LOCK(&p->mutex); - n=end; - goto found; - } - /* Let it lock on the last one it used */ -badexit: - ACQUIRE_LOCK(&p->m[*lastUsed]->mutex); - return p->m[*lastUsed]; -found: - *lastUsed=n; - if(tc) - tc->mymspace=n; - else - { - if(TLSSET(p->mycache, (void *)(size_t)(-(n+1)))) abort(); - } - return p->m[n]; -} - -nedpool *nedcreatepool(size_t capacity, int threads) THROWSPEC -{ - nedpool *ret; - if(!(ret=(nedpool *) nedpcalloc(0, 1, sizeof(nedpool)))) return 0; - if(!InitPool(ret, capacity, threads)) - { - nedpfree(0, ret); - return 0; - } - return ret; -} -void neddestroypool(nedpool *p) THROWSPEC -{ - int n; - ACQUIRE_LOCK(&p->mutex); - DestroyCaches(p); - for(n=0; p->m[n]; n++) - { - destroy_mspace(p->m[n]); - p->m[n]=0; - } - RELEASE_LOCK(&p->mutex); - if(TLSFREE(p->mycache)) abort(); - nedpfree(0, p); -} - -void nedpsetvalue(nedpool *p, void *v) THROWSPEC -{ - if(!p) { p=&syspool; if(!syspool.threads) InitPool(&syspool, 0, -1); } - p->uservalue=v; -} -void *nedgetvalue(nedpool **p, void *mem) THROWSPEC -{ - nedpool *np=0; - mchunkptr mcp=mem2chunk(mem); - mstate fm; - if(!(is_aligned(chunk2mem(mcp))) && mcp->head != FENCEPOST_HEAD) return 0; - if(!cinuse(mcp)) return 0; - if(!next_pinuse(mcp)) return 0; - if(!is_mmapped(mcp) && !pinuse(mcp)) - { - if(next_chunk(prev_chunk(mcp))!=mcp) return 0; - } - fm=get_mstate_for(mcp); - if(!ok_magic(fm)) return 0; - if(!ok_address(fm, mcp)) return 0; - if(!fm->extp) return 0; - np=(nedpool *) fm->extp; - if(p) *p=np; - return np->uservalue; -} - -void neddisablethreadcache(nedpool *p) THROWSPEC -{ - int mycache; - if(!p) - { - p=&syspool; - if(!syspool.threads) InitPool(&syspool, 0, -1); - } - mycache=(int)(size_t) TLSGET(p->mycache); - if(!mycache) - { /* Set to mspace 0 */ - if(TLSSET(p->mycache, (void *)-1)) abort(); - } - else if(mycache>0) - { /* Set to last used mspace */ - threadcache *tc=p->caches[mycache-1]; -#if defined(DEBUG) - printf("Threadcache utilisation: %lf%% in cache with %lf%% lost to other threads\n", - 100.0*tc->successes/tc->mallocs, 100.0*((double) tc->mallocs-tc->frees)/tc->mallocs); -#endif - if(TLSSET(p->mycache, (void *)(size_t)(-tc->mymspace))) abort(); - tc->frees++; - RemoveCacheEntries(p, tc, 0); - assert(!tc->freeInCache); - tc->mymspace=-1; - tc->threadid=0; - mspace_free(0, p->caches[mycache-1]); - p->caches[mycache-1]=0; - } -} - -#define GETMSPACE(m,p,tc,ms,s,action) \ - do \ - { \ - mstate m = GetMSpace((p),(tc),(ms),(s)); \ - action; \ - RELEASE_LOCK(&m->mutex); \ - } while (0) - -static FORCEINLINE mstate GetMSpace(nedpool *p, threadcache *tc, int mymspace, size_t size) THROWSPEC -{ /* Returns a locked and ready for use mspace */ - mstate m=p->m[mymspace]; - assert(m); - if(!TRY_LOCK(&p->m[mymspace]->mutex)) m=FindMSpace(p, tc, &mymspace, size);\ - /*assert(IS_LOCKED(&p->m[mymspace]->mutex));*/ - return m; -} -static FORCEINLINE void GetThreadCache(nedpool **p, threadcache **tc, int *mymspace, size_t *size) THROWSPEC -{ - int mycache; - if(size && *size<sizeof(threadcacheblk)) *size=sizeof(threadcacheblk); - if(!*p) - { - *p=&syspool; - if(!syspool.threads) InitPool(&syspool, 0, -1); - } - mycache=(int)(size_t) TLSGET((*p)->mycache); - if(mycache>0) - { - *tc=(*p)->caches[mycache-1]; - *mymspace=(*tc)->mymspace; - } - else if(!mycache) - { - *tc=AllocCache(*p); - if(!*tc) - { /* Disable */ - if(TLSSET((*p)->mycache, (void *)-1)) abort(); - *mymspace=0; - } - else - *mymspace=(*tc)->mymspace; - } - else - { - *tc=0; - *mymspace=-mycache-1; - } - assert(*mymspace>=0); - assert((long)(size_t)CURRENT_THREAD==(*tc)->threadid); -#ifdef FULLSANITYCHECKS - if(*tc) - { - if(*(unsigned int *)"NEDMALC1"!=(*tc)->magic1 || *(unsigned int *)"NEDMALC2"!=(*tc)->magic2) - { - abort(); - } - } -#endif -} - -void * nedpmalloc(nedpool *p, size_t size) THROWSPEC -{ - void *ret=0; - threadcache *tc; - int mymspace; - GetThreadCache(&p, &tc, &mymspace, &size); -#if THREADCACHEMAX - if(tc && size<=THREADCACHEMAX) - { /* Use the thread cache */ - ret=threadcache_malloc(p, tc, &size); - } -#endif - if(!ret) - { /* Use this thread's mspace */ - GETMSPACE(m, p, tc, mymspace, size, - ret=mspace_malloc(m, size)); - } - return ret; -} -void * nedpcalloc(nedpool *p, size_t no, size_t size) THROWSPEC -{ - size_t rsize=size*no; - void *ret=0; - threadcache *tc; - int mymspace; - GetThreadCache(&p, &tc, &mymspace, &rsize); -#if THREADCACHEMAX - if(tc && rsize<=THREADCACHEMAX) - { /* Use the thread cache */ - if((ret=threadcache_malloc(p, tc, &rsize))) - memset(ret, 0, rsize); - } -#endif - if(!ret) - { /* Use this thread's mspace */ - GETMSPACE(m, p, tc, mymspace, rsize, - ret=mspace_calloc(m, 1, rsize)); - } - return ret; -} -void * nedprealloc(nedpool *p, void *mem, size_t size) THROWSPEC -{ - void *ret=0; - threadcache *tc; - int mymspace; - if(!mem) return nedpmalloc(p, size); - GetThreadCache(&p, &tc, &mymspace, &size); -#if THREADCACHEMAX - if(tc && size && size<=THREADCACHEMAX) - { /* Use the thread cache */ - size_t memsize=nedblksize(mem); - assert(memsize); - if((ret=threadcache_malloc(p, tc, &size))) - { - memcpy(ret, mem, memsize<size ? memsize : size); - if(memsize<=THREADCACHEMAX) - threadcache_free(p, tc, mymspace, mem, memsize); - else - mspace_free(0, mem); - } - } -#endif - if(!ret) - { /* Reallocs always happen in the mspace they happened in, so skip - locking the preferred mspace for this thread */ - ret=mspace_realloc(0, mem, size); - } - return ret; -} -void nedpfree(nedpool *p, void *mem) THROWSPEC -{ /* Frees always happen in the mspace they happened in, so skip - locking the preferred mspace for this thread */ - threadcache *tc; - int mymspace; - size_t memsize; - assert(mem); - GetThreadCache(&p, &tc, &mymspace, 0); -#if THREADCACHEMAX - memsize=nedblksize(mem); - assert(memsize); - if(mem && tc && memsize<=(THREADCACHEMAX+CHUNK_OVERHEAD)) - threadcache_free(p, tc, mymspace, mem, memsize); - else -#endif - mspace_free(0, mem); -} -void * nedpmemalign(nedpool *p, size_t alignment, size_t bytes) THROWSPEC -{ - void *ret; - threadcache *tc; - int mymspace; - GetThreadCache(&p, &tc, &mymspace, &bytes); - { /* Use this thread's mspace */ - GETMSPACE(m, p, tc, mymspace, bytes, - ret=mspace_memalign(m, alignment, bytes)); - } - return ret; -} -#if !NO_MALLINFO -struct mallinfo nedpmallinfo(nedpool *p) THROWSPEC -{ - int n; - struct mallinfo ret={0}; - if(!p) { p=&syspool; if(!syspool.threads) InitPool(&syspool, 0, -1); } - for(n=0; p->m[n]; n++) - { - struct mallinfo t=mspace_mallinfo(p->m[n]); - ret.arena+=t.arena; - ret.ordblks+=t.ordblks; - ret.hblkhd+=t.hblkhd; - ret.usmblks+=t.usmblks; - ret.uordblks+=t.uordblks; - ret.fordblks+=t.fordblks; - ret.keepcost+=t.keepcost; - } - return ret; -} -#endif -int nedpmallopt(nedpool *p, int parno, int value) THROWSPEC -{ - return mspace_mallopt(parno, value); -} -int nedpmalloc_trim(nedpool *p, size_t pad) THROWSPEC -{ - int n, ret=0; - if(!p) { p=&syspool; if(!syspool.threads) InitPool(&syspool, 0, -1); } - for(n=0; p->m[n]; n++) - { - ret+=mspace_trim(p->m[n], pad); - } - return ret; -} -void nedpmalloc_stats(nedpool *p) THROWSPEC -{ - int n; - if(!p) { p=&syspool; if(!syspool.threads) InitPool(&syspool, 0, -1); } - for(n=0; p->m[n]; n++) - { - mspace_malloc_stats(p->m[n]); - } -} -size_t nedpmalloc_footprint(nedpool *p) THROWSPEC -{ - size_t ret=0; - int n; - if(!p) { p=&syspool; if(!syspool.threads) InitPool(&syspool, 0, -1); } - for(n=0; p->m[n]; n++) - { - ret+=mspace_footprint(p->m[n]); - } - return ret; -} -void **nedpindependent_calloc(nedpool *p, size_t elemsno, size_t elemsize, void **chunks) THROWSPEC -{ - void **ret; - threadcache *tc; - int mymspace; - GetThreadCache(&p, &tc, &mymspace, &elemsize); - GETMSPACE(m, p, tc, mymspace, elemsno*elemsize, - ret=mspace_independent_calloc(m, elemsno, elemsize, chunks)); - return ret; -} -void **nedpindependent_comalloc(nedpool *p, size_t elems, size_t *sizes, void **chunks) THROWSPEC -{ - void **ret; - threadcache *tc; - int mymspace; - size_t i, *adjustedsizes=(size_t *) alloca(elems*sizeof(size_t)); - if(!adjustedsizes) return 0; - for(i=0; i<elems; i++) - adjustedsizes[i]=sizes[i]<sizeof(threadcacheblk) ? sizeof(threadcacheblk) : sizes[i]; - GetThreadCache(&p, &tc, &mymspace, 0); - GETMSPACE(m, p, tc, mymspace, 0, - ret=mspace_independent_comalloc(m, elems, adjustedsizes, chunks)); - return ret; -} - -#if defined(__cplusplus) -} -#endif
diff --git a/compat/nedmalloc/nedmalloc.h b/compat/nedmalloc/nedmalloc.h deleted file mode 100644 index f960e66..0000000 --- a/compat/nedmalloc/nedmalloc.h +++ /dev/null
@@ -1,180 +0,0 @@ -/* nedalloc, an alternative malloc implementation for multiple threads without -lock contention based on dlmalloc v2.8.3. (C) 2005 Niall Douglas - -Boost Software License - Version 1.0 - August 17th, 2003 - -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: - -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -*/ - -#ifndef NEDMALLOC_H -#define NEDMALLOC_H - - -/* See malloc.c.h for what each function does. - -REPLACE_SYSTEM_ALLOCATOR causes nedalloc's functions to be called malloc, -free etc. instead of nedmalloc, nedfree etc. You may or may not want this. - -NO_NED_NAMESPACE prevents the functions from being defined in the nedalloc -namespace when in C++ (uses the global namespace instead). - -EXTSPEC can be defined to be __declspec(dllexport) or -__attribute__ ((visibility("default"))) or whatever you like. It defaults -to extern. - -USE_LOCKS can be 2 if you want to define your own MLOCK_T, INITIAL_LOCK, -ACQUIRE_LOCK, RELEASE_LOCK, TRY_LOCK, IS_LOCKED and NULL_LOCK_INITIALIZER. - -*/ - -#include <stddef.h> /* for size_t */ - -#ifndef EXTSPEC - #define EXTSPEC extern -#endif - -#if defined(_MSC_VER) && _MSC_VER>=1400 - #define MALLOCATTR __declspec(restrict) -#endif -#ifdef __GNUC__ - #define MALLOCATTR __attribute__ ((malloc)) -#endif -#ifndef MALLOCATTR - #define MALLOCATTR -#endif - -#ifdef REPLACE_SYSTEM_ALLOCATOR - #define nedmalloc malloc - #define nedcalloc calloc - #define nedrealloc realloc - #define nedfree free - #define nedmemalign memalign - #define nedmallinfo mallinfo - #define nedmallopt mallopt - #define nedmalloc_trim malloc_trim - #define nedmalloc_stats malloc_stats - #define nedmalloc_footprint malloc_footprint - #define nedindependent_calloc independent_calloc - #define nedindependent_comalloc independent_comalloc - #ifdef _MSC_VER - #define nedblksize _msize - #endif -#endif - -#ifndef NO_MALLINFO -#define NO_MALLINFO 0 -#endif - -#if !NO_MALLINFO -struct mallinfo; -#endif - -#if defined(__cplusplus) - #if !defined(NO_NED_NAMESPACE) -namespace nedalloc { - #else -extern "C" { - #endif - #define THROWSPEC throw() -#else - #define THROWSPEC -#endif - -/* These are the global functions */ - -/* Gets the usable size of an allocated block. Note this will always be bigger than what was -asked for due to rounding etc. -*/ -EXTSPEC size_t nedblksize(void *mem) THROWSPEC; - -EXTSPEC void nedsetvalue(void *v) THROWSPEC; - -EXTSPEC MALLOCATTR void * nedmalloc(size_t size) THROWSPEC; -EXTSPEC MALLOCATTR void * nedcalloc(size_t no, size_t size) THROWSPEC; -EXTSPEC MALLOCATTR void * nedrealloc(void *mem, size_t size) THROWSPEC; -EXTSPEC void nedfree(void *mem) THROWSPEC; -EXTSPEC MALLOCATTR void * nedmemalign(size_t alignment, size_t bytes) THROWSPEC; -#if !NO_MALLINFO -EXTSPEC struct mallinfo nedmallinfo(void) THROWSPEC; -#endif -EXTSPEC int nedmallopt(int parno, int value) THROWSPEC; -EXTSPEC int nedmalloc_trim(size_t pad) THROWSPEC; -EXTSPEC void nedmalloc_stats(void) THROWSPEC; -EXTSPEC size_t nedmalloc_footprint(void) THROWSPEC; -EXTSPEC MALLOCATTR void **nedindependent_calloc(size_t elemsno, size_t elemsize, void **chunks) THROWSPEC; -EXTSPEC MALLOCATTR void **nedindependent_comalloc(size_t elems, size_t *sizes, void **chunks) THROWSPEC; - -/* These are the pool functions */ -struct nedpool_t; -typedef struct nedpool_t nedpool; - -/* Creates a memory pool for use with the nedp* functions below. -Capacity is how much to allocate immediately (if you know you'll be allocating a lot -of memory very soon) which you can leave at zero. Threads specifies how many threads -will *normally* be accessing the pool concurrently. Setting this to zero means it -extends on demand, but be careful of this as it can rapidly consume system resources -where bursts of concurrent threads use a pool at once. -*/ -EXTSPEC MALLOCATTR nedpool *nedcreatepool(size_t capacity, int threads) THROWSPEC; - -/* Destroys a memory pool previously created by nedcreatepool(). -*/ -EXTSPEC void neddestroypool(nedpool *p) THROWSPEC; - -/* Sets a value to be associated with a pool. You can retrieve this value by passing -any memory block allocated from that pool. -*/ -EXTSPEC void nedpsetvalue(nedpool *p, void *v) THROWSPEC; -/* Gets a previously set value using nedpsetvalue() or zero if memory is unknown. -Optionally can also retrieve pool. -*/ -EXTSPEC void *nedgetvalue(nedpool **p, void *mem) THROWSPEC; - -/* Disables the thread cache for the calling thread, returning any existing cache -data to the central pool. -*/ -EXTSPEC void neddisablethreadcache(nedpool *p) THROWSPEC; - -EXTSPEC MALLOCATTR void * nedpmalloc(nedpool *p, size_t size) THROWSPEC; -EXTSPEC MALLOCATTR void * nedpcalloc(nedpool *p, size_t no, size_t size) THROWSPEC; -EXTSPEC MALLOCATTR void * nedprealloc(nedpool *p, void *mem, size_t size) THROWSPEC; -EXTSPEC void nedpfree(nedpool *p, void *mem) THROWSPEC; -EXTSPEC MALLOCATTR void * nedpmemalign(nedpool *p, size_t alignment, size_t bytes) THROWSPEC; -#if !NO_MALLINFO -EXTSPEC struct mallinfo nedpmallinfo(nedpool *p) THROWSPEC; -#endif -EXTSPEC int nedpmallopt(nedpool *p, int parno, int value) THROWSPEC; -EXTSPEC int nedpmalloc_trim(nedpool *p, size_t pad) THROWSPEC; -EXTSPEC void nedpmalloc_stats(nedpool *p) THROWSPEC; -EXTSPEC size_t nedpmalloc_footprint(nedpool *p) THROWSPEC; -EXTSPEC MALLOCATTR void **nedpindependent_calloc(nedpool *p, size_t elemsno, size_t elemsize, void **chunks) THROWSPEC; -EXTSPEC MALLOCATTR void **nedpindependent_comalloc(nedpool *p, size_t elems, size_t *sizes, void **chunks) THROWSPEC; - -#if defined(__cplusplus) -} -#endif - -#undef MALLOCATTR -#undef EXTSPEC - -#endif
diff --git a/compat/posix.h b/compat/posix.h index faaae1b..2f01564 100644 --- a/compat/posix.h +++ b/compat/posix.h
@@ -4,22 +4,33 @@ #define _FILE_OFFSET_BITS 64 /* - * Derived from Linux "Features Test Macro" header - * Convenience macros to test the versions of gcc (or - * a compatible compiler). + * Convenience macros to test the versions of GCC (or a compatible compiler). * Use them like this: * #if GIT_GNUC_PREREQ (2,8) - * ... code requiring gcc 2.8 or later ... + * ... code requiring GCC 2.8 or later ... * #endif * + * Note that Clang and other compilers define __GNUC__ for compatibility; use + * GIT_CLANG_PREREQ() to check for specific Clang versions. + * * This macro of course is not part of POSIX, but we need it for the UNUSED * macro which is used by some of our POSIX compatibility wrappers. -*/ + */ #if defined(__GNUC__) && defined(__GNUC_MINOR__) # define GIT_GNUC_PREREQ(maj, min) \ - ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min)) + ((__GNUC__ > (maj)) || \ + (__GNUC__ == (maj) && __GNUC_MINOR__ >= (min))) #else - #define GIT_GNUC_PREREQ(maj, min) 0 +# define GIT_GNUC_PREREQ(maj, min) 0 +#endif + +/* Similar for Clang. */ +#if defined(__clang__) && defined(__clang_minor__) && defined(__clang_major__) +# define GIT_CLANG_PREREQ(maj, min) \ + ((__clang_major__ > (maj)) || \ + (__clang_major__ == (maj) && __clang_minor__ >= (min))) +#else +# define GIT_CLANG_PREREQ(maj, min) 0 #endif /* @@ -35,14 +46,14 @@ * When a parameter may be used or unused, depending on conditional * compilation, consider using MAYBE_UNUSED instead. */ -#if GIT_GNUC_PREREQ(4, 5) -#define UNUSED __attribute__((unused)) \ - __attribute__((deprecated ("parameter declared as UNUSED"))) +#if GIT_GNUC_PREREQ(4, 5) || GIT_CLANG_PREREQ(2, 9) +# define UNUSED __attribute__((unused)) \ + __attribute__((deprecated("parameter declared as UNUSED"))) #elif defined(__GNUC__) -#define UNUSED __attribute__((unused)) \ +# define UNUSED __attribute__((unused)) \ __attribute__((deprecated)) #else -#define UNUSED +# define UNUSED #endif #ifdef __MINGW64__
diff --git a/compat/precompose_utf8.c b/compat/precompose_utf8.c index 43b3be0..1711794 100644 --- a/compat/precompose_utf8.c +++ b/compat/precompose_utf8.c
@@ -48,16 +48,18 @@ void probe_utf8_pathname_composition(void) static const char *auml_nfc = "\xc3\xa4"; static const char *auml_nfd = "\x61\xcc\x88"; int output_fd; - if (precomposed_unicode != -1) + struct repo_config_values *cfg = repo_config_values(the_repository); + + if (cfg->precomposed_unicode != -1) return; /* We found it defined in the global config, respect it */ repo_git_path_replace(the_repository, &path, "%s", auml_nfc); output_fd = open(path.buf, O_CREAT|O_EXCL|O_RDWR, 0600); if (output_fd >= 0) { close(output_fd); repo_git_path_replace(the_repository, &path, "%s", auml_nfd); - precomposed_unicode = access(path.buf, R_OK) ? 0 : 1; + cfg->precomposed_unicode = access(path.buf, R_OK) ? 0 : 1; repo_config_set(the_repository, "core.precomposeunicode", - precomposed_unicode ? "true" : "false"); + cfg->precomposed_unicode ? "true" : "false"); repo_git_path_replace(the_repository, &path, "%s", auml_nfc); if (unlink(path.buf)) die_errno(_("failed to unlink '%s'"), path.buf); @@ -69,14 +71,16 @@ const char *precompose_string_if_needed(const char *in) { size_t inlen; size_t outlen; + struct repo_config_values *cfg = repo_config_values(the_repository); + if (!in) return NULL; if (has_non_ascii(in, (size_t)-1, &inlen)) { iconv_t ic_prec; char *out; - if (precomposed_unicode < 0) - repo_config_get_bool(the_repository, "core.precomposeunicode", &precomposed_unicode); - if (precomposed_unicode != 1) + if (cfg->precomposed_unicode < 0) + repo_config_get_bool(the_repository, "core.precomposeunicode", &cfg->precomposed_unicode); + if (cfg->precomposed_unicode != 1) return in; ic_prec = iconv_open(repo_encoding, path_encoding); if (ic_prec == (iconv_t) -1) @@ -85,7 +89,7 @@ const char *precompose_string_if_needed(const char *in) out = reencode_string_iconv(in, inlen, ic_prec, 0, &outlen); if (out) { if (outlen == inlen && !memcmp(in, out, outlen)) - free(out); /* no need to return indentical */ + free(out); /* no need to return identical */ else in = out; } @@ -130,7 +134,9 @@ PREC_DIR *precompose_utf8_opendir(const char *dirname) struct dirent_prec_psx *precompose_utf8_readdir(PREC_DIR *prec_dir) { + struct repo_config_values *cfg = repo_config_values(the_repository); struct dirent *res; + res = readdir(prec_dir->dirp); if (res) { size_t namelenz = strlen(res->d_name) + 1; /* \0 */ @@ -149,7 +155,7 @@ struct dirent_prec_psx *precompose_utf8_readdir(PREC_DIR *prec_dir) prec_dir->dirent_nfc->d_ino = res->d_ino; prec_dir->dirent_nfc->d_type = res->d_type; - if ((precomposed_unicode == 1) && has_non_ascii(res->d_name, (size_t)-1, NULL)) { + if ((cfg->precomposed_unicode == 1) && has_non_ascii(res->d_name, (size_t)-1, NULL)) { if (prec_dir->ic_precompose == (iconv_t)-1) { die("iconv_open(%s,%s) failed, but needed:\n" " precomposed unicode is not supported.\n"
diff --git a/compat/win32/exit-process.h b/compat/win32/exit-process.h new file mode 100644 index 0000000..d539898 --- /dev/null +++ b/compat/win32/exit-process.h
@@ -0,0 +1,165 @@ +#ifndef EXIT_PROCESS_H +#define EXIT_PROCESS_H + +/* + * This file contains functions to terminate a Win32 process, as gently as + * possible. + * + * At first, we will attempt to inject a thread that calls ExitProcess(). If + * that fails, we will fall back to terminating the entire process tree. + * + * For simplicity, these functions are marked as file-local. + */ + +#include <tlhelp32.h> + +/* + * Terminates the process corresponding to the process ID and all of its + * directly and indirectly spawned subprocesses. + * + * This way of terminating the processes is not gentle: the processes get + * no chance of cleaning up after themselves (closing file handles, removing + * .lock files, terminating spawned processes (if any), etc). + */ +static int terminate_process_tree(HANDLE main_process, int exit_status) +{ + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + PROCESSENTRY32 entry; + DWORD pids[16384]; + int max_len = sizeof(pids) / sizeof(*pids), i, len, ret = 0; + pid_t pid = GetProcessId(main_process); + + pids[0] = (DWORD)pid; + len = 1; + + /* + * Even if Process32First()/Process32Next() seem to traverse the + * processes in topological order (i.e. parent processes before + * child processes), there is nothing in the Win32 API documentation + * suggesting that this is guaranteed. + * + * Therefore, run through them at least twice and stop when no more + * process IDs were added to the list. + */ + for (;;) { + int orig_len = len; + + memset(&entry, 0, sizeof(entry)); + entry.dwSize = sizeof(entry); + + if (!Process32First(snapshot, &entry)) + break; + + do { + for (i = len - 1; i >= 0; i--) { + if (pids[i] == entry.th32ProcessID) + break; + if (pids[i] == entry.th32ParentProcessID) + pids[len++] = entry.th32ProcessID; + } + } while (len < max_len && Process32Next(snapshot, &entry)); + + if (orig_len == len || len >= max_len) + break; + } + + for (i = len - 1; i > 0; i--) { + HANDLE process = OpenProcess(PROCESS_TERMINATE, FALSE, pids[i]); + + if (process) { + if (!TerminateProcess(process, exit_status)) + ret = -1; + CloseHandle(process); + } + } + if (!TerminateProcess(main_process, exit_status)) + ret = -1; + CloseHandle(main_process); + + return ret; +} + +/** + * Determine whether a process runs in the same architecture as the current + * one. That test is required before we assume that GetProcAddress() returns + * a valid address *for the target process*. + */ +static inline int process_architecture_matches_current(HANDLE process) +{ + static BOOL current_is_wow = -1; + BOOL is_wow; + + if (current_is_wow == -1 && + !IsWow64Process (GetCurrentProcess(), ¤t_is_wow)) + current_is_wow = -2; + if (current_is_wow == -2) + return 0; /* could not determine current process' WoW-ness */ + if (!IsWow64Process (process, &is_wow)) + return 0; /* cannot determine */ + return is_wow == current_is_wow; +} + +/** + * Inject a thread into the given process that runs ExitProcess(). + * + * Note: as kernel32.dll is loaded before any process, the other process and + * this process will have ExitProcess() at the same address. + * + * This function expects the process handle to have the access rights for + * CreateRemoteThread(): PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION, + * PROCESS_VM_OPERATION, PROCESS_VM_WRITE, and PROCESS_VM_READ. + * + * The idea comes from the Dr Dobb's article "A Safer Alternative to + * TerminateProcess()" by Andrew Tucker (July 1, 1999), + * http://www.drdobbs.com/a-safer-alternative-to-terminateprocess/184416547 + * + * If this method fails, we fall back to running terminate_process_tree(). + */ +static int exit_process(HANDLE process, int exit_code) +{ + DWORD code; + + if (GetExitCodeProcess(process, &code) && code == STILL_ACTIVE) { + static int initialized; + static LPTHREAD_START_ROUTINE exit_process_address; + PVOID arg = (PVOID)(intptr_t)exit_code; + DWORD thread_id; + HANDLE thread = NULL; + + if (!initialized) { + HINSTANCE kernel32 = GetModuleHandleA("kernel32"); + if (!kernel32) + die("BUG: cannot find kernel32"); + exit_process_address = + (LPTHREAD_START_ROUTINE)(void (*)(void)) + GetProcAddress(kernel32, "ExitProcess"); + initialized = 1; + } + if (!exit_process_address || + !process_architecture_matches_current(process)) + return terminate_process_tree(process, exit_code); + + thread = CreateRemoteThread(process, NULL, 0, + exit_process_address, + arg, 0, &thread_id); + if (thread) { + CloseHandle(thread); + /* + * If the process survives for 10 seconds (a completely + * arbitrary value picked from thin air), fall back to + * killing the process tree via TerminateProcess(). + */ + if (WaitForSingleObject(process, 10000) == + WAIT_OBJECT_0) { + CloseHandle(process); + return 0; + } + } + + return terminate_process_tree(process, exit_code); + } + + return 0; +} + +#endif
diff --git a/compat/win32/pthread.c b/compat/win32/pthread.c index 7e93146..398caa9 100644 --- a/compat/win32/pthread.c +++ b/compat/win32/pthread.c
@@ -66,3 +66,29 @@ int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) return err_win_to_posix(GetLastError()); return 0; } + +int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, + const struct timespec *abstime) +{ + struct timeval now; + long long now_ms, deadline_ms; + DWORD timeout_ms; + + gettimeofday(&now, NULL); + now_ms = (long long)now.tv_sec * 1000 + now.tv_usec / 1000; + deadline_ms = (long long)abstime->tv_sec * 1000 + + abstime->tv_nsec / 1000000; + + if (deadline_ms <= now_ms) + return ETIMEDOUT; + else + timeout_ms = (DWORD)(deadline_ms - now_ms); + + if (SleepConditionVariableCS(cond, mutex, timeout_ms) == 0) { + DWORD err = GetLastError(); + if (err == ERROR_TIMEOUT) + return ETIMEDOUT; + return err_win_to_posix(err); + } + return 0; +}
diff --git a/compat/win32/pthread.h b/compat/win32/pthread.h index ccacc5a..d80df8d 100644 --- a/compat/win32/pthread.h +++ b/compat/win32/pthread.h
@@ -64,6 +64,8 @@ int win32_pthread_join(pthread_t *thread, void **value_ptr); pthread_t pthread_self(void); int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); +int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, + const struct timespec *abstime); static inline void NORETURN pthread_exit(void *ret) {
diff --git a/config.c b/config.c index 156f2a2..45144f7 100644 --- a/config.c +++ b/config.c
@@ -536,11 +536,14 @@ static inline int iskeychar(int c) * -2 if there is no section name in the key. * * store_key - pointer to char* which will hold a copy of the key with - * lowercase section and variable name + * lowercase section and variable name, can be NULL to skip + * allocation when only validation is needed * baselen - pointer to size_t which will hold the length of the * section + subsection part, can be NULL + * quiet - when non-zero, suppress error() reports on rejection */ -int git_config_parse_key(const char *key, char **store_key, size_t *baselen_) +static int do_parse_config_key(const char *key, char **store_key, + size_t *baselen_, int quiet) { size_t i, baselen; int dot; @@ -552,12 +555,14 @@ int git_config_parse_key(const char *key, char **store_key, size_t *baselen_) */ if (last_dot == NULL || last_dot == key) { - error(_("key does not contain a section: %s"), key); + if (!quiet) + error(_("key does not contain a section: %s"), key); return -CONFIG_NO_SECTION_OR_NAME; } if (!last_dot[1]) { - error(_("key does not contain variable name: %s"), key); + if (!quiet) + error(_("key does not contain variable name: %s"), key); return -CONFIG_NO_SECTION_OR_NAME; } @@ -568,7 +573,8 @@ int git_config_parse_key(const char *key, char **store_key, size_t *baselen_) /* * Validate the key and while at it, lower case it for matching. */ - *store_key = xmallocz(strlen(key)); + if (store_key) + *store_key = xmallocz(strlen(key)); dot = 0; for (i = 0; key[i]; i++) { @@ -579,24 +585,38 @@ int git_config_parse_key(const char *key, char **store_key, size_t *baselen_) if (!dot || i > baselen) { if (!iskeychar(c) || (i == baselen + 1 && !isalpha(c))) { - error(_("invalid key: %s"), key); + if (!quiet) + error(_("invalid key: %s"), key); goto out_free_ret_1; } c = tolower(c); } else if (c == '\n') { - error(_("invalid key (newline): %s"), key); + if (!quiet) + error(_("invalid key (newline): %s"), key); goto out_free_ret_1; } - (*store_key)[i] = c; + if (store_key) + (*store_key)[i] = c; } return 0; out_free_ret_1: - FREE_AND_NULL(*store_key); + if (store_key) + FREE_AND_NULL(*store_key); return -CONFIG_INVALID_KEY; } +int git_config_parse_key(const char *key, char **store_key, size_t *baselen_) +{ + return do_parse_config_key(key, store_key, baselen_, 0); +} + +int git_config_key_is_valid(const char *key) +{ + return !do_parse_config_key(key, NULL, NULL, 1); +} + static int config_parse_pair(const char *key, const char *value, struct key_value_info *kvi, config_fn_t fn, void *data) @@ -1212,6 +1232,15 @@ int git_config_int(const char *name, const char *value, return ret; } +unsigned int git_config_uint(const char *name, const char *value, + const struct key_value_info *kvi) +{ + unsigned int ret; + if (!git_parse_uint(value, &ret)) + die_bad_number(name, value, kvi); + return ret; +} + int64_t git_config_int64(const char *name, const char *value, const struct key_value_info *kvi) { @@ -1907,6 +1936,18 @@ int git_configset_get_int(struct config_set *set, const char *key, int *dest) return 1; } +int git_configset_get_uint(struct config_set *set, const char *key, unsigned int *dest) +{ + const char *value; + struct key_value_info kvi; + + if (!git_configset_get_value(set, key, &value, &kvi)) { + *dest = git_config_uint(key, value, &kvi); + return 0; + } else + return 1; +} + int git_configset_get_ulong(struct config_set *set, const char *key, unsigned long *dest) { const char *value; @@ -2356,6 +2397,13 @@ int repo_config_get_int(struct repository *repo, return git_configset_get_int(repo->config, key, dest); } +int repo_config_get_uint(struct repository *repo, + const char *key, unsigned int *dest) +{ + git_config_check_init(repo); + return git_configset_get_uint(repo->config, key, dest); +} + int repo_config_get_ulong(struct repository *repo, const char *key, unsigned long *dest) {
diff --git a/config.h b/config.h index ba426a9..31fe3e2 100644 --- a/config.h +++ b/config.h
@@ -268,6 +268,12 @@ int64_t git_config_int64(const char *, const char *, const struct key_value_info *); /** + * Identical to `git_config_int`, but for unsigned ints. + */ +unsigned int git_config_uint(const char *, const char *, + const struct key_value_info *); + +/** * Identical to `git_config_int`, but for unsigned longs. */ unsigned long git_config_ulong(const char *, const char *, @@ -337,6 +343,8 @@ void repo_config_set(struct repository *, const char *, const char *); int git_config_parse_key(const char *, char **, size_t *); +int git_config_key_is_valid(const char *); + /* * The following macros specify flag bits that alter the behavior * of the repo_config_set_multivar*() methods. @@ -560,6 +568,7 @@ int git_configset_get_value(struct config_set *cs, const char *key, int git_configset_get_string(struct config_set *cs, const char *key, char **dest); int git_configset_get_int(struct config_set *cs, const char *key, int *dest); +int git_configset_get_uint(struct config_set *cs, const char *key, unsigned int *dest); int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *dest); int git_configset_get_bool(struct config_set *cs, const char *key, int *dest); int git_configset_get_bool_or_int(struct config_set *cs, const char *key, int *is_bool, int *dest); @@ -651,6 +660,12 @@ int repo_config_get_string_tmp(struct repository *r, int repo_config_get_int(struct repository *r, const char *key, int *dest); /** + * Similar to `repo_config_get_int` but for unsigned ints. + */ +int repo_config_get_uint(struct repository *r, + const char *key, unsigned int *dest); + +/** * Similar to `repo_config_get_int` but for unsigned longs. */ int repo_config_get_ulong(struct repository *r,
diff --git a/config.mak.dev b/config.mak.dev index c8dcf78..eecb12c 100644 --- a/config.mak.dev +++ b/config.mak.dev
@@ -98,6 +98,13 @@ endif endif +# glibc 2.43 headers unconditionally use _Generic even when we ask the +# compiler to stick to -std=gnu99 and unlike GCC, clang lacks a +# workaround to squelch warnings from system headers. +ifneq ($(filter clang1,$(COMPILER_FEATURES)),) # if we are using clang +DEVELOPER_CFLAGS += -Wno-c11-extensions +endif + # https://bugzilla.redhat.com/show_bug.cgi?id=2075786 ifneq ($(filter gcc12,$(COMPILER_FEATURES)),) DEVELOPER_CFLAGS += -Wno-error=stringop-overread
diff --git a/config.mak.uname b/config.mak.uname index 5feb582..8719e09 100644 --- a/config.mak.uname +++ b/config.mak.uname
@@ -63,11 +63,22 @@ PROCFS_EXECUTABLE_PATH = /proc/self/exe HAVE_PLATFORM_PROCINFO = YesPlease COMPAT_OBJS += compat/linux/procinfo.o + EXTLIBS += -ldl # centos7/rhel7 provides gcc 4.8.5 and zlib 1.2.7. ifneq ($(findstring .el7.,$(uname_R)),) BASIC_CFLAGS += -std=c99 endif LINK_FUZZ_PROGRAMS = YesPlease + + # The builtin FSMonitor on Linux builds upon Simple-IPC. Both require + # Unix domain sockets and PThreads. + ifndef NO_PTHREADS + ifndef NO_UNIX_SOCKETS + FSMONITOR_DAEMON_BACKEND = linux + FSMONITOR_OS_SETTINGS = unix + BASIC_CFLAGS += -DHAVE_LINUX_MAGIC_H + endif + endif endif ifeq ($(uname_S),GNU/kFreeBSD) HAVE_ALLOCA_H = YesPlease @@ -162,12 +173,18 @@ NEEDS_GOOD_LIBICONV = UnfortunatelyYes endif + # Silence Xcode 16.3+ linker warning about __DATA,__common alignment. + LD_MAJOR_VERSION = $(shell ld -v 2>&1 | sed -n 's/.*PROJECT:ld-\([0-9]*\).*/\1/p') + ifeq ($(shell test -n "$(LD_MAJOR_VERSION)" && test "$(LD_MAJOR_VERSION)" -ge 1167 && echo 1),1) + BASIC_CFLAGS += -fno-common + endif + # The builtin FSMonitor on MacOS builds upon Simple-IPC. Both require # Unix domain sockets and PThreads. ifndef NO_PTHREADS ifndef NO_UNIX_SOCKETS FSMONITOR_DAEMON_BACKEND = darwin - FSMONITOR_OS_SETTINGS = darwin + FSMONITOR_OS_SETTINGS = unix endif endif @@ -491,7 +508,6 @@ USE_WIN32_IPC = YesPlease USE_WIN32_MMAP = YesPlease MMAP_PREVENTS_DELETE = UnfortunatelyYes - # USE_NED_ALLOCATOR = YesPlease UNRELIABLE_FSTAT = UnfortunatelyYes OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo NO_REGEX = YesPlease @@ -758,9 +774,6 @@ HAVE_LIBCHARSET_H = YesPlease USE_GETTEXT_SCHEME = fallthrough USE_LIBPCRE = YesPlease - ifneq (CLANGARM64,$(MSYSTEM)) - USE_NED_ALLOCATOR = YesPlease - endif ifeq (/mingw64,$(subst 32,64,$(subst clangarm,mingw,$(prefix)))) # Move system config into top-level /etc/ ETC_GITCONFIG = ../etc/gitconfig
diff --git a/connect.c b/connect.c index fcd35c5..47e39d2 100644 --- a/connect.c +++ b/connect.c
@@ -700,51 +700,21 @@ int server_supports(const char *feature) return !!server_feature_value(feature, NULL); } -enum protocol { - PROTO_LOCAL = 1, - PROTO_FILE, - PROTO_SSH, - PROTO_GIT -}; - -int url_is_local_not_ssh(const char *url) +static const char *url_scheme_name(enum url_scheme scheme) { - const char *colon = strchr(url, ':'); - const char *slash = strchr(url, '/'); - return !colon || (slash && slash < colon) || - (has_dos_drive_prefix(url) && is_valid_path(url)); -} - -static const char *prot_name(enum protocol protocol) -{ - switch (protocol) { - case PROTO_LOCAL: - case PROTO_FILE: + switch (scheme) { + case URL_SCHEME_LOCAL: + case URL_SCHEME_FILE: return "file"; - case PROTO_SSH: + case URL_SCHEME_SSH: return "ssh"; - case PROTO_GIT: + case URL_SCHEME_GIT: return "git"; default: return "unknown protocol"; } } -static enum protocol get_protocol(const char *name) -{ - if (!strcmp(name, "ssh")) - return PROTO_SSH; - if (!strcmp(name, "git")) - return PROTO_GIT; - if (!strcmp(name, "git+ssh")) /* deprecated - do not use */ - return PROTO_SSH; - if (!strcmp(name, "ssh+git")) /* deprecated - do not use */ - return PROTO_SSH; - if (!strcmp(name, "file")) - return PROTO_FILE; - die(_("protocol '%s' is not supported"), name); -} - static char *host_end(char **hoststart, int removebrackets) { char *host = *hoststart; @@ -1054,8 +1024,6 @@ static struct child_process *git_proxy_connect(int fd[2], char *host) strvec_push(&proxy->args, port); proxy->in = -1; proxy->out = -1; - proxy->clean_on_exit = 1; - proxy->wait_after_clean = 1; if (start_command(proxy)) die(_("cannot start proxy %s"), git_proxy_command); fd[0] = proxy->out; /* read from proxy stdout */ @@ -1083,14 +1051,14 @@ static char *get_port(char *host) * Extract protocol and relevant parts from the specified connection URL. * The caller must free() the returned strings. */ -static enum protocol parse_connect_url(const char *url_orig, char **ret_host, - char **ret_path) +static enum url_scheme parse_connect_url(const char *url_orig, char **ret_host, + char **ret_path) { char *url; char *host, *path; char *end; int separator = '/'; - enum protocol protocol = PROTO_LOCAL; + enum url_scheme scheme = URL_SCHEME_LOCAL; if (is_url(url_orig)) url = url_decode(url_orig); @@ -1100,12 +1068,14 @@ static enum protocol parse_connect_url(const char *url_orig, char **ret_host, host = strstr(url, "://"); if (host) { *host = '\0'; - protocol = get_protocol(url); + scheme = url_get_scheme(url); + if (scheme == URL_SCHEME_UNKNOWN) + die(_("protocol '%s' is not supported"), url); host += 3; } else { host = url; if (!url_is_local_not_ssh(url)) { - protocol = PROTO_SSH; + scheme = URL_SCHEME_SSH; separator = ':'; } } @@ -1116,13 +1086,13 @@ static enum protocol parse_connect_url(const char *url_orig, char **ret_host, */ end = host_end(&host, 0); - if (protocol == PROTO_LOCAL) + if (scheme == URL_SCHEME_LOCAL) path = end; - else if (protocol == PROTO_FILE && *host != '/' && + else if (scheme == URL_SCHEME_FILE && *host != '/' && !has_dos_drive_prefix(host) && offset_1st_component(host - 2) > 1) path = host - 2; /* include the leading "//" */ - else if (protocol == PROTO_FILE && has_dos_drive_prefix(end)) + else if (scheme == URL_SCHEME_FILE && has_dos_drive_prefix(end)) path = end; /* "file://$(pwd)" may be "file://C:/projects/repo" */ else path = strchr(end, separator); @@ -1138,7 +1108,7 @@ static enum protocol parse_connect_url(const char *url_orig, char **ret_host, end = path; /* Need to \0 terminate host here */ if (separator == ':') path++; /* path starts after ':' */ - if (protocol == PROTO_GIT || protocol == PROTO_SSH) { + if (scheme == URL_SCHEME_GIT || scheme == URL_SCHEME_SSH) { if (path[1] == '~') path++; } @@ -1149,7 +1119,7 @@ static enum protocol parse_connect_url(const char *url_orig, char **ret_host, *ret_host = xstrdup(host); *ret_path = path; free(url); - return protocol; + return scheme; } static const char *get_ssh_command(void) @@ -1429,12 +1399,12 @@ static void fill_ssh_args(struct child_process *conn, const char *ssh_host, * the connection failed). */ struct child_process *git_connect(int fd[2], const char *url, - const char *name, + enum git_connect_service service, const char *prog, int flags) { char *hostandport, *path; struct child_process *conn; - enum protocol protocol; + enum url_scheme scheme; enum protocol_version version = get_protocol_version_config(); /* @@ -1443,7 +1413,7 @@ struct child_process *git_connect(int fd[2], const char *url, * fetch, ls-remote, etc), then fallback to v0 since we don't know how * to do anything else (like push or remote archive) via v2. */ - if (version == protocol_v2 && strcmp("git-upload-pack", name)) + if (version == protocol_v2 && service != GIT_CONNECT_UPLOAD_PACK) version = protocol_v0; /* Without this we cannot rely on waitpid() to tell @@ -1451,14 +1421,14 @@ struct child_process *git_connect(int fd[2], const char *url, */ signal(SIGCHLD, SIG_DFL); - protocol = parse_connect_url(url, &hostandport, &path); - if ((flags & CONNECT_DIAG_URL) && (protocol != PROTO_SSH)) { + scheme = parse_connect_url(url, &hostandport, &path); + if ((flags & CONNECT_DIAG_URL) && (scheme != URL_SCHEME_SSH)) { printf("Diag: url=%s\n", url ? url : "NULL"); - printf("Diag: protocol=%s\n", prot_name(protocol)); + printf("Diag: protocol=%s\n", url_scheme_name(scheme)); printf("Diag: hostandport=%s\n", hostandport ? hostandport : "NULL"); printf("Diag: path=%s\n", path ? path : "NULL"); conn = NULL; - } else if (protocol == PROTO_GIT) { + } else if (scheme == URL_SCHEME_GIT) { conn = git_connect_git(fd, hostandport, path, prog, version, flags); conn->trace2_child_class = "transport/git"; } else { @@ -1481,7 +1451,7 @@ struct child_process *git_connect(int fd[2], const char *url, conn->use_shell = 1; conn->in = conn->out = -1; - if (protocol == PROTO_SSH) { + if (scheme == URL_SCHEME_SSH) { char *ssh_host = hostandport; const char *port = NULL; transport_check_allowed("ssh"); @@ -1492,7 +1462,7 @@ struct child_process *git_connect(int fd[2], const char *url, if (flags & CONNECT_DIAG_URL) { printf("Diag: url=%s\n", url ? url : "NULL"); - printf("Diag: protocol=%s\n", prot_name(protocol)); + printf("Diag: protocol=%s\n", url_scheme_name(scheme)); printf("Diag: userandhost=%s\n", ssh_host ? ssh_host : "NULL"); printf("Diag: port=%s\n", port ? port : "NONE"); printf("Diag: path=%s\n", path ? path : "NULL"); @@ -1517,8 +1487,6 @@ struct child_process *git_connect(int fd[2], const char *url, } strvec_push(&conn->args, cmd.buf); - conn->clean_on_exit = 1; - conn->wait_after_clean = 1; if (start_command(conn)) die(_("unable to fork"));
diff --git a/connect.h b/connect.h index 1645126..aa482a3 100644 --- a/connect.h +++ b/connect.h
@@ -7,13 +7,17 @@ #define CONNECT_DIAG_URL (1u << 1) #define CONNECT_IPV4 (1u << 2) #define CONNECT_IPV6 (1u << 3) -struct child_process *git_connect(int fd[2], const char *url, const char *name, const char *prog, int flags); +enum git_connect_service { + GIT_CONNECT_UPLOAD_PACK, + GIT_CONNECT_RECEIVE_PACK, + GIT_CONNECT_UPLOAD_ARCHIVE, +}; +struct child_process *git_connect(int fd[2], const char *url, enum git_connect_service, const char *prog, int flags); int finish_connect(struct child_process *conn); int git_connection_is_socket(struct child_process *conn); int server_supports(const char *feature); int parse_feature_request(const char *features, const char *feature); const char *server_feature_value(const char *feature, size_t *len_ret); -int url_is_local_not_ssh(const char *url); struct packet_reader; enum protocol_version discover_version(struct packet_reader *reader);
diff --git a/connected.c b/connected.c index 6718503..7e26976 100644 --- a/connected.c +++ b/connected.c
@@ -76,6 +76,8 @@ int check_connected(oid_iterate_fn fn, void *cb_data, promisor_pack_found: ; } while ((oid = fn(cb_data)) != NULL); + if (opt->err_fd) + close(opt->err_fd); return 0; }
diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt index 81b4306..a57c4b4 100644 --- a/contrib/buildsystems/CMakeLists.txt +++ b/contrib/buildsystems/CMakeLists.txt
@@ -255,7 +255,7 @@ add_compile_definitions(HAVE_ALLOCA_H NO_POSIX_GOODIES NATIVE_CRLF NO_UNIX_SOCKETS WIN32 _CONSOLE DETECT_MSYS_TTY STRIP_EXTENSION=".exe" NO_SYMLINK_HEAD UNRELIABLE_FSTAT NOGDI OBJECT_CREATION_MODE=1 __USE_MINGW_ANSI_STDIO=0 - USE_NED_ALLOCATOR OVERRIDE_STRDUP MMAP_PREVENTS_DELETE USE_WIN32_MMAP + OVERRIDE_STRDUP MMAP_PREVENTS_DELETE USE_WIN32_MMAP HAVE_WPGMPTR ENSURE_MSYSTEM_IS_SET HAVE_RTLGENRANDOM) list(APPEND compat_SOURCES compat/mingw.c @@ -267,7 +267,6 @@ compat/win32/syslog.c compat/win32/trace2_win32_process_info.c compat/win32/dirent.c - compat/nedmalloc/nedmalloc.c compat/strdup.c) set(NO_UNIX_SOCKETS 1) @@ -293,23 +292,26 @@ if(SUPPORTS_SIMPLE_IPC) if(CMAKE_SYSTEM_NAME STREQUAL "Windows") - add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND) - list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-win32.c) - list(APPEND compat_SOURCES compat/fsmonitor/fsm-health-win32.c) - list(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-win32.c) - list(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-win32.c) - - add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS) - list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-win32.c) + set(FSMONITOR_DAEMON_BACKEND "win32") + set(FSMONITOR_OS_SETTINGS "win32") elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(FSMONITOR_DAEMON_BACKEND "darwin") + set(FSMONITOR_OS_SETTINGS "unix") + elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(FSMONITOR_DAEMON_BACKEND "linux") + set(FSMONITOR_OS_SETTINGS "unix") + add_compile_definitions(HAVE_LINUX_MAGIC_H) + endif() + + if(FSMONITOR_DAEMON_BACKEND) add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND) - list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c) - list(APPEND compat_SOURCES compat/fsmonitor/fsm-health-darwin.c) - list(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-darwin.c) - list(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-darwin.c) + list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-${FSMONITOR_DAEMON_BACKEND}.c) + list(APPEND compat_SOURCES compat/fsmonitor/fsm-health-${FSMONITOR_DAEMON_BACKEND}.c) + list(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-${FSMONITOR_OS_SETTINGS}.c) + list(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-${FSMONITOR_DAEMON_BACKEND}.c) add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS) - list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-darwin.c) + list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-${FSMONITOR_OS_SETTINGS}.c) endif() endif() @@ -1152,8 +1154,8 @@ file(STRINGS ${CMAKE_SOURCE_DIR}/GIT-BUILD-OPTIONS.in git_build_options NEWLINE_CONSUME) string(REPLACE "@BROKEN_PATH_FIX@" "" git_build_options "${git_build_options}") string(REPLACE "@DIFF@" "'${DIFF}'" git_build_options "${git_build_options}") -string(REPLACE "@FSMONITOR_DAEMON_BACKEND@" "win32" git_build_options "${git_build_options}") -string(REPLACE "@FSMONITOR_OS_SETTINGS@" "win32" git_build_options "${git_build_options}") +string(REPLACE "@FSMONITOR_DAEMON_BACKEND@" "${FSMONITOR_DAEMON_BACKEND}" git_build_options "${git_build_options}") +string(REPLACE "@FSMONITOR_OS_SETTINGS@" "${FSMONITOR_OS_SETTINGS}" git_build_options "${git_build_options}") string(REPLACE "@GITWEBDIR@" "'${GITWEBDIR}'" git_build_options "${git_build_options}") string(REPLACE "@GIT_INTEROP_MAKE_OPTS@" "" git_build_options "${git_build_options}") string(REPLACE "@GIT_PERF_LARGE_REPO@" "" git_build_options "${git_build_options}")
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index a8e7c6d..e875787 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash
@@ -2195,7 +2195,7 @@ --not --all --branches --tags --remotes --first-parent --merges --no-merges - --max-count= + --max-count= --max-count-oldest= --max-age= --since= --after= --min-age= --until= --before= --min-parents= --max-parents=
diff --git a/contrib/git-jump/README b/contrib/git-jump/README index 3211841..aabec4a 100644 --- a/contrib/git-jump/README +++ b/contrib/git-jump/README
@@ -75,8 +75,20 @@ # arbitrary grep options git jump grep -i foo_bar +# jump to places with conflict markers or whitespace errors +# (as reported by `git diff --check`) +git jump ws + # use the silver searcher for git jump grep git config jump.grepCmd "ag --column" + +# pick a mode automatically: "merge" if there are unmerged paths, +# "diff" if the worktree has unstaged changes, "ws" if there are +# whitespace problems; otherwise show usage +git jump auto + +# with no explicit mode and no args, same as "auto" +git jump -------------------------------------------------- You can use the optional argument '--stdout' to print the listing to
diff --git a/contrib/git-jump/git-jump b/contrib/git-jump/git-jump index 8d1d5d7..79286d8 100755 --- a/contrib/git-jump/git-jump +++ b/contrib/git-jump/git-jump
@@ -3,9 +3,11 @@ usage() { cat <<\EOF usage: git jump [--stdout] <mode> [<args>] + or: git jump [--stdout] Jump to interesting elements in an editor. -The <mode> parameter is one of: +The <mode> parameter is one of the following. +With no <mode> and no <args>, it defaults to "auto". diff: elements are diff hunks. Arguments are given to diff. @@ -16,6 +18,10 @@ ws: elements are whitespace errors. Arguments are given to diff --check. +auto: select one of the other modes based on worktree state; + "merge" if there are unmerged paths, "diff" if there are + unstaged changes, "ws" if there are whitespace errors. + If the optional argument `--stdout` is given, print the quickfix lines to standard output instead of feeding it to the editor. EOF @@ -82,6 +88,21 @@ git diff --check "$@" } +mode_auto() { + if test "$(git rev-parse --is-inside-work-tree 2>/dev/null)" != "true"; then + usage >&2 + exit 1 + fi + if test -n "$(git ls-files -u "$@")"; then + mode_merge "$@" + elif ! git diff --quiet "$@"; then + mode_diff "$@" + else + usage >&2 + exit 1 + fi +} + use_stdout= while test $# -gt 0; do case "$1" in @@ -99,8 +120,7 @@ shift done if test $# -lt 1; then - usage >&2 - exit 1 + set -- auto fi mode=$1; shift type "mode_$mode" >/dev/null 2>&1 || { usage >&2; exit 1; }
diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh index 18d2b56..4194687 100755 --- a/contrib/subtree/t/t7900-subtree.sh +++ b/contrib/subtree/t/t7900-subtree.sh
@@ -75,7 +75,7 @@ # # Create a simple subtree on a new branch named ORPHAN in REPO. # The subtree is then merged into the current branch of REPO, -# under PREFIX. The generated subtree has has one commit +# under PREFIX. The generated subtree has one commit # with subject and tag FILENAME with a single file "FILENAME.t" # # When this method returns:
diff --git a/contrib/vscode/init.sh b/contrib/vscode/init.sh index f2d61bb..3d58f73 100755 --- a/contrib/vscode/init.sh +++ b/contrib/vscode/init.sh
@@ -202,7 +202,6 @@ "\\bUSE_STDEV\\b", "\\Wchar *\\*\\W*utfs\\W", "cURL's", - "nedmalloc'ed", "ntifs\\.h", ], }
diff --git a/convert.c b/convert.c index eae36c8..0365068 100644 --- a/convert.c +++ b/convert.c
@@ -1239,7 +1239,7 @@ static int ident_to_worktree(const char *src, size_t len, /* step 4: substitute */ strbuf_addstr(buf, "Id: "); - strbuf_addstr(buf, oid_to_hex(&oid)); + strbuf_add_oid_hex(buf, &oid); strbuf_addstr(buf, " $"); } strbuf_add(buf, src, len);
diff --git a/csum-file.c b/csum-file.c index 9558177..d7a682c 100644 --- a/csum-file.c +++ b/csum-file.c
@@ -178,7 +178,7 @@ struct hashfile *hashfd_ext(const struct git_hash_algo *algop, f->algop = unsafe_hash_algo(algop); f->algop->init_fn(&f->ctx); - f->buffer_len = opts->buffer_len ? opts->buffer_len : 128 * 1024; + f->buffer_len = opts->buffer_len ? opts->buffer_len : DEFAULT_IO_BUFFER_SIZE; f->buffer = xmalloc(f->buffer_len); f->check_buffer = NULL;
diff --git a/csum-file.h b/csum-file.h index a9b390d..a270738 100644 --- a/csum-file.h +++ b/csum-file.h
@@ -52,7 +52,7 @@ struct hashfd_options { */ struct progress *progress; - /* The length of the buffer that shall be used read read data. */ + /* The length of the buffer that shall be used to read data. */ size_t buffer_len; };
diff --git a/daemon.c b/daemon.c index 0a7b1aa..5d79782 100644 --- a/daemon.c +++ b/daemon.c
@@ -244,14 +244,14 @@ static const char *path_ok(const char *directory, struct hostinfo *hi) } enter_repo_flags = strict_paths ? ENTER_REPO_STRICT : 0; - path = enter_repo(dir, enter_repo_flags); + path = enter_repo(the_repository, dir, enter_repo_flags); if (!path && base_path && base_path_relaxed) { /* * if we fail and base_path_relaxed is enabled, try without * prefixing the base path */ dir = directory; - path = enter_repo(dir, enter_repo_flags); + path = enter_repo(the_repository, dir, enter_repo_flags); } if (!path) { @@ -674,9 +674,20 @@ static void lookup_hostname(struct hostinfo *hi) gai = getaddrinfo(hi->hostname.buf, NULL, &hints, &ai); if (!gai) { - struct sockaddr_in *sin_addr = (void *)ai->ai_addr; + void *addr; - inet_ntop(AF_INET, &sin_addr->sin_addr, + if (ai->ai_family == AF_INET) { + struct sockaddr_in *sa = (void *)ai->ai_addr; + addr = &sa->sin_addr; + } else if (ai->ai_family == AF_INET6) { + struct sockaddr_in6 *sa6 = (void *)ai->ai_addr; + addr = &sa6->sin6_addr; + } else { + die("unexpected address family: %d", + ai->ai_family); + } + + inet_ntop(ai->ai_family, addr, addrbuf, sizeof(addrbuf)); strbuf_addstr(&hi->ip_address, addrbuf); @@ -742,7 +753,7 @@ static int execute(void) struct strvec env = STRVEC_INIT; if (addr) - loginfo("Connection from %s:%s", addr, port); + loginfo("Connection from %s:%s", addr, port ? port : "?"); set_keep_alive(0); alarm(init_timeout ? init_timeout : timeout); @@ -936,7 +947,7 @@ struct socketlist { size_t alloc; }; -static const char *ip2str(int family, struct sockaddr *sin, socklen_t len) +static const char *ip2str(int family, struct sockaddr *sin) { #ifdef NO_IPV6 static char ip[INET_ADDRSTRLEN]; @@ -947,11 +958,11 @@ static const char *ip2str(int family, struct sockaddr *sin, socklen_t len) switch (family) { #ifndef NO_IPV6 case AF_INET6: - inet_ntop(family, &((struct sockaddr_in6*)sin)->sin6_addr, ip, len); + inet_ntop(family, &((struct sockaddr_in6*)sin)->sin6_addr, ip, sizeof(ip)); break; #endif case AF_INET: - inet_ntop(family, &((struct sockaddr_in*)sin)->sin_addr, ip, len); + inet_ntop(family, &((struct sockaddr_in*)sin)->sin_addr, ip, sizeof(ip)); break; default: xsnprintf(ip, sizeof(ip), "<unknown>"); @@ -1008,14 +1019,14 @@ static int setup_named_sock(char *listen_addr, int listen_port, struct socketlis if (bind(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) { logerror("Could not bind to %s: %s", - ip2str(ai->ai_family, ai->ai_addr, ai->ai_addrlen), + ip2str(ai->ai_family, ai->ai_addr), strerror(errno)); close(sockfd); continue; /* not fatal */ } if (listen(sockfd, 5) < 0) { logerror("Could not listen to %s: %s", - ip2str(ai->ai_family, ai->ai_addr, ai->ai_addrlen), + ip2str(ai->ai_family, ai->ai_addr), strerror(errno)); close(sockfd); continue; /* not fatal */ @@ -1069,7 +1080,7 @@ static int setup_named_sock(char *listen_addr, int listen_port, struct socketlis if ( bind(sockfd, (struct sockaddr *)&sin, sizeof sin) < 0 ) { logerror("Could not bind to %s: %s", - ip2str(AF_INET, (struct sockaddr *)&sin, sizeof(sin)), + ip2str(AF_INET, (struct sockaddr *)&sin), strerror(errno)); close(sockfd); return 0; @@ -1077,7 +1088,7 @@ static int setup_named_sock(char *listen_addr, int listen_port, struct socketlis if (listen(sockfd, 5) < 0) { logerror("Could not listen to %s: %s", - ip2str(AF_INET, (struct sockaddr *)&sin, sizeof(sin)), + ip2str(AF_INET, (struct sockaddr *)&sin), strerror(errno)); close(sockfd); return 0;
diff --git a/date.c b/date.c index 17a9507..014065b 100644 --- a/date.c +++ b/date.c
@@ -1071,13 +1071,22 @@ void datestamp(struct strbuf *out) /* * Relative time update (eg "2 days ago"). If we haven't set the time * yet, we need to set it from current time. + * + * The tm->tm_mday field has an additional logic of using negative values + * for date adjustments: -2 means yesterday and -3 the day before that, + * and so on. The idea is to defer such adjustments until we are sure + * there's no explicit mday specification in the approxidate string. */ static time_t update_tm(struct tm *tm, struct tm *now, time_t sec) { time_t n; - if (tm->tm_mday < 0) + if (tm->tm_mday < 0) { + int offset = tm->tm_mday + 1; + if (sec == 0 && offset < 0) + sec = -offset * 24*60*60; tm->tm_mday = now->tm_mday; + } if (tm->tm_mon < 0) tm->tm_mon = now->tm_mon; if (tm->tm_year < 0) { @@ -1127,34 +1136,39 @@ static void date_now(struct tm *tm, struct tm *now, int *num) static void date_yesterday(struct tm *tm, struct tm *now, int *num) { *num = 0; + tm->tm_mday = -1; update_tm(tm, now, 24*60*60); } -static void date_time(struct tm *tm, struct tm *now, int hour) +static void date_time(struct tm *tm, int hour) { - if (tm->tm_hour < hour) - update_tm(tm, now, 24*60*60); + /* + * If we do not yet have a specified day, we'll use the most recent + * version of "hour" relative to now. But that may be yesterday. + */ + if (tm->tm_mday < 0 && tm->tm_hour < hour) + tm->tm_mday = -2; /* eventually handled by update_tm() */ tm->tm_hour = hour; tm->tm_min = 0; tm->tm_sec = 0; } -static void date_midnight(struct tm *tm, struct tm *now, int *num) +static void date_midnight(struct tm *tm, struct tm *now UNUSED, int *num) { pending_number(tm, num); - date_time(tm, now, 0); + date_time(tm, 0); } -static void date_noon(struct tm *tm, struct tm *now, int *num) +static void date_noon(struct tm *tm, struct tm *now UNUSED, int *num) { pending_number(tm, num); - date_time(tm, now, 12); + date_time(tm, 12); } -static void date_tea(struct tm *tm, struct tm *now, int *num) +static void date_tea(struct tm *tm, struct tm *now UNUSED, int *num) { pending_number(tm, num); - date_time(tm, now, 17); + date_time(tm, 17); } static void date_pm(struct tm *tm, struct tm *now UNUSED, int *num) @@ -1192,6 +1206,17 @@ static void date_never(struct tm *tm, struct tm *now UNUSED, int *num) *num = 0; } +static void date_today(struct tm *tm, struct tm *now, int *num) +{ + if (tm->tm_hour == now->tm_hour && + tm->tm_min == now->tm_min && + tm->tm_sec == now->tm_sec) + date_time(tm, 0); + *num = 0; + tm->tm_mday = -1; + update_tm(tm, now, 0); +} + static const struct special { const char *name; void (*fn)(struct tm *, struct tm *, int *); @@ -1204,6 +1229,7 @@ static const struct special { { "AM", date_am }, { "never", date_never }, { "now", date_now }, + { "today", date_today }, { NULL } };
diff --git a/delta-islands.c b/delta-islands.c index f4d2468..e71a7e1 100644 --- a/delta-islands.c +++ b/delta-islands.c
@@ -527,7 +527,7 @@ void free_island_marks(void) kh_destroy_oid_map(island_marks); } - /* detect use-after-free with a an address which is never valid: */ + /* detect use-after-free with an address which is never valid: */ island_marks = (void *)-1; }
diff --git a/diff.c b/diff.c index 397e38b..9be7582 100644 --- a/diff.c +++ b/diff.c
@@ -60,8 +60,8 @@ static int diff_suppress_blank_empty; static enum git_colorbool diff_use_color_default = GIT_COLOR_UNKNOWN; static int diff_color_moved_default; static int diff_color_moved_ws_default; -static int diff_context_default = 3; -static int diff_interhunk_context_default; +static unsigned int diff_context_default = 3; +static unsigned int diff_interhunk_context_default; static char *diff_word_regex_cfg; static struct external_diff external_diff_cfg; static char *diff_order_file_cfg; @@ -382,16 +382,17 @@ int git_diff_ui_config(const char *var, const char *value, return 0; } if (!strcmp(var, "diff.context")) { - diff_context_default = git_config_int(var, value, ctx->kvi); - if (diff_context_default < 0) + int val = git_config_int(var, value, ctx->kvi); + if (val < 0) return -1; + diff_context_default = val; return 0; } if (!strcmp(var, "diff.interhunkcontext")) { - diff_interhunk_context_default = git_config_int(var, value, - ctx->kvi); - if (diff_interhunk_context_default < 0) + int val = git_config_int(var, value, ctx->kvi); + if (val < 0) return -1; + diff_interhunk_context_default = val; return 0; } if (!strcmp(var, "diff.renames")) { @@ -2927,6 +2928,28 @@ void print_stat_summary(FILE *fp, int files, print_stat_summary_inserts_deletes(&o, files, insertions, deletions); } +/* + * Like utf8_width(), but guaranteed safe for use in loops that subtract + * per-character widths: + * + * - utf8_width() sets *start to NULL on invalid UTF-8 and returns 0; + * we restore the pointer and advance by one byte, returning width 1 + * (matching the strlen()-based fallback in utf8_strwidth()). + * + * - utf8_width() returns -1 for control characters; we return 0 + * (matching utf8_strnwidth() which skips them). + */ +static int utf8_ish_width(const char **start) +{ + const char *old = *start; + int w = utf8_width(start, NULL); + if (!*start) { + *start = old + 1; + return 1; + } + return (w < 0) ? 0 : w; +} + static void show_stats(struct diffstat_t *data, struct diff_options *options) { int i, len, add, del, adds = 0, dels = 0; @@ -3093,8 +3116,8 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) if (len < 0) len = 0; - while (name_len > len) - name_len -= utf8_width((const char**)&name, NULL); + while (name_len > len && *name) + name_len -= utf8_ish_width((const char**)&name); slash = strchr(name, '/'); if (slash) @@ -3589,8 +3612,9 @@ static unsigned char *deflate_it(char *data, int bound; unsigned char *deflated; git_zstream stream; + struct repo_config_values *cfg = repo_config_values(the_repository); - git_deflate_init(&stream, zlib_compression_level); + git_deflate_init(&stream, cfg->zlib_compression_level); bound = git_deflate_bound(&stream, size); deflated = xmalloc(bound); stream.next_out = deflated; @@ -5924,9 +5948,12 @@ static int diff_opt_unified(const struct option *opt, BUG_ON_OPT_NEG(unset); if (arg) { - options->context = strtol(arg, &s, 10); + long val = strtol(arg, &s, 10); if (*s) return error(_("%s expects a numerical value"), "--unified"); + if (val < 0) + return error(_("%s expects a non-negative integer"), "--unified"); + options->context = val; } enable_patch_output(&options->output_format); @@ -6111,9 +6138,8 @@ struct option *add_diff_options(const struct option *opts, OPT_CALLBACK_F(0, "default-prefix", options, NULL, N_("use default prefixes a/ and b/"), PARSE_OPT_NONEG | PARSE_OPT_NOARG, diff_opt_default_prefix), - OPT_INTEGER_F(0, "inter-hunk-context", &options->interhunkcontext, - N_("show context between diff hunks up to the specified number of lines"), - PARSE_OPT_NONEG), + OPT_UNSIGNED(0, "inter-hunk-context", &options->interhunkcontext, + N_("show context between diff hunks up to the specified number of lines")), OPT_CALLBACK_F(0, "output-indicator-new", &options->output_indicators[OUTPUT_INDICATOR_NEW], N_("<char>"),
diff --git a/diff.h b/diff.h index 7eb84aa..bb5cdda 100644 --- a/diff.h +++ b/diff.h
@@ -294,9 +294,9 @@ struct diff_options { enum git_colorbool use_color; /* Number of context lines to generate in patch output. */ - int context; + unsigned int context; - int interhunkcontext; + unsigned int interhunkcontext; /* Affects the way detection logic for complete rewrites, renames and * copies.
diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c index a52d569..b0915be 100644 --- a/diffcore-pickaxe.c +++ b/diffcore-pickaxe.c
@@ -203,7 +203,7 @@ static void pickaxe(struct diff_queue_struct *q, struct diff_options *o, for (i = 0; i < q->nr; i++) diff_free_filepair(q->queue[i]); } else { - /* Showing only the filepairs that has the needle */ + /* Showing only the filepairs that have the needle */ for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; if (pickaxe_match(p, o, regexp, kws, fn))
diff --git a/dir.c b/dir.c index fcb8f6d..7a73690 100644 --- a/dir.c +++ b/dir.c
@@ -2985,7 +2985,7 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d return NULL; /* - * We only support $GIT_DIR/info/exclude and core.excludesfile + * We only support $GIT_COMMON_DIR/info/exclude and core.excludesfile * as the global ignore rule files. Any other additions * (e.g. from command line) invalidate the cache. This * condition also catches running setup_standard_excludes() @@ -3078,7 +3078,7 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d istate->cache_changed |= UNTRACKED_CHANGED; } - /* Validate $GIT_DIR/info/exclude and core.excludesfile */ + /* Validate $GIT_COMMON_DIR/info/exclude and core.excludesfile */ root = dir->untracked->root; if (!oideq(&dir->internal.ss_info_exclude.oid, &dir->untracked->ss_info_exclude.oid)) { @@ -3508,8 +3508,9 @@ int get_sparse_checkout_patterns(struct pattern_list *pl) { int res; char *sparse_filename = get_sparse_checkout_filename(); + struct repo_config_values *cfg = repo_config_values(the_repository); - pl->use_cone_patterns = core_sparse_checkout_cone; + pl->use_cone_patterns = cfg->core_sparse_checkout_cone; res = add_patterns_from_file_to_list(sparse_filename, "", 0, pl, NULL, 0); free(sparse_filename);
diff --git a/dir.h b/dir.h index 20d4a07..83e0f64 100644 --- a/dir.h +++ b/dir.h
@@ -153,7 +153,7 @@ struct oid_stat { * - The list of files and directories of the directory in question * - The $GIT_DIR/index * - dir_struct flags - * - The content of $GIT_DIR/info/exclude + * - The content of $GIT_COMMON_DIR/info/exclude * - The content of core.excludesfile * - The content (or the lack) of .gitignore of all parent directories * from $GIT_WORK_TREE
diff --git a/entry.c b/entry.c index 7817aee..c55e867 100644 --- a/entry.c +++ b/entry.c
@@ -443,7 +443,8 @@ static int check_path(const char *path, int len, struct stat *st, int skiplen) static void mark_colliding_entries(const struct checkout *state, struct cache_entry *ce, struct stat *st) { - int trust_ino = check_stat; + struct repo_config_values *cfg = repo_config_values(the_repository); + int trust_ino = cfg->check_stat; #if defined(GIT_WINDOWS_NATIVE) || defined(__CYGWIN__) trust_ino = 0;
diff --git a/environment.c b/environment.c index fc3ed8b..ba2c601 100644 --- a/environment.c +++ b/environment.c
@@ -42,20 +42,15 @@ static int pack_compression_seen; static int zlib_compression_seen; int trust_executable_bit = 1; -int trust_ctime = 1; -int check_stat = 1; int has_symlinks = 1; int minimum_abbrev = 4, default_abbrev = -1; int ignore_case; int assume_unchanged; int is_bare_repository_cfg = -1; /* unspecified */ -int warn_on_object_refname_ambiguity = 1; char *git_commit_encoding; char *git_log_output_encoding; char *apply_default_whitespace; char *apply_default_ignorewhitespace; -int zlib_compression_level = Z_BEST_SPEED; -int pack_compression_level = Z_DEFAULT_COMPRESSION; int fsync_object_files = -1; int use_fsync = -1; enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT; @@ -74,9 +69,6 @@ enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED; #endif enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE; int grafts_keep_true_parents; -int core_sparse_checkout_cone; -int sparse_expect_files_outside_of_patterns; -int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */ unsigned long pack_size_limit_cfg; #ifndef PROTECT_HFS_DEFAULT @@ -309,16 +301,16 @@ int git_default_core_config(const char *var, const char *value, return 0; } if (!strcmp(var, "core.trustctime")) { - trust_ctime = git_config_bool(var, value); + cfg->trust_ctime = git_config_bool(var, value); return 0; } if (!strcmp(var, "core.checkstat")) { if (!value) return config_error_nonbool(var); if (!strcasecmp(value, "default")) - check_stat = 1; + cfg->check_stat = 1; else if (!strcasecmp(value, "minimal")) - check_stat = 0; + cfg->check_stat = 0; else return error(_("invalid value for '%s': '%s'"), var, value); @@ -379,7 +371,7 @@ int git_default_core_config(const char *var, const char *value, level = Z_DEFAULT_COMPRESSION; else if (level < 0 || level > Z_BEST_COMPRESSION) die(_("bad zlib compression level %d"), level); - zlib_compression_level = level; + cfg->zlib_compression_level = level; zlib_compression_seen = 1; return 0; } @@ -391,9 +383,9 @@ int git_default_core_config(const char *var, const char *value, else if (level < 0 || level > Z_BEST_COMPRESSION) die(_("bad zlib compression level %d"), level); if (!zlib_compression_seen) - zlib_compression_level = level; + cfg->zlib_compression_level = level; if (!pack_compression_seen) - pack_compression_level = level; + cfg->pack_compression_level = level; return 0; } @@ -531,12 +523,12 @@ int git_default_core_config(const char *var, const char *value, } if (!strcmp(var, "core.sparsecheckoutcone")) { - core_sparse_checkout_cone = git_config_bool(var, value); + cfg->core_sparse_checkout_cone = git_config_bool(var, value); return 0; } if (!strcmp(var, "core.precomposeunicode")) { - precomposed_unicode = git_config_bool(var, value); + cfg->precomposed_unicode = git_config_bool(var, value); return 0; } @@ -556,8 +548,10 @@ int git_default_core_config(const char *var, const char *value, static int git_default_sparse_config(const char *var, const char *value) { + struct repo_config_values *cfg = repo_config_values(the_repository); + if (!strcmp(var, "sparse.expectfilesoutsideofpatterns")) { - sparse_expect_files_outside_of_patterns = git_config_bool(var, value); + cfg->sparse_expect_files_outside_of_patterns = git_config_bool(var, value); return 0; } @@ -665,6 +659,8 @@ static int git_default_attr_config(const char *var, const char *value) int git_default_config(const char *var, const char *value, const struct config_context *ctx, void *cb) { + struct repo_config_values *cfg = repo_config_values(the_repository); + if (starts_with(var, "core.")) return git_default_core_config(var, value, ctx, cb); @@ -704,7 +700,7 @@ int git_default_config(const char *var, const char *value, level = Z_DEFAULT_COMPRESSION; else if (level < 0 || level > Z_BEST_COMPRESSION) die(_("bad pack compression level %d"), level); - pack_compression_level = level; + cfg->pack_compression_level = level; pack_compression_seen = 1; return 0; } @@ -721,4 +717,12 @@ void repo_config_values_init(struct repo_config_values *cfg) cfg->attributes_file = NULL; cfg->apply_sparse_checkout = 0; cfg->branch_track = BRANCH_TRACK_REMOTE; + cfg->trust_ctime = 1; + cfg->check_stat = 1; + cfg->zlib_compression_level = Z_BEST_SPEED; + cfg->pack_compression_level = Z_DEFAULT_COMPRESSION; + cfg->precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */ + cfg->core_sparse_checkout_cone = 0; + cfg->sparse_expect_files_outside_of_patterns = 0; + cfg->warn_on_object_refname_ambiguity = 1; }
diff --git a/environment.h b/environment.h index 123a71c..6f18286 100644 --- a/environment.h +++ b/environment.h
@@ -91,6 +91,16 @@ struct repo_config_values { /* section "core" config values */ char *attributes_file; int apply_sparse_checkout; + int trust_ctime; + int check_stat; + int zlib_compression_level; + int pack_compression_level; + int precomposed_unicode; + int core_sparse_checkout_cone; + int warn_on_object_refname_ambiguity; + + /* section "sparse" config values */ + int sparse_expect_files_outside_of_patterns; /* section "branch" config values */ enum branch_track branch_track; @@ -130,13 +140,6 @@ void repo_config_values_init(struct repo_config_values *cfg); * `the_repository`. We should eventually get rid of these and make the * dependency on a repository explicit: * - * - `setup_git_env()` ideally shouldn't exist as it modifies global state, - * namely the environment. The current process shouldn't ever access that - * state via envvars though, but should instead consult a `struct - * repository`. When spawning new processes, we would ideally also pass a - * `struct repository` and then set up the environment variables for the - * child process, only. - * * - `have_git_dir()` should not have to exist at all. Instead, we should * decide on whether or not we have a `struct repository`. * @@ -147,7 +150,6 @@ void repo_config_values_init(struct repo_config_values *cfg); * Please do not add new global config variables here. */ # ifdef USE_THE_REPOSITORY_VARIABLE -void setup_git_env(const char *git_dir); /* * Returns true iff we have a configured git repository (either via @@ -161,26 +163,17 @@ extern char *git_work_tree_cfg; /* Environment bits from configuration mechanism */ extern int trust_executable_bit; -extern int trust_ctime; -extern int check_stat; extern int has_symlinks; extern int minimum_abbrev, default_abbrev; extern int ignore_case; extern int assume_unchanged; -extern int warn_on_object_refname_ambiguity; extern char *apply_default_whitespace; extern char *apply_default_ignorewhitespace; -extern int zlib_compression_level; -extern int pack_compression_level; extern unsigned long pack_size_limit_cfg; -extern int precomposed_unicode; extern int protect_hfs; extern int protect_ntfs; -extern int core_sparse_checkout_cone; -extern int sparse_expect_files_outside_of_patterns; - enum rebase_setup_type { AUTOREBASE_NEVER = 0, AUTOREBASE_LOCAL,
diff --git a/fetch-negotiator.h b/fetch-negotiator.h index e348905..6ca422a 100644 --- a/fetch-negotiator.h +++ b/fetch-negotiator.h
@@ -47,6 +47,15 @@ struct fetch_negotiator { */ int (*ack)(struct fetch_negotiator *, struct commit *); + /* + * Inform the negotiator that this commit has already been sent as + * a "have" line outside of the negotiator's control. The negotiator + * should avoid outputting it from next() and may use it to optimize + * further negotiation (e.g., by treating it and its ancestors as + * common). + */ + void (*have_sent)(struct fetch_negotiator *, struct commit *); + void (*release)(struct fetch_negotiator *); /* internal use */
diff --git a/fetch-pack.c b/fetch-pack.c index c8fa0a6..120e01f 100644 --- a/fetch-pack.c +++ b/fetch-pack.c
@@ -25,6 +25,7 @@ #include "oidset.h" #include "packfile.h" #include "odb.h" +#include "object-name.h" #include "path.h" #include "connected.h" #include "fetch-negotiator.h" @@ -290,21 +291,21 @@ static int next_flush(int stateless_rpc, int count) } static void mark_tips(struct fetch_negotiator *negotiator, - const struct oid_array *negotiation_tips) + const struct oid_array *negotiation_restrict_tips) { struct refs_for_each_ref_options opts = { .flags = REFS_FOR_EACH_INCLUDE_BROKEN, }; int i; - if (!negotiation_tips) { + if (!negotiation_restrict_tips) { refs_for_each_ref_ext(get_main_ref_store(the_repository), rev_list_insert_ref_oid, negotiator, &opts); return; } - for (i = 0; i < negotiation_tips->nr; i++) - rev_list_insert_ref(negotiator, &negotiation_tips->oid[i]); + for (i = 0; i < negotiation_restrict_tips->nr; i++) + rev_list_insert_ref(negotiator, &negotiation_restrict_tips->oid[i]); return; } @@ -331,6 +332,21 @@ static void send_filter(struct fetch_pack_args *args, } } +static void add_oids_to_set(const struct oid_array *array, + struct oidset *set) +{ + if (!array) + return; + + for (size_t i = 0; i < array->nr; i++) { + struct object_id *oid = &array->oid[i]; + if (!odb_has_object(the_repository->objects, oid, 0)) + die(_("the object %s does not exist"), oid_to_hex(oid)); + + oidset_insert(set, oid); + } +} + static int find_common(struct fetch_negotiator *negotiator, struct fetch_pack_args *args, int fd[2], struct object_id *result_oid, @@ -346,6 +362,7 @@ static int find_common(struct fetch_negotiator *negotiator, struct strbuf req_buf = STRBUF_INIT; size_t state_len = 0; struct packet_reader reader; + struct oidset negotiation_include_oids = OIDSET_INIT; if (args->stateless_rpc && multi_ack == 1) die(_("the option '%s' requires '%s'"), "--stateless-rpc", "multi_ack_detailed"); @@ -354,7 +371,7 @@ static int find_common(struct fetch_negotiator *negotiator, PACKET_READ_CHOMP_NEWLINE | PACKET_READ_DIE_ON_ERR_PACKET); - mark_tips(negotiator, args->negotiation_tips); + mark_tips(negotiator, args->negotiation_restrict_tips); for_each_cached_alternate(negotiator, insert_one_alternate_object); fetching = 0; @@ -473,6 +490,27 @@ static int find_common(struct fetch_negotiator *negotiator, trace2_region_enter("fetch-pack", "negotiation_v0_v1", the_repository); flushes = 0; retval = -1; + + /* Send unconditional haves from --negotiation-include */ + add_oids_to_set(args->negotiation_include_tips, + &negotiation_include_oids); + if (oidset_size(&negotiation_include_oids)) { + struct oidset_iter iter; + oidset_iter_init(&negotiation_include_oids, &iter); + + while ((oid = oidset_iter_next(&iter))) { + struct commit *commit; + packet_buf_write(&req_buf, "have %s\n", + oid_to_hex(oid)); + print_verbose(args, "have %s", oid_to_hex(oid)); + count++; + + commit = lookup_commit(the_repository, oid); + if (commit) + negotiator->have_sent(negotiator, commit); + } + } + while ((oid = negotiator->next(negotiator))) { packet_buf_write(&req_buf, "have %s\n", oid_to_hex(oid)); print_verbose(args, "have %s", oid_to_hex(oid)); @@ -583,6 +621,7 @@ static int find_common(struct fetch_negotiator *negotiator, flushes++; } strbuf_release(&req_buf); + oidset_clear(&negotiation_include_oids); if (!got_ready || !no_done) consume_shallow_list(args, &reader); @@ -1304,11 +1343,27 @@ static void add_common(struct strbuf *req_buf, struct oidset *common) static int add_haves(struct fetch_negotiator *negotiator, struct strbuf *req_buf, - int *haves_to_send) + int *haves_to_send, + struct oidset *negotiation_include_oids) { int haves_added = 0; const struct object_id *oid; + /* Send unconditional haves from --negotiation-include */ + if (negotiation_include_oids) { + struct oidset_iter iter; + oidset_iter_init(negotiation_include_oids, &iter); + + while ((oid = oidset_iter_next(&iter))) { + struct commit *commit = lookup_commit(the_repository, oid); + if (commit) { + packet_buf_write(req_buf, "have %s\n", + oid_to_hex(oid)); + negotiator->have_sent(negotiator, commit); + } + } + } + while ((oid = negotiator->next(negotiator))) { packet_buf_write(req_buf, "have %s\n", oid_to_hex(oid)); if (++haves_added >= *haves_to_send) @@ -1357,7 +1412,8 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out, struct fetch_pack_args *args, const struct ref *wants, struct oidset *common, int *haves_to_send, int *in_vain, - int sideband_all, int seen_ack) + int sideband_all, int seen_ack, + struct oidset *negotiation_include_oids) { int haves_added; int done_sent = 0; @@ -1412,7 +1468,8 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out, /* Add all of the common commits we've found in previous rounds */ add_common(&req_buf, common); - haves_added = add_haves(negotiator, &req_buf, haves_to_send); + haves_added = add_haves(negotiator, &req_buf, haves_to_send, + negotiation_include_oids); *in_vain += haves_added; trace2_data_intmax("negotiation_v2", the_repository, "haves_added", haves_added); trace2_data_intmax("negotiation_v2", the_repository, "in_vain", *in_vain); @@ -1657,6 +1714,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args, struct ref *ref = copy_ref_list(orig_ref); enum fetch_state state = FETCH_CHECK_LOCAL; struct oidset common = OIDSET_INIT; + struct oidset negotiation_include_oids = OIDSET_INIT; struct packet_reader reader; int in_vain = 0, negotiation_started = 0; int negotiation_round = 0; @@ -1730,7 +1788,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args, else state = FETCH_SEND_REQUEST; - mark_tips(negotiator, args->negotiation_tips); + mark_tips(negotiator, args->negotiation_restrict_tips); + add_oids_to_set(args->negotiation_include_tips, + &negotiation_include_oids); for_each_cached_alternate(negotiator, insert_one_alternate_object); break; @@ -1749,7 +1809,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args, &common, &haves_to_send, &in_vain, reader.use_sideband, - seen_ack)) { + seen_ack, + &negotiation_include_oids)) { trace2_region_leave_printf("negotiation_v2", "round", the_repository, "%d", negotiation_round); @@ -1886,6 +1947,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args, fsck_options_clear(&fsck_options); oidset_clear(&common); + oidset_clear(&negotiation_include_oids); return ref; } @@ -2180,16 +2242,18 @@ static void clear_common_flag(struct oidset *s) } } -void negotiate_using_fetch(const struct oid_array *negotiation_tips, +void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips, const struct string_list *server_options, int stateless_rpc, int fd[], - struct oidset *acked_commits) + struct oidset *acked_commits, + const struct oid_array *negotiation_include_tips) { struct fetch_negotiator negotiator; struct packet_reader reader; struct object_array nt_object_array = OBJECT_ARRAY_INIT; struct strbuf req_buf = STRBUF_INIT; + struct oidset negotiation_include_oids = OIDSET_INIT; int haves_to_send = INITIAL_FLUSH; int in_vain = 0; int seen_ack = 0; @@ -2198,13 +2262,16 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips, timestamp_t min_generation = GENERATION_NUMBER_INFINITY; fetch_negotiator_init(the_repository, &negotiator); - mark_tips(&negotiator, negotiation_tips); + mark_tips(&negotiator, negotiation_restrict_tips); + + add_oids_to_set(negotiation_include_tips, + &negotiation_include_oids); packet_reader_init(&reader, fd[0], NULL, 0, PACKET_READ_CHOMP_NEWLINE | PACKET_READ_DIE_ON_ERR_PACKET); - oid_array_for_each((struct oid_array *) negotiation_tips, + oid_array_for_each((struct oid_array *) negotiation_restrict_tips, add_to_object_array, &nt_object_array); @@ -2224,7 +2291,8 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips, packet_buf_write(&req_buf, "wait-for-done"); - haves_added = add_haves(&negotiator, &req_buf, &haves_to_send); + haves_added = add_haves(&negotiator, &req_buf, &haves_to_send, + &negotiation_include_oids); in_vain += haves_added; if (!haves_added || (seen_ack && in_vain >= MAX_IN_VAIN)) last_iteration = 1; @@ -2276,6 +2344,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips, clear_common_flag(acked_commits); object_array_clear(&nt_object_array); + oidset_clear(&negotiation_include_oids); negotiator.release(&negotiator); strbuf_release(&req_buf); }
diff --git a/fetch-pack.h b/fetch-pack.h index 9d34703..6d0dec7 100644 --- a/fetch-pack.h +++ b/fetch-pack.h
@@ -19,9 +19,10 @@ struct fetch_pack_args { /* * If not NULL, during packfile negotiation, fetch-pack will send "have" - * lines only with these tips and their ancestors. + * lines for all _include_ tips and then a subset of the _restrict_ tips. */ - const struct oid_array *negotiation_tips; + const struct oid_array *negotiation_restrict_tips; + const struct oid_array *negotiation_include_tips; unsigned deepen_relative:1; unsigned quiet:1; @@ -89,11 +90,12 @@ struct ref *fetch_pack(struct fetch_pack_args *args, * In the capability advertisement that has happened prior to invoking this * function, the "wait-for-done" capability must be present. */ -void negotiate_using_fetch(const struct oid_array *negotiation_tips, +void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips, const struct string_list *server_options, int stateless_rpc, int fd[], - struct oidset *acked_commits); + struct oidset *acked_commits, + const struct oid_array *negotiation_include_tips); /* * Print an appropriate error message for each sought ref that wasn't
diff --git a/fsck.c b/fsck.c index b72200c..b4ffee6 100644 --- a/fsck.c +++ b/fsck.c
@@ -344,7 +344,7 @@ const char *fsck_describe_object(struct fsck_options *options, buf = bufs + b; b = (b + 1) % ARRAY_SIZE(bufs); strbuf_reset(buf); - strbuf_addstr(buf, oid_to_hex(oid)); + strbuf_add_oid_hex(buf, oid); if (name) strbuf_addf(buf, " (%s)", name);
diff --git a/fsmonitor-ipc.c b/fsmonitor-ipc.c index f1b1631..6112d13 100644 --- a/fsmonitor-ipc.c +++ b/fsmonitor-ipc.c
@@ -61,6 +61,9 @@ static int spawn_daemon(void) cmd.git_cmd = 1; cmd.no_stdin = 1; + cmd.no_stdout = 1; + cmd.no_stderr = 1; + cmd.close_fd_above_stderr = 1; cmd.trace2_child_class = "fsmonitor"; strvec_pushl(&cmd.args, "fsmonitor--daemon", "start", NULL);
diff --git a/git-compat-util.h b/git-compat-util.h index ae1bdc9..8809776 100644 --- a/git-compat-util.h +++ b/git-compat-util.h
@@ -614,12 +614,30 @@ static inline bool strip_suffix(const char *str, const char *suffix, int git_open_cloexec(const char *name, int flags); #define git_open(name) git_open_cloexec(name, O_RDONLY) -static inline size_t st_add(size_t a, size_t b) + +/* + * Help Clang; GCC generates the same instructions for both variants on + * x64 and aarch64. + */ +#ifdef __clang__ +#define st_add_overflow __builtin_add_overflow +#else +static inline bool st_add_overflow(size_t a, size_t b, size_t *out) { if (unsigned_add_overflows(a, b)) + return true; + *out = a + b; + return false; +} +#endif + +static inline size_t st_add(size_t a, size_t b) +{ + size_t result; + if (st_add_overflow(a, b, &result)) die("size_t overflow: %"PRIuMAX" + %"PRIuMAX, (uintmax_t)a, (uintmax_t)b); - return a + b; + return result; } #define st_add3(a,b,c) st_add(st_add((a),(b)),(c)) #define st_add4(a,b,c,d) st_add(st_add3((a),(b),(c)),(d)) @@ -712,6 +730,12 @@ static inline uint64_t u64_add(uint64_t a, uint64_t b) # endif #endif +/* + * Default buffer size for buffered I/O in index-pack, unpack-objects, + * and the hashfile layer in csum-file. + */ +#define DEFAULT_IO_BUFFER_SIZE (128 * 1024) + #ifdef HAVE_ALLOCA_H # include <alloca.h> # define xalloca(size) (alloca(size))
diff --git a/git-gui/Makefile b/git-gui/Makefile index ca01068..d33204e 100644 --- a/git-gui/Makefile +++ b/git-gui/Makefile
@@ -64,6 +64,7 @@ REMOVE_F1 = CLEAN_DST = true +ifneq ($(findstring s,$(firstword -$(MAKEFLAGS))),s) ifndef V QUIET = @ QUIET_GEN = $(QUIET)echo ' ' GEN '$@' && @@ -89,6 +90,7 @@ REMOVE_F0 = dst= REMOVE_F1 = && echo ' ' REMOVE `basename "$$dst"` && $(RM_RF) "$$dst" endif +endif TCLTK_PATH ?= wish ifeq (./,$(dir $(TCLTK_PATH))) @@ -97,10 +99,6 @@ TCL_PATH ?= $(dir $(TCLTK_PATH))$(notdir $(subst wish,tclsh,$(TCLTK_PATH))) endif -ifeq ($(findstring $(firstword -$(MAKEFLAGS)),s),s) -QUIET_GEN = -endif - -include config.mak DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index 23fe76e..15dd2b3 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh
@@ -372,8 +372,8 @@ set _appname {Git Gui} set _gitdir {} set _gitworktree {} -set _isbare {} set _githtmldir {} +set _prefix {} set _reponame {} set _shellpath {@@SHELL_PATH@@} @@ -523,29 +523,7 @@ } proc is_bare {} { - global _isbare - global _gitdir - global _gitworktree - - if {$_isbare eq {}} { - if {[catch { - set _bare [git rev-parse --is-bare-repository] - switch -- $_bare { - true { set _isbare 1 } - false { set _isbare 0} - default { throw } - } - }]} { - if {[is_config_true core.bare] - || ($_gitworktree eq {} - && [lindex [file split $_gitdir] end] ne {.git})} { - set _isbare 1 - } else { - set _isbare 0 - } - } - } - return $_isbare + return [expr {$::_gitworktree eq {}}] } ###################################################################### @@ -670,6 +648,9 @@ set current_branch [git branch --show-current] set is_detached [expr [string length $current_branch] == 0] + if {$is_detached} { + set current_branch {HEAD} + } } auto_load tk_optionMenu @@ -1043,6 +1024,8 @@ ## ## feature option selection +enable_option picker +enable_option gitdir_discovery if {[regexp {^git-(.+)$} [file tail $argv0] _junk subcommand]} { unset _junk } else { @@ -1054,6 +1037,9 @@ if {$subcommand eq {gui} && [llength $argv] > 0} { set subcommand [lindex $argv 0] set argv [lrange $argv 1 end] + if {$subcommand eq {gui}} { + disable_option picker + } } enable_option multicommit @@ -1069,6 +1055,7 @@ disable_option multicommit disable_option branch disable_option transport + disable_option picker } citool { enable_option singlecommit @@ -1077,6 +1064,7 @@ disable_option multicommit disable_option branch disable_option transport + disable_option picker while {[llength $argv] > 0} { set a [lindex $argv 0] @@ -1099,6 +1087,9 @@ set argv [lrange $argv 1 end] } } +pick { + disable_option gitdir_discovery +} } ###################################################################### @@ -1122,26 +1113,138 @@ ## ## repository setup +proc find_worktree_from_gitdir {} { + # this is invoked only if the current directory is inside the repository + set worktree {} + if {[file tail $::_gitdir] eq {.git}} { + # the dir containing .git is a worktree if repo allows it + # Check that git reports parent as a worktree (gitdir might not allow a worktree) + if {[catch { + set parent [file dirname $::_gitdir] + set worktree [git -C $parent rev-parse --show-toplevel] + }]} { + set worktree {} + } + } elseif [file exists {gitdir}] { + # a worktree gitdir has .gitdir naming worktree/.git + # assure git run there reports this dir as the gitdir (links might be broken) + if {[catch { + set fd_gitdir [open {gitdir} {r}] + set worktree [file dirname [read $fd_gitdir]] + catch {close $fd_gitdir} + set worktree_gitdir [git -C $worktree rev-parse --absolute-git-dir] + if {$::_gitdir ne $worktree_gitdir} { + set worktree {} + } + }]} { + catch {close $fd_gitdir} + set worktree {} + } + } + return $worktree +} + +proc is_gitvars_error {err} { + set havevars 0 + set GIT_DIR {} + set GIT_WORK_TREE {} + catch {set GIT_DIR $::env(GIT_DIR); set havevars 1} + catch {set GIT_WORK_TREE $::env(GIT_WORK_TREE); set havevars 1} + + if {$havevars} { + catch {wm withdraw .} + error_popup [strcat [mc "Invalid configuration:"] \ + "\n" "GIT_DIR: " $GIT_DIR \ + "\n" "GIT_WORK_TREE: " $GIT_WORK_TREE \ + "\n\n$err"] + return 1 + } + return 0 +} + +proc set_gitdir_vars {} { + global _gitdir _gitworktree env + set env(GIT_DIR) $_gitdir + if {$_gitworktree ne {}} { + set env(GIT_WORK_TREE) $_gitworktree + } +} + +proc unset_gitdir_vars {} { + global env + catch {unset env(GIT_DIR)} + catch {unset env(GIT_WORK_TREE)} +} + +# find repository +set _gitdir {} +if {[is_enabled gitdir_discovery]} { + if {[catch { + set _gitdir [git rev-parse --absolute-git-dir] + } err]} { + if {[is_gitvars_error $err]} { + exit 1 + } + set _gitdir {} + } +} + set picked 0 -if {[catch { - set _gitdir $env(GIT_DIR) - set _prefix {} - }] - && [catch { - # beware that from the .git dir this sets _gitdir to . - # and _prefix to the empty string - set _gitdir [git rev-parse --git-dir] - set _prefix [git rev-parse --show-prefix] - } err]} { +if {$_gitdir eq {} && [is_enabled picker]} { + unset_gitdir_vars load_config 1 apply_config choose_repository::pick - if {![file isdirectory $_gitdir]} { + if {[catch { + set _gitdir [git rev-parse --absolute-git-dir] + } err]} { + catch {wm withdraw .} + error_popup [strcat [mc "Unusable repo/worktree:"] " [pwd] \n\n$err"] exit 1 } set picked 1 } +if {$_gitdir eq {}} { + catch {wm withdraw .} + error_popup [strcat [mc "Git directory not found:"] "\n\n$err"] + exit 1 +} + +# find worktree, continue without if not required +if {[catch { + set _gitworktree [git rev-parse --show-toplevel] + set _prefix [git rev-parse --show-prefix] + } err]} { + if {[is_gitvars_error $err]} { + exit 1 + } + set _gitworktree {} + set _prefix {} +} + +if {[is_bare]} { + # Maybe we are in an embedded or worktree specific gitdir + if {[set _gitworktree [find_worktree_from_gitdir]] ne {}} { + set _prefix {} + } +} + +if {![is_bare]} { + if {[catch {cd $_gitworktree} err]} { + catch {wm withdraw .} + error_popup [strcat [mc "No working directory"] " $_gitworktree:\n\n$err"] + exit 1 + } +} elseif {![is_enabled bare]} { + catch {wm withdraw .} + error_popup [strcat [mc "Cannot use bare repository:"] "\n\n$_gitdir"] + exit 1 +} + +# repository and worktree config are complete, export them +set_gitdir_vars + # Use object format as hash algorithm (either "sha1" or "sha256") set hashalgorithm [git rev-parse --show-object-format] if {$hashalgorithm eq "sha1"} { @@ -1153,53 +1256,10 @@ exit 1 } -# we expand the _gitdir when it's just a single dot (i.e. when we're being -# run from the .git dir itself) lest the routines to find the worktree -# get confused -if {$_gitdir eq "."} { - set _gitdir [pwd] -} - -if {![file isdirectory $_gitdir]} { - catch {wm withdraw .} - error_popup [strcat [mc "Git directory not found:"] "\n\n$_gitdir"] - exit 1 -} # _gitdir exists, so try loading the config load_config 0 apply_config -set _gitworktree [git rev-parse --show-toplevel] - -if {$_prefix ne {}} { - if {$_gitworktree eq {}} { - regsub -all {[^/]+/} $_prefix ../ cdup - } else { - set cdup $_gitworktree - } - if {[catch {cd $cdup} err]} { - catch {wm withdraw .} - error_popup [strcat [mc "Cannot move to top of working directory:"] "\n\n$err"] - exit 1 - } - set _gitworktree [pwd] - unset cdup -} elseif {![is_enabled bare]} { - if {[is_bare]} { - catch {wm withdraw .} - error_popup [strcat [mc "Cannot use bare repository:"] "\n\n$_gitdir"] - exit 1 - } - if {$_gitworktree eq {}} { - set _gitworktree [file dirname $_gitdir] - } - if {[catch {cd $_gitworktree} err]} { - catch {wm withdraw .} - error_popup [strcat [mc "No working directory"] " $_gitworktree:\n\n$err"] - exit 1 - } - set _gitworktree [pwd] -} set _reponame [file split [file normalize $_gitdir]] if {[lindex $_reponame end] eq {.git}} { set _reponame [lindex $_reponame end-1] @@ -1207,9 +1267,6 @@ set _reponame [lindex $_reponame end] } -set env(GIT_DIR) $_gitdir -set env(GIT_WORK_TREE) $_gitworktree - ###################################################################### ## ## global init @@ -2007,7 +2064,6 @@ proc do_gitk {revs {is_submodule false}} { global current_diff_path file_states current_diff_side ui_index - global _gitdir _gitworktree # -- Always start gitk through whatever we were loaded with. This # lets us bypass using shell process on Windows systems. @@ -2017,15 +2073,9 @@ if {$exe eq {}} { error_popup [mc "Couldn't find gitk in PATH"] } else { - global env - set pwd [pwd] - if {!$is_submodule} { - if {![is_bare]} { - cd $_gitworktree - } - } else { + if {$is_submodule} { cd $current_diff_path if {$revs eq {--}} { set s $file_states($current_diff_path) @@ -2050,13 +2100,11 @@ # TODO we could make life easier (start up faster?) for gitk # by setting these to the appropriate values to allow gitk # to skip the heuristics to find their proper value - unset env(GIT_DIR) - unset env(GIT_WORK_TREE) + unset_gitdir_vars } safe_exec_bg [concat $cmd $revs "--" "--"] - set env(GIT_DIR) $_gitdir - set env(GIT_WORK_TREE) $_gitworktree + set_gitdir_vars cd $pwd if {[info exists main_status]} { @@ -2079,21 +2127,16 @@ if {$exe eq {}} { error_popup [mc "Couldn't find git gui in PATH"] } else { - global env - global _gitdir _gitworktree - # see note in do_gitk about unsetting these vars when # running tools in a submodule - unset env(GIT_DIR) - unset env(GIT_WORK_TREE) + unset_gitdir_vars set pwd [pwd] cd $current_diff_path safe_exec_bg [concat $exe gui] - set env(GIT_DIR) $_gitdir - set env(GIT_WORK_TREE) $_gitworktree + set_gitdir_vars cd $pwd set status_operation [$::main_status \ @@ -2965,7 +3008,21 @@ } lappend elements $item } - return [eval file join $elements] + if {$elements ne {}} { + return [eval file join $elements] + } else { + return {.} + } +} + +proc show_parse_err {err} { + if {[tk windowingsystem] eq "win32"} { + catch {wm withdraw .} + error_popup $err + } else { + puts stderr $err + } + exit 1 } # -- Not a normal commit type invocation? Do that instead! @@ -2974,108 +3031,103 @@ browser - blame { if {$subcommand eq "blame"} { - set subcommand_args {[--line=<num>] rev? path} + set subcommand_args {[--line=<num>] [rev] [--] <filename>} + set required_pathtype blob } else { - set subcommand_args {rev? path} + set subcommand_args {[rev] [--] <dirname>} + set required_pathtype tree } - if {$argv eq {}} usage + set maxargs [llength $subcommand_args] + set nargs [llength $argv] + if {$nargs < 1 || $nargs > $maxargs} usage set head {} set path {} set jump_spec {} - set is_path 0 - foreach a $argv { - set p [file join $_prefix $a] - if {$is_path || [file exists $p]} { - if {$path ne {}} usage - set path [normalize_relpath $p] - break + set iarg 0 + foreach a $argv { + incr iarg + if {$iarg == $nargs} { + # final argument is path + set path [normalize_relpath [file join $_prefix $a]] } elseif {$a eq {--}} { - if {$path ne {}} { - if {$head ne {}} usage - set head $path - set path {} + # allow before required final arg that must be path + if {$iarg != $nargs - 1} { + usage } - set is_path 1 } elseif {[regexp {^--line=(\d+)$} $a a lnum]} { - if {$jump_spec ne {} || $head ne {}} usage + # --line can only be the first arg + if {$iarg != 1 || $subcommand ne {blame}} usage set jump_spec [list $lnum] } elseif {$head eq {}} { - if {$head ne {}} usage set head $a - set is_path 1 } else { usage } } - unset is_path - if {$head ne {} && $path eq {}} { - if {[string index $head 0] eq {/}} { - set path [normalize_relpath $head] - set head {} + # If head not given, use current branch (HEAD), + # and blame will use worktree if there is one. + set use_worktree 0 + if {$head eq {}} { + load_current_branch + set head $current_branch + if {$subcommand eq {blame} && ![is_bare]} { + if {![file isfile $path]} { + show_parse_err [mc "fatal: no such file '%s' in worktree" $path] + } + set use_worktree 1 + } + } else { + if {[catch { + set commitid \ + [git rev-parse --verify --end-of-options \ + [strcat $head "^{commit}"]] + }]} { + show_parse_err [mc "fatal: '%s' is not a valid rev'" $head] } else { - set path [normalize_relpath $_prefix$head] - set head {} + set current_branch $head } } - if {$head eq {}} { - load_current_branch - } else { - if {[regexp [string map "@@ [expr $hashlength - 1]" {^[0-9a-f]{1,@@}$}] $head]} { - if {[catch { - set head [git rev-parse --verify $head] - } err]} { - if {[tk windowingsystem] eq "win32"} { - tk_messageBox -icon error -title [mc Error] -message $err - } else { - puts stderr $err - } - exit 1 - } + # check path is known in head, and is file / directory as required + set pathtype {} + catch {set pathtype [git ls-tree {--format=%(objecttype)} $head $path]} + if {$pathtype ne {} && $path eq {.}} { + # ls-tree gives contents of root-dir, we need root-dir itself + set pathtype {tree} + } + + if {$pathtype ne $required_pathtype} { + switch -- $required_pathtype { + tree {show_parse_err \ + [mc "'%s' is not a directory in rev '%s'" $path $head]} + blob {show_parse_err \ + [mc "'%s' is not a filename in rev '%s'" $path $head]} } - set current_branch $head } wm deiconify . switch -- $subcommand { browser { - if {$jump_spec ne {}} usage - if {$head eq {}} { - if {$path ne {} && [file isdirectory $path]} { - set head $current_branch - } else { - set head $path - set path {} - } - } browser::new $head $path } blame { - if {$head eq {} && ![file exists $path]} { - catch {wm withdraw .} - tk_messageBox \ - -icon error \ - -type ok \ - -title [mc "git-gui: fatal error"] \ - -message [mc "fatal: cannot stat path %s: No such file or directory" $path] - exit 1 - } - blame::new $head $path $jump_spec + blame::new [expr {$use_worktree ? {} : $head}] $path $jump_spec } } return } citool - -gui { +gui - +pick { if {[llength $argv] != 0} { usage } # fall through to setup UI for commits } default { - set err "[mc usage:] $argv0 \[{blame|browser|citool}\]" + set err "[mc usage:] $argv0 \[{blame|browser|citool|gui|pick}\]" if {[tk windowingsystem] eq "win32"} { wm withdraw . tk_messageBox -icon error -message $err \
diff --git a/git-gui/lib/choose_repository.tcl b/git-gui/lib/choose_repository.tcl index 7e1462a..4b06afe 100644 --- a/git-gui/lib/choose_repository.tcl +++ b/git-gui/lib/choose_repository.tcl
@@ -15,7 +15,7 @@ field w_localpath ; # Entry widget bound to local_path field done 0 ; # Finished picking the repository? -field clone_ok false ; # clone succeeeded +field pick_ok 0 ; # true if repo pick/clone succeeded field local_path {} ; # Where this repository is locally field origin_url {} ; # Where we are cloning from field origin_name origin ; # What we shall call 'origin' @@ -220,6 +220,8 @@ if {$top eq {.}} { eval destroy [winfo children $top] } + + return $pick_ok } method _center {} { @@ -327,8 +329,7 @@ } _append_recentrepos [pwd] - set ::_gitdir .git - set ::_prefix {} + set pick_ok 1 return 1 } @@ -409,6 +410,7 @@ if {![_git_init $this]} { return } + set pick_ok 1 set done 1 } @@ -621,7 +623,7 @@ } tkwait variable @done - if {!$clone_ok} { + if {!$pick_ok} { error_popup [mc "Clone failed."] return } @@ -632,18 +634,12 @@ if {$ok} { if {[catch { cd $local_path - set ::_gitdir .git - set ::_prefix {} _append_recentrepos [pwd] } err]} { set ok 0 } } - if {!$ok} { - set ::_gitdir {} - set ::_prefix {} - } - set clone_ok $ok + set pick_ok $ok set done 1 } @@ -721,8 +717,7 @@ } _append_recentrepos [pwd] - set ::_gitdir $actualgit - set ::_prefix {} + set pick_ok 1 set done 1 }
diff --git a/git.c b/git.c index 5a40eab..36f0889 100644 --- a/git.c +++ b/git.c
@@ -84,7 +84,7 @@ static int list_cmds(const char *spec) * Set up the repository so we can pick up any repo-level config (like * completion.commands). */ - setup_git_directory_gently(&nongit); + setup_git_directory_gently(the_repository, &nongit); while (*spec) { const char *sep = strchrnul(spec, ','); @@ -386,7 +386,7 @@ static int handle_alias(struct strvec *args, struct string_list *expanded_aliase int nongit_ok; /* Aliases expect GIT_PREFIX, GIT_DIR etc to be set */ - setup_git_directory_gently(&nongit_ok); + setup_git_directory_gently(the_repository, &nongit_ok); commit_pager_choice(); @@ -477,10 +477,10 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv, struct run_setup = RUN_SETUP_GENTLY; if (run_setup & RUN_SETUP) { - prefix = setup_git_directory(); + prefix = setup_git_directory(the_repository); no_repo = 0; } else if (run_setup & RUN_SETUP_GENTLY) { - prefix = setup_git_directory_gently(&no_repo); + prefix = setup_git_directory_gently(the_repository, &no_repo); } else { prefix = NULL; } @@ -497,7 +497,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv, struct commit_pager_choice(); if (!help && p->option & NEED_WORK_TREE) - setup_work_tree(); + setup_work_tree(the_repository); trace_argv_printf(argv, "trace: built-in: git"); trace2_cmd_name(p->cmd); @@ -578,6 +578,7 @@ static struct cmd_struct commands[] = { { "for-each-ref", cmd_for_each_ref, RUN_SETUP }, { "for-each-repo", cmd_for_each_repo, RUN_SETUP_GENTLY }, { "format-patch", cmd_format_patch, RUN_SETUP }, + { "format-rev", cmd_format_rev, RUN_SETUP }, { "fsck", cmd_fsck, RUN_SETUP }, { "fsck-objects", cmd_fsck, RUN_SETUP }, { "fsmonitor--daemon", cmd_fsmonitor__daemon, RUN_SETUP }, @@ -670,6 +671,7 @@ static struct cmd_struct commands[] = { { "upload-archive", cmd_upload_archive, NO_PARSEOPT }, { "upload-archive--writer", cmd_upload_archive_writer, NO_PARSEOPT }, { "upload-pack", cmd_upload_pack }, + { "url-parse", cmd_url_parse }, { "var", cmd_var, RUN_SETUP_GENTLY | NO_PARSEOPT }, { "verify-commit", cmd_verify_commit, RUN_SETUP }, { "verify-pack", cmd_verify_pack },
diff --git a/gitk-git/gitk b/gitk-git/gitk index 2730274..0f35710 100755 --- a/gitk-git/gitk +++ b/gitk-git/gitk
@@ -2469,7 +2469,8 @@ -selectbackground $selectbgcolor \ -background $bgcolor -bd 0 \ -xscrollincr $linespc \ - -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll" + -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll" \ + -xscrollcommand ".tf.histframe.cxsb set" .tf.histframe.pwclist add $canv set canv2 .tf.histframe.pwclist.canv2 canvas $canv2 \ @@ -2487,9 +2488,11 @@ .tf.histframe.pwclist sashpos 0 [lindex $::geometry(pwsash0) 0] } - # a scroll bar to rule them + # a scroll bar to rule them (vertical), and one for horizontal scroll of left pane ttk::scrollbar $cscroll -command {allcanvs yview} pack $cscroll -side right -fill y + ttk::scrollbar .tf.histframe.cxsb -orient horizontal -command "$canv xview" + pack .tf.histframe.cxsb -side bottom -fill x bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w} lappend bglist $canv $canv2 $canv3 pack .tf.histframe.pwclist -fill both -expand 1 -side left
diff --git a/graph.c b/graph.c index 26f6fbf..8422826 100644 --- a/graph.c +++ b/graph.c
@@ -317,6 +317,15 @@ struct git_graph { struct strbuf prefix_buf; }; +static inline int graph_needs_truncation(struct git_graph *graph, int lane) +{ + int max = graph->revs->graph_max_lanes; + /* + * Ignore values <= 0, meaning no limit. + */ + return max > 0 && lane >= max; +} + static const char *diff_output_prefix_callback(struct diff_options *opt, void *data) { struct git_graph *graph = data; @@ -697,6 +706,19 @@ static void graph_update_columns(struct git_graph *graph) } /* + * If graph_max_lanes is set, cap the width + */ + if (graph->revs->graph_max_lanes > 0) { + /* + * width of "| " per lanes plus truncation mark "~ ". + * Allow commits from merges to align to the merged lane. + */ + int max_width = graph->revs->graph_max_lanes * 2 + 2; + if (graph->width > max_width) + graph->width = max_width; + } + + /* * Shrink mapping_size to be the minimum necessary */ while (graph->mapping_size > 1 && @@ -846,6 +868,10 @@ static void graph_output_padding_line(struct git_graph *graph, * Output a padding row, that leaves all branch lines unchanged */ for (i = 0; i < graph->num_new_columns; i++) { + if (graph_needs_truncation(graph, i)) { + graph_line_addstr(line, "~ "); + break; + } graph_line_write_column(line, &graph->new_columns[i], '|'); graph_line_addch(line, ' '); } @@ -903,6 +929,9 @@ static void graph_output_pre_commit_line(struct git_graph *graph, seen_this = 1; graph_line_write_column(line, col, '|'); graph_line_addchars(line, ' ', graph->expansion_row); + } else if (seen_this && graph_needs_truncation(graph, i)) { + graph_line_addstr(line, "~ "); + break; } else if (seen_this && (graph->expansion_row == 0)) { /* * This is the first line of the pre-commit output. @@ -994,6 +1023,16 @@ static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_line col = &graph->new_columns[j]; graph_line_write_column(line, col, '-'); + + /* + * Commit is at commit_index, each iteration move one lane to + * the right from the commit. + */ + if (graph_needs_truncation(graph, graph->commit_index + 1 + i)) { + graph_line_addstr(line, "~ "); + break; + } + graph_line_write_column(line, col, (i == dashed_parents - 1) ? '.' : '-'); } @@ -1028,8 +1067,17 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line seen_this = 1; graph_output_commit_char(graph, line); + if (graph_needs_truncation(graph, i)) { + graph_line_addch(line, ' '); + break; + } + if (graph->num_parents > 2) graph_draw_octopus_merge(graph, line); + } else if (graph_needs_truncation(graph, i)) { + graph_line_addstr(line, "~ "); + seen_this = 1; + break; } else if (seen_this && (graph->edges_added > 1)) { graph_line_write_column(line, col, '\\'); } else if (seen_this && (graph->edges_added == 1)) { @@ -1065,13 +1113,46 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line /* * Update graph->state + * + * If the commit is a merge and the first parent is in a visible lane, + * then the GRAPH_POST_MERGE is needed to draw the merge lane. + * + * If the commit is over the truncation limit, but the first parent is on + * a visible lane, then we still need the merge lane but truncated. + * + * If both commit and first parent are over the truncation limit, then + * there's no need to draw the merge lane because it would work as a + * padding lane. */ - if (graph->num_parents > 1) - graph_update_state(graph, GRAPH_POST_MERGE); - else if (graph_is_mapping_correct(graph)) + if (graph->num_parents > 1) { + if (!graph_needs_truncation(graph, graph->commit_index)) { + graph_update_state(graph, GRAPH_POST_MERGE); + } else { + struct commit_list *p = first_interesting_parent(graph); + int lane; + + /* + * graph->num_parents are found using first_interesting_parent + * and next_interesting_parent so it can't be a scenario + * where num_parents > 1 and there are no interesting parents + */ + if (!p) + BUG("num_parents > 1 but no interesting parent"); + + lane = graph_find_new_column_by_commit(graph, p->item); + + if (!graph_needs_truncation(graph, lane)) + graph_update_state(graph, GRAPH_POST_MERGE); + else if (graph_is_mapping_correct(graph)) + graph_update_state(graph, GRAPH_PADDING); + else + graph_update_state(graph, GRAPH_COLLAPSING); + } + } else if (graph_is_mapping_correct(graph)) { graph_update_state(graph, GRAPH_PADDING); - else + } else { graph_update_state(graph, GRAPH_COLLAPSING); + } } static const char merge_chars[] = {'/', '|', '\\'}; @@ -1109,6 +1190,7 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l int par_column; int idx = graph->merge_layout; char c; + int truncated = 0; seen_this = 1; for (j = 0; j < graph->num_parents; j++) { @@ -1117,23 +1199,56 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l c = merge_chars[idx]; graph_line_write_column(line, &graph->new_columns[par_column], c); + + /* + * j counts parents, it needs to be halved to be + * comparable with i. Don't truncate if there are + * no more lanes to print (end of the lane) + */ + if (graph_needs_truncation(graph, j / 2 + i) && + j / 2 + i <= graph->num_columns) { + if ((j + i * 2) % 2 != 0) + graph_line_addch(line, ' '); + graph_line_addstr(line, "~ "); + truncated = 1; + break; + } + if (idx == 2) { - if (graph->edges_added > 0 || j < graph->num_parents - 1) + /* + * Check if the next lane needs truncation + * to avoid having the padding doubled + */ + if (graph_needs_truncation(graph, (j + 1) / 2 + i) && + j < graph->num_parents - 1) { + graph_line_addstr(line, "~ "); + truncated = 1; + break; + } else if (graph->edges_added > 0 || j < graph->num_parents - 1) graph_line_addch(line, ' '); } else { idx++; } parents = next_interesting_parent(graph, parents); } + if (truncated) + break; if (graph->edges_added == 0) graph_line_addch(line, ' '); - + } else if (graph_needs_truncation(graph, i)) { + graph_line_addstr(line, "~ "); + break; } else if (seen_this) { if (graph->edges_added > 0) graph_line_write_column(line, col, '\\'); else graph_line_write_column(line, col, '|'); - graph_line_addch(line, ' '); + /* + * If it's between two lanes and next would be truncated, + * don't add space padding. + */ + if (!graph_needs_truncation(graph, i + 1)) + graph_line_addch(line, ' '); } else { graph_line_write_column(line, col, '|'); if (graph->merge_layout != 0 || i != graph->commit_index - 1) { @@ -1164,6 +1279,7 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l short used_horizontal = 0; int horizontal_edge = -1; int horizontal_edge_target = -1; + int truncated = 0; /* * Swap the mapping and old_mapping arrays @@ -1279,26 +1395,35 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l */ for (i = 0; i < graph->mapping_size; i++) { int target = graph->mapping[i]; - if (target < 0) - graph_line_addch(line, ' '); - else if (target * 2 == i) - graph_line_write_column(line, &graph->new_columns[target], '|'); - else if (target == horizontal_edge_target && - i != horizontal_edge - 1) { - /* - * Set the mappings for all but the - * first segment to -1 so that they - * won't continue into the next line. - */ - if (i != (target * 2)+3) - graph->mapping[i] = -1; - used_horizontal = 1; - graph_line_write_column(line, &graph->new_columns[target], '_'); + + if (!truncated && graph_needs_truncation(graph, i / 2)) { + graph_line_addstr(line, "~ "); + truncated = 1; + } + + if (target < 0) { + if (!truncated) + graph_line_addch(line, ' '); + } else if (target * 2 == i) { + if (!truncated) + graph_line_write_column(line, &graph->new_columns[target], '|'); + } else if (target == horizontal_edge_target && + i != horizontal_edge - 1) { + /* + * Set the mappings for all but the + * first segment to -1 so that they + * won't continue into the next line. + */ + if (i != (target * 2)+3) + graph->mapping[i] = -1; + used_horizontal = 1; + if (!truncated) + graph_line_write_column(line, &graph->new_columns[target], '_'); } else { if (used_horizontal && i < horizontal_edge) graph->mapping[i] = -1; - graph_line_write_column(line, &graph->new_columns[target], '/'); - + if (!truncated) + graph_line_write_column(line, &graph->new_columns[target], '/'); } } @@ -1372,6 +1497,11 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb) for (i = 0; i < graph->num_columns; i++) { struct column *col = &graph->columns[i]; + if (graph_needs_truncation(graph, i)) { + graph_line_addstr(&line, "~ "); + break; + } + graph_line_write_column(&line, col, '|'); if (col->commit == graph->commit && graph->num_parents > 2) {
diff --git a/grep.c b/grep.c index c7e1dc1..a54e5d8 100644 --- a/grep.c +++ b/grep.c
@@ -1267,6 +1267,7 @@ static void show_line(struct grep_opt *opt, regmatch_t match; enum grep_context ctx = GREP_CONTEXT_BODY; int eflags = 0; + const char *start = bol; if (want_color(opt->color)) { if (sign == ':') @@ -1285,6 +1286,7 @@ static void show_line(struct grep_opt *opt, if (match.rm_so == match.rm_eo) break; + cno = bol - start + match.rm_so + 1; if (opt->only_matching) show_line_header(opt, name, lno, cno, sign); else @@ -1294,7 +1296,6 @@ static void show_line(struct grep_opt *opt, if (opt->only_matching) opt->output(opt, "\n", 1); bol += match.rm_eo; - cno += match.rm_eo; rest -= match.rm_eo; eflags = REG_NOTBOL; }
diff --git a/help.c b/help.c index 3e59d07..4624149 100644 --- a/help.c +++ b/help.c
@@ -592,14 +592,21 @@ static int git_unknown_cmd_config(const char *var, const char *value, /* Also use aliases for command lookup */ if (!parse_config_key(var, "alias", &subsection, &subsection_len, &key)) { + size_t key_len = strlen(key); + if (subsection) { /* [alias "name"] command = value */ if (!strcmp(key, "command")) add_cmdname(&cfg->aliases, subsection, subsection_len); + else { + key = var + strlen("alias."); + key_len = strlen(key); + add_cmdname(&cfg->aliases, key, key_len); + } } else { /* alias.name = value */ - add_cmdname(&cfg->aliases, key, strlen(key)); + add_cmdname(&cfg->aliases, key, key_len); } }
diff --git a/hex.c b/hex.c index bc75672..f028321 100644 --- a/hex.c +++ b/hex.c
@@ -3,6 +3,7 @@ #include "git-compat-util.h" #include "hash.h" #include "hex.h" +#include "strbuf.h" static int get_hash_hex_algop(const char *hex, unsigned char *hash, const struct git_hash_algo *algop) @@ -122,3 +123,12 @@ char *oid_to_hex(const struct object_id *oid) { return hash_to_hex_algop(oid->hash, &hash_algos[oid->algo]); } + +void strbuf_add_oid_hex(struct strbuf *sb, const struct object_id *oid) +{ + const struct git_hash_algo *algop = oid->algo ? + &hash_algos[oid->algo] : the_hash_algo; + strbuf_grow(sb, algop->hexsz); + hash_to_hex_algop_r(sb->buf + sb->len, oid->hash, algop); + strbuf_setlen(sb, sb->len + algop->hexsz); +}
diff --git a/hex.h b/hex.h index 1e9a65d..f15c7e2 100644 --- a/hex.h +++ b/hex.h
@@ -33,6 +33,11 @@ char *oid_to_hex_r(char *out, const struct object_id *oid); char *hash_to_hex_algop(const unsigned char *hash, const struct git_hash_algo *); /* static buffer result! */ char *oid_to_hex(const struct object_id *oid); /* same static buffer */ +struct strbuf; + +/* Apply oid_to_hex_r() to a strbuf to append the hexadecimal hash. */ +void strbuf_add_oid_hex(struct strbuf *sb, const struct object_id *oid); + /* * Parse a 40-character hexadecimal object ID starting from hex, updating the * pointer specified by end when parsing stops. The resulting object ID is
diff --git a/hook.c b/hook.c index cc23276..d10eef4 100644 --- a/hook.c +++ b/hook.c
@@ -5,12 +5,23 @@ #include "environment.h" #include "gettext.h" #include "hook.h" +#include "hook-list.h" #include "parse.h" #include "path.h" #include "run-command.h" #include "setup.h" #include "strbuf.h" #include "strmap.h" +#include "thread-utils.h" + +bool is_known_hook(const char *name) +{ + const char **h; + for (h = hook_name_list; *h; h++) + if (!strcmp(*h, name)) + return true; + return false; +} const char *find_hook(struct repository *r, const char *name) { @@ -116,18 +127,27 @@ struct hook_config_cache_entry { char *command; enum config_scope scope; bool disabled; + bool parallel; }; /* * Callback struct to collect all hook.* keys in a single config pass. * commands: friendly-name to command map. * event_hooks: event-name to list of friendly-names map. - * disabled_hooks: set of friendly-names with hook.<friendly-name>.enabled = false. + * disabled_hooks: set of all names with hook.<name>.enabled = false; after + * parsing, names that are not friendly-names become event-level + * disables stored in r->disabled_events. This collects all. + * parallel_hooks: friendly-name to parallel flag. + * event_jobs: event-name to per-event jobs count (stored as uintptr_t, NULL == unset). + * jobs: value of the global hook.jobs key. Defaults to 0 if unset (stored in r->hook_jobs). */ struct hook_all_config_cb { struct strmap commands; struct strmap event_hooks; struct string_list disabled_hooks; + struct strmap parallel_hooks; + struct strmap event_jobs; + unsigned int jobs; }; /* repo_config() callback that collects all hook.* configuration in one pass. */ @@ -143,6 +163,24 @@ static int hook_config_lookup_all(const char *key, const char *value, if (parse_config_key(key, "hook", &name, &name_len, &subkey)) return 0; + /* Handle plain hook.<key> entries that have no hook name component. */ + if (!name) { + if (!strcmp(subkey, "jobs") && value) { + int v; + if (!git_parse_int(value, &v)) + warning(_("hook.jobs must be an integer, ignoring: '%s'"), value); + else if (v == -1) + data->jobs = online_cpus(); + else if (v > 0) + data->jobs = v; + else + warning(_("hook.jobs must be a positive integer" + " or -1, ignoring: '%s'"), + value); + } + return 0; + } + if (!value) return config_error_nonbool(key); @@ -158,8 +196,21 @@ static int hook_config_lookup_all(const char *key, const char *value, strmap_for_each_entry(&data->event_hooks, &iter, e) unsorted_string_list_remove(e->value, hook_name, 0); } else { - struct string_list *hooks = - strmap_get(&data->event_hooks, value); + struct string_list *hooks; + + if (is_known_hook(hook_name)) + die(_("hook friendly-name '%s' collides with " + "a known event name; please choose a " + "different friendly-name"), + hook_name); + + if (!strcmp(hook_name, value)) + warning(_("hook friendly-name '%s' is the " + "same as its event; this may cause " + "ambiguity with hook.%s.enabled"), + hook_name, hook_name); + + hooks = strmap_get(&data->event_hooks, value); if (!hooks) { CALLOC_ARRAY(hooks, 1); @@ -203,6 +254,31 @@ static int hook_config_lookup_all(const char *key, const char *value, default: break; /* ignore unrecognised values */ } + } else if (!strcmp(subkey, "parallel")) { + int v = git_parse_maybe_bool(value); + if (v >= 0) + strmap_put(&data->parallel_hooks, hook_name, + (void *)(uintptr_t)v); + else + warning(_("hook.%s.parallel must be a boolean," + " ignoring: '%s'"), + hook_name, value); + } else if (!strcmp(subkey, "jobs")) { + int v; + if (!git_parse_int(value, &v)) + warning(_("hook.%s.jobs must be an integer," + " ignoring: '%s'"), + hook_name, value); + else if (v == -1) + strmap_put(&data->event_jobs, hook_name, + (void *)(uintptr_t)online_cpus()); + else if (v > 0) + strmap_put(&data->event_jobs, hook_name, + (void *)(uintptr_t)v); + else + warning(_("hook.%s.jobs must be a positive" + " integer or -1, ignoring: '%s'"), + hook_name, value); } free(hook_name); @@ -237,20 +313,78 @@ void hook_cache_clear(struct strmap *cache) strmap_clear(cache, 0); } +/* + * Return true if `name` is a hook friendly-name, i.e. it has at least one of + * .command, .event, or .parallel configured. These are the reliable clues + * that distinguish a friendly-name from an event name. Note: .enabled is + * deliberately excluded because it can appear under both namespaces. + */ +static int is_friendly_name(struct hook_all_config_cb *cb, const char *name) +{ + struct hashmap_iter iter; + struct strmap_entry *e; + + if (strmap_get(&cb->commands, name) || strmap_get(&cb->parallel_hooks, name)) + return 1; + + strmap_for_each_entry(&cb->event_hooks, &iter, e) { + if (unsorted_string_list_lookup(e->value, name)) + return 1; + } + + return 0; +} + +/* Warn if any name in event_jobs is also a hook friendly-name. */ +static void warn_jobs_on_friendly_names(struct hook_all_config_cb *cb_data) +{ + struct hashmap_iter iter; + struct strmap_entry *e; + + strmap_for_each_entry(&cb_data->event_jobs, &iter, e) { + if (is_friendly_name(cb_data, e->key)) + warning(_("hook.%s.jobs is set but '%s' looks like a " + "hook friendly-name, not an event name; " + "hook.<event>.jobs uses the event name " + "(e.g. hook.post-receive.jobs), so this " + "setting will be ignored"), e->key, e->key); + } +} + /* Populate `cache` with the complete hook configuration */ static void build_hook_config_map(struct repository *r, struct strmap *cache) { - struct hook_all_config_cb cb_data; + struct hook_all_config_cb cb_data = { 0 }; struct hashmap_iter iter; struct strmap_entry *e; strmap_init(&cb_data.commands); strmap_init(&cb_data.event_hooks); string_list_init_dup(&cb_data.disabled_hooks); + strmap_init(&cb_data.parallel_hooks); + strmap_init(&cb_data.event_jobs); - /* Parse all configs in one run. */ + /* Parse all configs in one run, capturing hook.* including hook.jobs. */ repo_config(r, hook_config_lookup_all, &cb_data); + warn_jobs_on_friendly_names(&cb_data); + + /* + * Populate disabled_events: names in disabled_hooks that are not + * friendly-names are event-level switches (hook.<event>.enabled = false). + * Names that are friendly-names are already handled per-hook via the + * hook_config_cache_entry.disabled flag below. + */ + if (r) { + string_list_clear(&r->disabled_events, 0); + string_list_init_dup(&r->disabled_events); + for (size_t i = 0; i < cb_data.disabled_hooks.nr; i++) { + const char *n = cb_data.disabled_hooks.items[i].string; + if (!is_friendly_name(&cb_data, n)) + string_list_append(&r->disabled_events, n); + } + } + /* Construct the cache from parsed configs. */ strmap_for_each_entry(&cb_data.event_hooks, &iter, e) { struct string_list *hook_names = e->value; @@ -266,6 +400,7 @@ static void build_hook_config_map(struct repository *r, struct strmap *cache) struct hook_config_cache_entry *entry; char *command; + bool is_par = !!strmap_get(&cb_data.parallel_hooks, hname); bool is_disabled = !!unsorted_string_list_lookup( &cb_data.disabled_hooks, hname); @@ -286,13 +421,20 @@ static void build_hook_config_map(struct repository *r, struct strmap *cache) entry->command = xstrdup_or_null(command); entry->scope = scope; entry->disabled = is_disabled; + entry->parallel = is_par; string_list_append(hooks, hname)->util = entry; } strmap_put(cache, e->key, hooks); } + if (r) { + r->hook_jobs = cb_data.jobs; + r->event_jobs = cb_data.event_jobs; + } + strmap_clear(&cb_data.commands, 1); + strmap_clear(&cb_data.parallel_hooks, 0); /* values are uintptr_t, not heap ptrs */ string_list_clear(&cb_data.disabled_hooks, 0); strmap_for_each_entry(&cb_data.event_hooks, &iter, e) { string_list_clear(e->value, 0); @@ -344,6 +486,8 @@ static void list_hooks_add_configured(struct repository *r, { struct strmap *cache = get_hook_config_cache(r); struct string_list *configured_hooks = strmap_get(cache, hookname); + bool event_is_disabled = r ? !!unsorted_string_list_lookup(&r->disabled_events, + hookname) : 0; /* Iterate through configured hooks and initialize internal states */ for (size_t i = 0; configured_hooks && i < configured_hooks->nr; i++) { @@ -370,6 +514,8 @@ static void list_hooks_add_configured(struct repository *r, entry->command ? xstrdup(entry->command) : NULL; hook->u.configured.scope = entry->scope; hook->u.configured.disabled = entry->disabled; + hook->u.configured.event_disabled = event_is_disabled; + hook->parallel = entry->parallel; string_list_append(list, friendly_name)->util = hook; } @@ -381,6 +527,8 @@ static void list_hooks_add_configured(struct repository *r, if (!r || !r->gitdir) { hook_cache_clear(cache); free(cache); + if (r) + string_list_clear(&r->disabled_events, 0); } } @@ -412,7 +560,7 @@ int hook_exists(struct repository *r, const char *name) for (size_t i = 0; i < hooks->nr; i++) { struct hook *h = hooks->items[i].util; if (h->kind == HOOK_TRADITIONAL || - !h->u.configured.disabled) { + (!h->u.configured.disabled && !h->u.configured.event_disabled)) { exists = 1; break; } @@ -435,7 +583,8 @@ static int pick_next_hook(struct child_process *cp, if (hook_cb->hook_to_run_index >= hook_list->nr) return 0; h = hook_list->items[hook_cb->hook_to_run_index++].util; - } while (h->kind == HOOK_CONFIGURED && h->u.configured.disabled); + } while (h->kind == HOOK_CONFIGURED && + (h->u.configured.disabled || h->u.configured.event_disabled)); cp->no_stdin = 1; strvec_pushv(&cp->env, hook_cb->options->env.v); @@ -519,21 +668,136 @@ static void run_hooks_opt_clear(struct run_hooks_opt *options) strvec_clear(&options->args); } +/* + * When running in parallel, stdout must be merged into stderr so + * run-command can buffer and de-interleave outputs correctly. This + * applies even to hooks like pre-push that normally keep stdout and + * stderr separate: the user has opted into parallelism, so the output + * stream behavior changes accordingly. + */ +static void merge_output_if_parallel(struct run_hooks_opt *options) +{ + if (options->jobs > 1) + options->stdout_to_stderr = 1; +} + +static void warn_non_parallel_hooks_override(unsigned int jobs, + struct string_list *hook_list) +{ + /* Don't warn for hooks running sequentially. */ + if (jobs == 1) + return; + + for (size_t i = 0; i < hook_list->nr; i++) { + struct hook *h = hook_list->items[i].util; + if (h->kind == HOOK_CONFIGURED && !h->parallel) + warning(_("hook '%s' is not marked as parallel=true, " + "running in parallel anyway due to -j%u"), + h->u.configured.friendly_name, jobs); + } +} + +/* Resolve a hook.jobs config key, handling -1 as online_cpus(). */ +static void resolve_hook_config_jobs(struct repository *r, + const char *key, + unsigned int *jobs) +{ + int v; + + if (repo_config_get_int(r, key, &v)) + return; + + if (v == -1) + *jobs = online_cpus(); + else if (v > 0) + *jobs = v; + else + warning(_("%s must be a positive integer or -1," + " ignoring: %d"), key, v); +} + +/* Determine how many jobs to use for hook execution. */ +static unsigned int get_hook_jobs(struct repository *r, + struct run_hooks_opt *options, + const char *hook_name, + struct string_list *hook_list) +{ + /* + * An explicit job count overrides everything else: this covers both + * FORCE_SERIAL callers (for hooks that must never run in parallel) + * and the -j flag from the CLI. The CLI override is intentional: users + * may want to serialize hooks declared parallel or to parallelize more + * aggressively than the default. + */ + if (options->jobs) + goto cleanup; + + /* + * Use hook.jobs from the already-parsed config cache (in-repo), or + * fallback to a direct config lookup (out-of-repo). + * Default to 1 (serial execution) on failure. + */ + options->jobs = 1; + if (r) { + if (r->gitdir && r->hook_config_cache) { + void *event_jobs; + + if (r->hook_jobs) + options->jobs = r->hook_jobs; + + event_jobs = strmap_get(&r->event_jobs, hook_name); + if (event_jobs) + options->jobs = (unsigned int)(uintptr_t)event_jobs; + } else { + char *key; + + resolve_hook_config_jobs(r, "hook.jobs", &options->jobs); + + key = xstrfmt("hook.%s.jobs", hook_name); + resolve_hook_config_jobs(r, key, &options->jobs); + free(key); + } + } + + /* + * Cap to serial any configured hook not marked as parallel = true. + * This enforces the parallel = false default, even for "traditional" + * hooks from the hookdir which cannot be marked parallel = true. + * The same restriction applies whether jobs came from hook.jobs or + * hook.<event>.jobs. + */ + for (size_t i = 0; i < hook_list->nr; i++) { + struct hook *h = hook_list->items[i].util; + if (h->kind == HOOK_CONFIGURED && !h->parallel) { + options->jobs = 1; + break; + } + } + +cleanup: + merge_output_if_parallel(options); + warn_non_parallel_hooks_override(options->jobs, hook_list); + return options->jobs; +} + int run_hooks_opt(struct repository *r, const char *hook_name, struct run_hooks_opt *options) { + struct string_list *hook_list = list_hooks(r, hook_name, options); struct hook_cb_data cb_data = { .rc = 0, .hook_name = hook_name, + .hook_command_list = hook_list, .options = options, }; int ret = 0; + unsigned int jobs = get_hook_jobs(r, options, hook_name, hook_list); const struct run_process_parallel_opts opts = { .tr2_category = "hook", .tr2_label = hook_name, - .processes = options->jobs, - .ungroup = options->jobs == 1, + .processes = jobs, + .ungroup = jobs == 1, .get_next_task = pick_next_hook, .start_failure = notify_start_failure, @@ -549,9 +813,6 @@ int run_hooks_opt(struct repository *r, const char *hook_name, if (options->path_to_stdin && options->feed_pipe) BUG("options path_to_stdin and feed_pipe are mutually exclusive"); - if (!options->jobs) - BUG("run_hooks_opt must be called with options.jobs >= 1"); - /* * Ensure cb_data copy and free functions are either provided together, * or neither one is provided. @@ -562,7 +823,6 @@ int run_hooks_opt(struct repository *r, const char *hook_name, if (options->invoked_hook) *options->invoked_hook = 0; - cb_data.hook_command_list = list_hooks(r, hook_name, options); if (!cb_data.hook_command_list->nr) { if (options->error_if_missing) ret = error("cannot find a hook named %s", hook_name);
diff --git a/hook.h b/hook.h index 5c5628d..27bb1ae 100644 --- a/hook.h +++ b/hook.h
@@ -32,10 +32,18 @@ struct hook { const char *command; enum config_scope scope; bool disabled; + bool event_disabled; } configured; } u; /** + * Whether this hook may run in parallel with other hooks for the same + * event. Only useful for configured (named) hooks. Traditional hooks + * always default to 0 (serial). Set via `hook.<name>.parallel = true`. + */ + bool parallel; + + /** * Opaque data pointer used to keep internal state across callback calls. * * It can be accessed directly via the third hook callback arg: @@ -72,6 +80,8 @@ struct run_hooks_opt { * * If > 1, output will be buffered and de-interleaved (ungroup=0). * If == 1, output will be real-time (ungroup=1). + * If == 0, the 'hook.jobs' config is used or, if the config is unset, + * defaults to 1 (serial execution). */ unsigned int jobs; @@ -97,8 +107,10 @@ struct run_hooks_opt { * Send the hook's stdout to stderr. * * This is the default behavior for all hooks except pre-push, - * which has separate stdout and stderr streams for backwards - * compatibility reasons. + * which keeps stdout and stderr separate for backwards compatibility. + * When parallel execution is requested (jobs > 1), get_hook_jobs() + * overrides this to 1 for all hooks so run-command can de-interleave + * their outputs correctly. */ unsigned int stdout_to_stderr:1; @@ -116,7 +128,7 @@ struct run_hooks_opt { * While the callback allows piecemeal writing, it can also be * used for smaller inputs, where it gets called only once. * - * Add hook callback initalization context to `feed_pipe_ctx`. + * Add hook callback initialization context to `feed_pipe_ctx`. * Add hook callback internal state to `feed_pipe_cb_data`. * */ @@ -152,10 +164,26 @@ struct run_hooks_opt { hook_data_free_fn feed_pipe_cb_data_free; }; +/** + * Default initializer for hooks. Parallelism is opt-in: .jobs = 0 defers to + * the 'hook.jobs' config, falling back to serial (1) if unset. + */ #define RUN_HOOKS_OPT_INIT { \ .env = STRVEC_INIT, \ .args = STRVEC_INIT, \ .stdout_to_stderr = 1, \ + .jobs = 0, \ +} + +/** + * Initializer for hooks that must always run sequentially regardless of + * 'hook.jobs'. Use this when git knows the hook cannot safely be parallelized + * .jobs = 1 is non-overridable. + */ +#define RUN_HOOKS_OPT_INIT_FORCE_SERIAL { \ + .env = STRVEC_INIT, \ + .args = STRVEC_INIT, \ + .stdout_to_stderr = 1, \ .jobs = 1, \ } @@ -208,6 +236,12 @@ void hook_free(void *p, const char *str); void hook_cache_clear(struct strmap *cache); /** + * Returns true if `name` is a recognized hook event name + * (e.g. "pre-commit", "post-receive"). + */ +bool is_known_hook(const char *name); + +/** * Returns the path to the hook file, or NULL if the hook is missing * or disabled. Note that this points to static storage that will be * overwritten by further calls to find_hook and run_hook_*.
diff --git a/http-backend.c b/http-backend.c index 1a171c5..c7566b1 100644 --- a/http-backend.c +++ b/http-backend.c
@@ -809,7 +809,7 @@ int cmd_main(int argc UNUSED, const char **argv UNUSED) not_found(&hdr, "Request not supported: '%s'", dir); setup_path(); - if (!enter_repo(dir, 0)) + if (!enter_repo(the_repository, dir, 0)) not_found(&hdr, "Not a git repository: '%s'", dir); if (!getenv("GIT_HTTP_EXPORT_ALL") && access("git-daemon-export-ok", F_OK) )
diff --git a/http-fetch.c b/http-fetch.c index 1922e23..f9b6ecb 100644 --- a/http-fetch.c +++ b/http-fetch.c
@@ -109,7 +109,7 @@ int cmd_main(int argc, const char **argv) struct strvec index_pack_args = STRVEC_INIT; int ret; - setup_git_directory_gently(&nongit); + setup_git_directory_gently(the_repository, &nongit); while (arg < argc && argv[arg][0] == '-') { const char *p;
diff --git a/http-push.c b/http-push.c index d143fe2..8e2248c 100644 --- a/http-push.c +++ b/http-push.c
@@ -369,13 +369,14 @@ static void start_put(struct transfer_request *request) int hdrlen; ssize_t size; git_zstream stream; + struct repo_config_values *cfg = repo_config_values(the_repository); unpacked = odb_read_object(the_repository->objects, &request->obj->oid, &type, &len); hdrlen = format_object_header(hdr, sizeof(hdr), type, len); /* Set it up */ - git_deflate_init(&stream, zlib_compression_level); + git_deflate_init(&stream, cfg->zlib_compression_level); size = git_deflate_bound(&stream, len + hdrlen); strbuf_grow(&request->buffer.buf, size); request->buffer.posn = 0; @@ -1788,7 +1789,7 @@ int cmd_main(int argc, const char **argv) if (delete_branch && rs.nr != 1) die("You must specify only one branch name when deleting a remote branch"); - gitdir = setup_git_directory(); + gitdir = setup_git_directory(the_repository); memset(remote_dir_exists, -1, 256);
diff --git a/http-walker.c b/http-walker.c index 1b6d496..b58a3b2 100644 --- a/http-walker.c +++ b/http-walker.c
@@ -268,7 +268,7 @@ static void process_alternates_response(void *callback_data) */ const char *colon_ss = strstr(base,"://"); if (colon_ss) { - serverlen = (strchr(colon_ss + 3, '/') + serverlen = (strchrnul(colon_ss + 3, '/') - base); okay = 1; } @@ -539,8 +539,9 @@ static int fetch_object(struct walker *walker, const struct object_id *oid) } else if (!oideq(&obj_req->oid, &req->real_oid)) { ret = error("File %s has bad hash", hex); } else if (req->rename < 0) { + struct odb_source_files *files = odb_source_files_downcast(the_repository->objects->sources); struct strbuf buf = STRBUF_INIT; - odb_loose_path(the_repository->objects->sources, &buf, &req->oid); + odb_loose_path(files->loose, &buf, &req->oid); ret = error("unable to write sha1 filename %s", buf.buf); strbuf_release(&buf); }
diff --git a/http.c b/http.c index 67c9c6f..5f0f42f 100644 --- a/http.c +++ b/http.c
@@ -138,6 +138,7 @@ static unsigned long empty_auth_useless = CURLAUTH_BASIC | CURLAUTH_DIGEST_IE | CURLAUTH_DIGEST; +static int empty_auth_try_negotiate; static struct curl_slist *pragma_header; static struct string_list extra_http_headers = STRING_LIST_INIT_DUP; @@ -665,6 +666,22 @@ static void init_curl_http_auth(CURL *result) } } +void http_reauth_prepare(int all_capabilities) +{ + /* + * If we deferred stripping Negotiate to give empty auth a + * chance (auto mode), skip credential_fill on this retry so + * that init_curl_http_auth() sends empty credentials and + * libcurl can attempt Negotiate with the system ticket cache. + */ + if (empty_auth_try_negotiate && + !http_auth.password && !http_auth.credential && + (http_auth_methods & CURLAUTH_GSSNEGOTIATE)) + return; + + credential_fill(the_repository, &http_auth, all_capabilities); +} + /* *var must be free-able */ static void var_override(char **var, char *value) { @@ -744,6 +761,69 @@ static int has_proxy_cert_password(void) return 1; } +static const struct socks_proxy_type { + const char *name; + long curlsym; +} socks_proxy_types[] = { + { "socks", CURLPROXY_SOCKS4 }, + { "socks4", CURLPROXY_SOCKS4 }, + { "socks4a", CURLPROXY_SOCKS4A }, + { "socks5", CURLPROXY_SOCKS5 }, + { "socks5h", CURLPROXY_SOCKS5_HOSTNAME }, +}; + +static const struct socks_proxy_type *find_socks_proxy_type(const char *protocol) +{ + int i; + + if (!protocol) + return NULL; + + for (i = 0; i < ARRAY_SIZE(socks_proxy_types); i++) { + if (!strcmp(socks_proxy_types[i].name, protocol)) + return &socks_proxy_types[i]; + } + + return NULL; +} + +static int is_socks_proxy_protocol(const char *protocol) +{ + return !!find_socks_proxy_type(protocol); +} + +static int set_curl_proxy_type(CURL *result, const char *protocol) +{ + const struct socks_proxy_type *socks_proxy_type; + + if (!protocol || !strcmp(protocol, "http")) + return 0; + + socks_proxy_type = find_socks_proxy_type(protocol); + if (socks_proxy_type) { + curl_easy_setopt(result, CURLOPT_PROXYTYPE, socks_proxy_type->curlsym); + return 0; + } + + if (!strcmp(protocol, "https")) { + curl_easy_setopt(result, CURLOPT_PROXYTYPE, (long)CURLPROXY_HTTPS); + + if (http_proxy_ssl_cert) + curl_easy_setopt(result, CURLOPT_PROXY_SSLCERT, + http_proxy_ssl_cert); + + if (http_proxy_ssl_key) + curl_easy_setopt(result, CURLOPT_PROXY_SSLKEY, + http_proxy_ssl_key); + + if (has_proxy_cert_password()) + curl_easy_setopt(result, CURLOPT_PROXY_KEYPASSWD, + proxy_cert_auth.password); + } + + return -1; +} + /* Return 1 if redactions have been made, 0 otherwise. */ static int redact_sensitive_header(struct strbuf *header, size_t offset) { @@ -1214,30 +1294,6 @@ static CURL *get_curl_handle(void) } else if (curl_http_proxy) { struct strbuf proxy = STRBUF_INIT; - if (starts_with(curl_http_proxy, "socks5h")) - curl_easy_setopt(result, - CURLOPT_PROXYTYPE, (long)CURLPROXY_SOCKS5_HOSTNAME); - else if (starts_with(curl_http_proxy, "socks5")) - curl_easy_setopt(result, - CURLOPT_PROXYTYPE, (long)CURLPROXY_SOCKS5); - else if (starts_with(curl_http_proxy, "socks4a")) - curl_easy_setopt(result, - CURLOPT_PROXYTYPE, (long)CURLPROXY_SOCKS4A); - else if (starts_with(curl_http_proxy, "socks")) - curl_easy_setopt(result, - CURLOPT_PROXYTYPE, (long)CURLPROXY_SOCKS4); - else if (starts_with(curl_http_proxy, "https")) { - curl_easy_setopt(result, CURLOPT_PROXYTYPE, (long)CURLPROXY_HTTPS); - - if (http_proxy_ssl_cert) - curl_easy_setopt(result, CURLOPT_PROXY_SSLCERT, http_proxy_ssl_cert); - - if (http_proxy_ssl_key) - curl_easy_setopt(result, CURLOPT_PROXY_SSLKEY, http_proxy_ssl_key); - - if (has_proxy_cert_password()) - curl_easy_setopt(result, CURLOPT_PROXY_KEYPASSWD, proxy_cert_auth.password); - } if (strstr(curl_http_proxy, "://")) credential_from_url(&proxy_auth, curl_http_proxy); else { @@ -1247,6 +1303,10 @@ static CURL *get_curl_handle(void) strbuf_release(&url); } + if (set_curl_proxy_type(result, proxy_auth.protocol) < 0) + die("Invalid proxy URL '%s': unsupported proxy scheme '%s'", + curl_http_proxy, proxy_auth.protocol); + if (!proxy_auth.host) die("Invalid proxy URL '%s'", curl_http_proxy); @@ -1257,7 +1317,7 @@ static CURL *get_curl_handle(void) if (ver->version_num < 0x075400) die("libcurl 7.84 or later is required to support paths in proxy URLs"); - if (!starts_with(proxy_auth.protocol, "socks")) + if (!is_socks_proxy_protocol(proxy_auth.protocol)) die("Invalid proxy URL '%s': only SOCKS proxies support paths", curl_http_proxy); @@ -1890,7 +1950,18 @@ static int handle_curl_result(struct slot_results *results) http_proactive_auth = PROACTIVE_AUTH_NONE; return HTTP_NOAUTH; } else { - http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE; + if (curl_empty_auth == -1 && + !empty_auth_try_negotiate && + (results->auth_avail & CURLAUTH_GSSNEGOTIATE)) { + /* + * In auto mode, give Negotiate a chance via + * empty auth before stripping it. If it fails, + * we will strip it on the next 401. + */ + empty_auth_try_negotiate = 1; + } else { + http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE; + } if (results->auth_avail) { http_auth_methods &= results->auth_avail; http_auth_methods_restricted = 1; @@ -2398,7 +2469,7 @@ static int http_request_recoverable(const char *url, sleep(retry_delay); } } else if (ret == HTTP_REAUTH) { - credential_fill(the_repository, &http_auth, 1); + http_reauth_prepare(1); } ret = http_request(url, result, target, options); @@ -2538,18 +2609,18 @@ static int fetch_and_setup_pack_index(struct packfile_list *packs, new_pack = parse_pack_index(the_repository, sha1, tmp_idx); if (!new_pack) { - unlink(tmp_idx); free(tmp_idx); - return -1; /* parse_pack_index() already issued error message */ } ret = verify_pack_index(new_pack); - if (!ret) - close_pack_index(new_pack); + + close_pack_index(new_pack); free(tmp_idx); - if (ret) + if (ret) { + free(new_pack); return -1; + } packfile_list_prepend(packs, new_pack); return 0; @@ -2755,6 +2826,7 @@ static size_t fwrite_sha1_file(char *ptr, size_t eltsize, size_t nmemb, struct http_object_request *new_http_object_request(const char *base_url, const struct object_id *oid) { + struct odb_source_files *files = odb_source_files_downcast(the_repository->objects->sources); char *hex = oid_to_hex(oid); struct strbuf filename = STRBUF_INIT; struct strbuf prevfile = STRBUF_INIT; @@ -2769,7 +2841,7 @@ struct http_object_request *new_http_object_request(const char *base_url, oidcpy(&freq->oid, oid); freq->localfile = -1; - odb_loose_path(the_repository->objects->sources, &filename, oid); + odb_loose_path(files->loose, &filename, oid); strbuf_addf(&freq->tmpfile, "%s.temp", filename.buf); strbuf_addf(&prevfile, "%s.prev", filename.buf); @@ -2895,6 +2967,7 @@ void process_http_object_request(struct http_object_request *freq) int finish_http_object_request(struct http_object_request *freq) { + struct odb_source_files *files = odb_source_files_downcast(the_repository->objects->sources); struct stat st; struct strbuf filename = STRBUF_INIT; @@ -2921,7 +2994,7 @@ int finish_http_object_request(struct http_object_request *freq) unlink_or_warn(freq->tmpfile.buf); return -1; } - odb_loose_path(the_repository->objects->sources, &filename, &freq->oid); + odb_loose_path(files->loose, &filename, &freq->oid); freq->rename = finalize_object_file(the_repository, freq->tmpfile.buf, filename.buf); strbuf_release(&filename);
diff --git a/http.h b/http.h index f9ee888..729c519 100644 --- a/http.h +++ b/http.h
@@ -76,6 +76,12 @@ extern int http_is_verbose; extern ssize_t http_post_buffer; extern struct credential http_auth; +/** + * Prepare for an HTTP re-authentication retry. This fills credentials + * via credential_fill() so the next request can include them. + */ +void http_reauth_prepare(int all_capabilities); + extern char curl_errorstr[CURL_ERROR_SIZE]; enum http_follow_config {
diff --git a/imap-send.c b/imap-send.c index af02c6a..cfd6a51 100644 --- a/imap-send.c +++ b/imap-send.c
@@ -1799,7 +1799,7 @@ int cmd_main(int argc, const char **argv) int nongit_ok; int ret; - setup_git_directory_gently(&nongit_ok); + setup_git_directory_gently(the_repository, &nongit_ok); repo_config(the_repository, git_imap_config, &server); argc = parse_options(argc, (const char **)argv, "", imap_send_options, imap_send_usage, 0);
diff --git a/line-log.c b/line-log.c index 858a899..5fc75ae 100644 --- a/line-log.c +++ b/line-log.c
@@ -13,7 +13,6 @@ #include "revision.h" #include "xdiff-interface.h" #include "strbuf.h" -#include "log-tree.h" #include "line-log.h" #include "setup.h" #include "strvec.h" @@ -589,7 +588,7 @@ parse_lines(struct repository *r, struct commit *commit, range_part = xstrndup(item->string, name_part - item->string); name_part++; - full_name = prefix_path(prefix, prefix ? strlen(prefix) : 0, + full_name = prefix_path(r, prefix, prefix ? strlen(prefix) : 0, name_part); spec = alloc_filespec(full_name); @@ -1004,29 +1003,18 @@ static int process_all_files(struct line_log_data **range_out, return changed; } -int line_log_print(struct rev_info *rev, struct commit *commit) +void line_log_queue_pairs(struct rev_info *rev, struct commit *commit) { - show_log(rev); - if (!(rev->diffopt.output_format & DIFF_FORMAT_NO_OUTPUT)) { - struct line_log_data *range = lookup_line_range(rev, commit); - struct line_log_data *r; - const char *prefix = diff_line_prefix(&rev->diffopt); + struct line_log_data *range = lookup_line_range(rev, commit); + struct line_log_data *r; - fprintf(rev->diffopt.file, "%s\n", prefix); - - for (r = range; r; r = r->next) { - if (r->pair) { - struct diff_filepair *p = - diff_filepair_dup(r->pair); - p->line_ranges = &r->ranges; - diff_q(&diff_queued_diff, p); - } + for (r = range; r; r = r->next) { + if (r->pair) { + struct diff_filepair *p = diff_filepair_dup(r->pair); + p->line_ranges = &r->ranges; + diff_q(&diff_queued_diff, p); } - - diffcore_std(&rev->diffopt); - diff_flush(&rev->diffopt); } - return 1; } static int bloom_filter_check(struct rev_info *rev,
diff --git a/line-log.h b/line-log.h index 04a6ea6..99e1755 100644 --- a/line-log.h +++ b/line-log.h
@@ -46,7 +46,7 @@ int line_log_filter(struct rev_info *rev); int line_log_process_ranges_arbitrary_commit(struct rev_info *rev, struct commit *commit); -int line_log_print(struct rev_info *rev, struct commit *commit); +void line_log_queue_pairs(struct rev_info *rev, struct commit *commit); void line_log_free(struct rev_info *rev);
diff --git a/list-objects-filter-options.c b/list-objects-filter-options.c index cef67e5..bc5d98f 100644 --- a/list-objects-filter-options.c +++ b/list-objects-filter-options.c
@@ -378,7 +378,7 @@ void partial_clone_register( */ return; } else { - if (upgrade_repository_format(1) < 0) + if (upgrade_repository_format(the_repository, 1) < 0) die(_("unable to upgrade repository format to support partial clone")); /* Add promisor config for the remote */
diff --git a/log-tree.c b/log-tree.c index 7e04870..88b3019 100644 --- a/log-tree.c +++ b/log-tree.c
@@ -1105,6 +1105,12 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log if (!all_need_diff && !opt->merges_need_diff) return 0; + if (opt->line_level_traverse) { + line_log_queue_pairs(opt, commit); + log_tree_diff_flush(opt); + return !opt->loginfo; + } + parse_commit_or_die(commit); oid = get_commit_tree_oid(commit); @@ -1179,10 +1185,6 @@ int log_tree_commit(struct rev_info *opt, struct commit *commit) opt->loginfo = &log; opt->diffopt.no_free = 1; - /* NEEDSWORK: no restoring of no_free? Why? */ - if (opt->line_level_traverse) - return line_log_print(opt, commit); - if (opt->track_linear && !opt->linear && !opt->reverse_output_stage) fprintf(opt->diffopt.file, "\n%s\n", opt->break_bar); shown = log_tree_diff(opt, commit, &log);
diff --git a/loose.c b/loose.c index 07333be..0b626c1 100644 --- a/loose.c +++ b/loose.c
@@ -46,38 +46,36 @@ static int insert_oid_pair(kh_oid_map_t *map, const struct object_id *key, const return 1; } -static int insert_loose_map(struct odb_source *source, +static int insert_loose_map(struct odb_source_loose *loose, const struct object_id *oid, const struct object_id *compat_oid) { - struct odb_source_files *files = odb_source_files_downcast(source); - struct loose_object_map *map = files->loose->map; + struct loose_object_map *map = loose->map; int inserted = 0; inserted |= insert_oid_pair(map->to_compat, oid, compat_oid); inserted |= insert_oid_pair(map->to_storage, compat_oid, oid); if (inserted) - oidtree_insert(files->loose->cache, compat_oid); + oidtree_insert(loose->cache, compat_oid, NULL); return inserted; } -static int load_one_loose_object_map(struct repository *repo, struct odb_source *source) +static int load_one_loose_object_map(struct repository *repo, struct odb_source_loose *loose) { - struct odb_source_files *files = odb_source_files_downcast(source); struct strbuf buf = STRBUF_INIT, path = STRBUF_INIT; FILE *fp; - if (!files->loose->map) - loose_object_map_init(&files->loose->map); - if (!files->loose->cache) { - ALLOC_ARRAY(files->loose->cache, 1); - oidtree_init(files->loose->cache); + if (!loose->map) + loose_object_map_init(&loose->map); + if (!loose->cache) { + ALLOC_ARRAY(loose->cache, 1); + oidtree_init(loose->cache); } - insert_loose_map(source, repo->hash_algo->empty_tree, repo->compat_hash_algo->empty_tree); - insert_loose_map(source, repo->hash_algo->empty_blob, repo->compat_hash_algo->empty_blob); - insert_loose_map(source, repo->hash_algo->null_oid, repo->compat_hash_algo->null_oid); + insert_loose_map(loose, repo->hash_algo->empty_tree, repo->compat_hash_algo->empty_tree); + insert_loose_map(loose, repo->hash_algo->empty_blob, repo->compat_hash_algo->empty_blob); + insert_loose_map(loose, repo->hash_algo->null_oid, repo->compat_hash_algo->null_oid); repo_common_path_replace(repo, &path, "objects/loose-object-idx"); fp = fopen(path.buf, "rb"); @@ -97,7 +95,7 @@ static int load_one_loose_object_map(struct repository *repo, struct odb_source parse_oid_hex_algop(p, &compat_oid, &p, repo->compat_hash_algo) || p != buf.buf + buf.len) goto err; - insert_loose_map(source, &oid, &compat_oid); + insert_loose_map(loose, &oid, &compat_oid); } strbuf_release(&buf); @@ -119,7 +117,8 @@ int repo_read_loose_object_map(struct repository *repo) odb_prepare_alternates(repo->objects); for (source = repo->objects->sources; source; source = source->next) { - if (load_one_loose_object_map(repo, source) < 0) { + struct odb_source_files *files = odb_source_files_downcast(source); + if (load_one_loose_object_map(repo, files->loose) < 0) { return -1; } } @@ -171,7 +170,7 @@ int repo_write_loose_object_map(struct repository *repo) return -1; } -static int write_one_object(struct odb_source *source, +static int write_one_object(struct odb_source_loose *loose, const struct object_id *oid, const struct object_id *compat_oid) { @@ -180,7 +179,7 @@ static int write_one_object(struct odb_source *source, struct stat st; struct strbuf buf = STRBUF_INIT, path = STRBUF_INIT; - strbuf_addf(&path, "%s/loose-object-idx", source->path); + strbuf_addf(&path, "%s/loose-object-idx", loose->base.path); hold_lock_file_for_update_timeout(&lock, path.buf, LOCK_DIE_ON_ERROR, -1); fd = open(path.buf, O_WRONLY | O_CREAT | O_APPEND, 0666); @@ -196,7 +195,7 @@ static int write_one_object(struct odb_source *source, goto errout; if (close(fd)) goto errout; - adjust_shared_perm(source->odb->repo, path.buf); + adjust_shared_perm(loose->base.odb->repo, path.buf); rollback_lock_file(&lock); strbuf_release(&buf); strbuf_release(&path); @@ -210,18 +209,18 @@ static int write_one_object(struct odb_source *source, return -1; } -int repo_add_loose_object_map(struct odb_source *source, +int repo_add_loose_object_map(struct odb_source_loose *loose, const struct object_id *oid, const struct object_id *compat_oid) { int inserted = 0; - if (!should_use_loose_object_map(source->odb->repo)) + if (!should_use_loose_object_map(loose->base.odb->repo)) return 0; - inserted = insert_loose_map(source, oid, compat_oid); + inserted = insert_loose_map(loose, oid, compat_oid); if (inserted) - return write_one_object(source, oid, compat_oid); + return write_one_object(loose, oid, compat_oid); return 0; }
diff --git a/loose.h b/loose.h index 6af1702..6c9b3f4 100644 --- a/loose.h +++ b/loose.h
@@ -4,7 +4,7 @@ #include "khash.h" struct repository; -struct odb_source; +struct odb_source_loose; struct loose_object_map { kh_oid_map_t *to_compat; @@ -17,7 +17,7 @@ int repo_loose_object_map_oid(struct repository *repo, const struct object_id *src, const struct git_hash_algo *dest_algo, struct object_id *dest); -int repo_add_loose_object_map(struct odb_source *source, +int repo_add_loose_object_map(struct odb_source_loose *loose, const struct object_id *oid, const struct object_id *compat_oid); int repo_read_loose_object_map(struct repository *repo);
diff --git a/merge-ort.c b/merge-ort.c index 00923ce..544be9e 100644 --- a/merge-ort.c +++ b/merge-ort.c
@@ -2954,32 +2954,6 @@ static int process_renames(struct merge_options *opt, continue; /* - * Rename caching from a previous commit might give us an - * irrelevant rename for the current commit. - * - * Imagine: - * foo/A -> bar/A - * was a cached rename for the upstream side from the - * previous commit (without the directories being renamed), - * but the next commit being replayed - * * does NOT add or delete files - * * does NOT have directory renames - * * does NOT modify any files under bar/ - * * does NOT modify foo/A - * * DOES modify other files under foo/ (otherwise the - * !oldinfo check above would have already exited for - * us) - * In such a case, our trivial directory resolution will - * have already merged bar/, and our attempt to process - * the cached - * foo/A -> bar/A - * would be counterproductive, and lack the necessary - * information anyway. Skip such renames. - */ - if (!newinfo) - continue; - - /* * diff_filepairs have copies of pathnames, thus we have to * use standard 'strcmp()' (negated) instead of '=='. */ @@ -3330,6 +3304,28 @@ static void use_cached_pairs(struct merge_options *opt, new_name = old_name; /* + * If this is a rename and the target path is either + * absent from opt->priv->paths (because a parent + * directory was trivially resolved) or already cleanly + * resolved (e.g. all three sides agree on its content), + * the cached rename is irrelevant for this commit. + * Skip it here rather than in process_renames() to + * preserve VERIFY_CI(newinfo)'s ability to catch bugs + * for non-cached renames (see 979ee83e8a90 (merge-ort: + * fix corner case recursive submodule/directory conflict + * handling, 2025-12-29) for an example of a bug that + * assertion caught). The rename remains in cached_pairs + * for use in subsequent commits. + */ + if (entry->value) { + struct merged_info *mi; + + mi = strmap_get(&opt->priv->paths, new_name); + if (!mi || mi->clean) + continue; + } + + /* * cached_pairs has *copies* of old_name and new_name, * because it has to persist across merges. Since * pool_alloc_filespec() will just re-use the existing
diff --git a/meson.build b/meson.build index 1148862..3247697 100644 --- a/meson.build +++ b/meson.build
@@ -404,7 +404,10 @@ 'odb.c', 'odb/source.c', 'odb/source-files.c', + 'odb/source-inmemory.c', + 'odb/source-loose.c', 'odb/streaming.c', + 'odb/transaction.c', 'oid-array.c', 'oidmap.c', 'oidset.c', @@ -563,6 +566,18 @@ env: script_environment, ) +libgit_sources += custom_target( + input: 'Documentation/githooks.adoc', + output: 'hook-list.h', + command: [ + shell, + meson.current_source_dir() + '/tools/generate-hooklist.sh', + meson.current_source_dir(), + '@OUTPUT@', + ], + env: script_environment, +) + builtin_sources = [ 'builtin/add.c', 'builtin/am.c', @@ -686,6 +701,7 @@ 'builtin/update-server-info.c', 'builtin/upload-archive.c', 'builtin/upload-pack.c', + 'builtin/url-parse.c', 'builtin/var.c', 'builtin/verify-commit.c', 'builtin/verify-pack.c', @@ -698,7 +714,6 @@ ':!contrib', ':!compat/inet_ntop.c', ':!compat/inet_pton.c', - ':!compat/nedmalloc', ':!compat/obstack.*', ':!compat/poll', ':!compat/regex', @@ -739,18 +754,6 @@ env: script_environment, ) -builtin_sources += custom_target( - input: 'Documentation/githooks.adoc', - output: 'hook-list.h', - command: [ - shell, - meson.current_source_dir() + '/tools/generate-hooklist.sh', - meson.current_source_dir(), - '@OUTPUT@', - ], - env: script_environment, -) - # This contains the variables for GIT-BUILD-OPTIONS, which we use to propagate # build options to our tests. build_options_config = configuration_data() @@ -866,6 +869,12 @@ libgit_c_args += cflag endif endforeach + + # Clang generates warnings when compiling glibc 2.43 because of the use of + # _Generic. + if compiler.get_id() == 'clang' + libgit_c_args += '-Wno-c11-extensions' + endif endif if get_option('breaking_changes') @@ -1285,7 +1294,6 @@ 'compat/win32/pthread.c', 'compat/win32/syslog.c', 'compat/win32mmap.c', - 'compat/nedmalloc/nedmalloc.c', ] libgit_c_args += [ @@ -1344,10 +1352,17 @@ endif fsmonitor_backend = '' +fsmonitor_os = '' if host_machine.system() == 'windows' fsmonitor_backend = 'win32' + fsmonitor_os = 'win32' +elif host_machine.system() == 'linux' and threads.found() and compiler.has_header('linux/magic.h') + fsmonitor_backend = 'linux' + fsmonitor_os = 'unix' + libgit_c_args += '-DHAVE_LINUX_MAGIC_H' elif host_machine.system() == 'darwin' fsmonitor_backend = 'darwin' + fsmonitor_os = 'unix' libgit_dependencies += dependency('CoreServices') endif if fsmonitor_backend != '' @@ -1356,14 +1371,14 @@ compat_sources += [ 'compat/fsmonitor/fsm-health-' + fsmonitor_backend + '.c', - 'compat/fsmonitor/fsm-ipc-' + fsmonitor_backend + '.c', + 'compat/fsmonitor/fsm-ipc-' + fsmonitor_os + '.c', 'compat/fsmonitor/fsm-listen-' + fsmonitor_backend + '.c', 'compat/fsmonitor/fsm-path-utils-' + fsmonitor_backend + '.c', - 'compat/fsmonitor/fsm-settings-' + fsmonitor_backend + '.c', + 'compat/fsmonitor/fsm-settings-' + fsmonitor_os + '.c', ] endif build_options_config.set_quoted('FSMONITOR_DAEMON_BACKEND', fsmonitor_backend) -build_options_config.set_quoted('FSMONITOR_OS_SETTINGS', fsmonitor_backend) +build_options_config.set_quoted('FSMONITOR_OS_SETTINGS', fsmonitor_os) if not get_option('b_sanitize').contains('address') and get_option('regex').allowed() and compiler.has_header('regex.h') and compiler.get_define('REG_STARTEND', prefix: '#include <regex.h>') != '' build_options_config.set('NO_REGEX', '') @@ -1744,8 +1759,7 @@ ) libgit_sources += version_def_h -cargo = find_program('cargo', dirs: program_path, native: true, required: get_option('rust')) -rust_option = get_option('rust').disable_auto_if(not cargo.found()) +rust_option = get_option('rust') if rust_option.allowed() subdir('src') libgit_c_args += '-DWITH_RUST'
diff --git a/meson_options.txt b/meson_options.txt index 659cbb2..d936ada 100644 --- a/meson_options.txt +++ b/meson_options.txt
@@ -77,7 +77,7 @@ # Build tweaks. option('breaking_changes', type: 'boolean', value: false, description: 'Enable upcoming breaking changes.') -option('rust', type: 'feature', value: 'auto', +option('rust', type: 'feature', value: 'enabled', description: 'Enable building with Rust.') option('macos_use_homebrew_gettext', type: 'boolean', value: true, description: 'Use gettext from Homebrew instead of the slightly-broken system-provided one.') @@ -106,7 +106,7 @@ # Documentation. option('docs', type: 'array', choices: ['man', 'html'], value: [], - description: 'Which documenattion formats to build and install.') + description: 'Which documentation formats to build and install.') option('default_help_format', type: 'combo', choices: ['man', 'html', 'platform'], value: 'platform', description: 'Default format used when executing git-help(1).') option('docs_backend', type: 'combo', choices: ['asciidoc', 'asciidoctor', 'auto'], value: 'auto',
diff --git a/midx-write.c b/midx-write.c index a25cab7..19e1cd1 100644 --- a/midx-write.c +++ b/midx-write.c
@@ -29,8 +29,7 @@ extern void clear_midx_files_ext(struct odb_source *source, const char *ext, const char *keep_hash); extern void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext, - const char **keep_hashes, - uint32_t hashes_nr); + const struct strvec *keep_hashes); extern int cmp_idx_or_pack_name(const char *idx_or_pack_name, const char *idx_name); @@ -1109,8 +1108,7 @@ static int link_midx_to_chain(struct multi_pack_index *m) } static void clear_midx_files(struct odb_source *source, - const char **hashes, uint32_t hashes_nr, - unsigned incremental) + const struct strvec *hashes, unsigned incremental) { /* * if incremental: @@ -1124,13 +1122,15 @@ static void clear_midx_files(struct odb_source *source, */ struct strbuf buf = STRBUF_INIT; const char *exts[] = { MIDX_EXT_BITMAP, MIDX_EXT_REV, MIDX_EXT_MIDX }; - uint32_t i, j; + uint32_t i; for (i = 0; i < ARRAY_SIZE(exts); i++) { - clear_incremental_midx_files_ext(source, exts[i], - hashes, hashes_nr); - for (j = 0; j < hashes_nr; j++) - clear_midx_files_ext(source, exts[i], hashes[j]); + clear_incremental_midx_files_ext(source, exts[i], hashes); + if (hashes) { + for (size_t j = 0; j < hashes->nr; j++) + clear_midx_files_ext(source, exts[i], + hashes->v[j]); + } } if (incremental) @@ -1152,7 +1152,7 @@ static bool midx_needs_update(struct multi_pack_index *midx, struct write_midx_c /* * Ensure that we have a valid checksum before consulting the - * exisiting MIDX in order to determine if we can avoid an + * existing MIDX in order to determine if we can avoid an * update. * * This is necessary because the given MIDX is loaded directly @@ -1208,14 +1208,16 @@ static bool midx_needs_update(struct multi_pack_index *midx, struct write_midx_c BUG("same pack added twice?"); } - for (uint32_t i = 0; i < ctx->nr; i++) { - strbuf_reset(&buf); - strbuf_addstr(&buf, midx->pack_names[i]); - strbuf_strip_suffix(&buf, ".idx"); + for (struct multi_pack_index *m = midx; m; m = m->base_midx) { + for (uint32_t i = 0; i < m->num_packs; i++) { + strbuf_reset(&buf); + strbuf_addstr(&buf, m->pack_names[i]); + strbuf_strip_suffix(&buf, ".idx"); - if (!strset_contains(&packs, buf.buf)) - goto out; - strset_remove(&packs, buf.buf); + if (!strset_contains(&packs, buf.buf)) + goto out; + strset_remove(&packs, buf.buf); + } } needed = false; @@ -1245,6 +1247,7 @@ struct write_midx_opts { const char *preferred_pack_name; const char *refs_snapshot; + const char *incremental_base; unsigned flags; }; @@ -1255,7 +1258,7 @@ static int write_midx_internal(struct write_midx_opts *opts) unsigned char midx_hash[GIT_MAX_RAWSZ]; uint32_t start_pack; struct hashfile *f = NULL; - struct lock_file lk; + struct lock_file lk = LOCK_INIT; struct tempfile *incr; struct write_midx_context ctx = { .preferred_pack_idx = NO_PREFERRED_PACK, @@ -1265,8 +1268,7 @@ static int write_midx_internal(struct write_midx_opts *opts) int pack_name_concat_len = 0; int dropped_packs = 0; int result = -1; - const char **keep_hashes = NULL; - size_t keep_hashes_nr = 0; + struct strvec keep_hashes = STRVEC_INIT; struct chunkfile *cf; trace2_region_enter("midx", "write_midx_internal", r); @@ -1329,11 +1331,32 @@ static int write_midx_internal(struct write_midx_opts *opts) /* * If compacting MIDX layer(s) in the range [from, to], then the - * compacted MIDX will share the same base MIDX as 'from'. + * compacted MIDX will share the same base MIDX as 'from', + * unless a custom --base is specified (see below). */ if (ctx.compact) ctx.base_midx = ctx.compact_from->base_midx; + if (opts->incremental_base) { + if (!strcmp(opts->incremental_base, "none")) { + ctx.base_midx = NULL; + } else { + while (ctx.base_midx) { + const char *cmp = midx_get_checksum_hex(ctx.base_midx); + if (!strcmp(opts->incremental_base, cmp)) + break; + + ctx.base_midx = ctx.base_midx->base_midx; + } + + if (!ctx.base_midx) { + error(_("could not find base MIDX '%s'"), + opts->incremental_base); + goto cleanup; + } + } + } + ctx.nr = 0; ctx.alloc = ctx.m ? ctx.m->num_packs + ctx.m->num_packs_in_base : 16; ctx.info = NULL; @@ -1438,7 +1461,7 @@ static int write_midx_internal(struct write_midx_opts *opts) /* * Attempt opening the pack index to populate num_objects. - * Ignore failiures as they can be expected and are not + * Ignore failures as they can be expected and are not * fatal during this selection time. */ open_pack_index(oldest); @@ -1600,11 +1623,14 @@ static int write_midx_internal(struct write_midx_opts *opts) } if (ctx.incremental) { - struct strbuf lock_name = STRBUF_INIT; + if (!(opts->flags & MIDX_WRITE_NO_CHAIN)) { + struct strbuf lock_name = STRBUF_INIT; - get_midx_chain_filename(opts->source, &lock_name); - hold_lock_file_for_update(&lk, lock_name.buf, LOCK_DIE_ON_ERROR); - strbuf_release(&lock_name); + get_midx_chain_filename(opts->source, &lock_name); + hold_lock_file_for_update(&lk, lock_name.buf, + LOCK_DIE_ON_ERROR); + strbuf_release(&lock_name); + } incr = mks_tempfile_m(midx_name.buf, 0444); if (!incr) { @@ -1706,33 +1732,23 @@ static int write_midx_internal(struct write_midx_opts *opts) if (ctx.num_multi_pack_indexes_before == UINT32_MAX) die(_("too many multi-pack-indexes")); - if (ctx.compact) { - struct multi_pack_index *m; - - /* - * Keep all MIDX layers excluding those in the range [from, to]. - */ - for (m = ctx.base_midx; m; m = m->base_midx) - keep_hashes_nr++; - for (m = ctx.m; - m && midx_hashcmp(m, ctx.compact_to, r->hash_algo); - m = m->base_midx) - keep_hashes_nr++; - - keep_hashes_nr++; /* include the compacted layer */ - } else { - keep_hashes_nr = ctx.num_multi_pack_indexes_before + 1; - } - CALLOC_ARRAY(keep_hashes, keep_hashes_nr); + if (!is_lock_file_locked(&lk)) + printf("%s\n", hash_to_hex_algop(midx_hash, r->hash_algo)); + else if (opts->flags & MIDX_WRITE_NO_CHAIN) + BUG("lockfile held with MIDX_WRITE_NO_CHAIN set?"); if (ctx.incremental) { - FILE *chainf = fdopen_lock_file(&lk, "w"); struct strbuf final_midx_name = STRBUF_INIT; struct multi_pack_index *m = ctx.base_midx; + struct multi_pack_index **layers = NULL; + size_t layers_nr = 0, layers_alloc = 0; - if (!chainf) { - error_errno(_("unable to open multi-pack-index chain file")); - goto cleanup; + if (is_lock_file_locked(&lk)){ + FILE *chainf = fdopen_lock_file(&lk, "w"); + if (!chainf) { + error_errno(_("unable to open multi-pack-index chain file")); + goto cleanup; + } } if (link_midx_to_chain(ctx.base_midx) < 0) @@ -1749,60 +1765,64 @@ static int write_midx_internal(struct write_midx_opts *opts) strbuf_release(&final_midx_name); if (ctx.compact) { - struct multi_pack_index *m; - uint32_t num_layers_before_from = 0; - uint32_t i; + struct multi_pack_index *mp; - for (m = ctx.base_midx; m; m = m->base_midx) - num_layers_before_from++; - - m = ctx.base_midx; - for (i = 0; i < num_layers_before_from; i++) { - uint32_t j = num_layers_before_from - i - 1; - - keep_hashes[j] = xstrdup(midx_get_checksum_hex(m)); - m = m->base_midx; + for (mp = ctx.base_midx; mp; mp = mp->base_midx) { + ALLOC_GROW(layers, layers_nr + 1, layers_alloc); + layers[layers_nr++] = mp; } + while (layers_nr) + strvec_push(&keep_hashes, + midx_get_checksum_hex(layers[--layers_nr])); - keep_hashes[i] = xstrdup(hash_to_hex_algop(midx_hash, - r->hash_algo)); + strvec_push(&keep_hashes, + hash_to_hex_algop(midx_hash, + r->hash_algo)); - i = 0; - for (m = ctx.m; - m && midx_hashcmp(m, ctx.compact_to, r->hash_algo); - m = m->base_midx) { - keep_hashes[keep_hashes_nr - i - 1] = - xstrdup(midx_get_checksum_hex(m)); - i++; + for (mp = ctx.m; + mp && midx_hashcmp(mp, ctx.compact_to, + r->hash_algo); + mp = mp->base_midx) { + ALLOC_GROW(layers, layers_nr + 1, layers_alloc); + layers[layers_nr++] = mp; } + while (layers_nr) + strvec_push(&keep_hashes, + midx_get_checksum_hex(layers[--layers_nr])); } else { - keep_hashes[ctx.num_multi_pack_indexes_before] = - xstrdup(hash_to_hex_algop(midx_hash, - r->hash_algo)); - - for (uint32_t i = 0; i < ctx.num_multi_pack_indexes_before; i++) { - uint32_t j = ctx.num_multi_pack_indexes_before - i - 1; - - keep_hashes[j] = xstrdup(midx_get_checksum_hex(m)); - m = m->base_midx; + for (; m; m = m->base_midx) { + ALLOC_GROW(layers, layers_nr + 1, layers_alloc); + layers[layers_nr++] = m; } + while (layers_nr) + strvec_push(&keep_hashes, + midx_get_checksum_hex(layers[--layers_nr])); + + strvec_push(&keep_hashes, + hash_to_hex_algop(midx_hash, + r->hash_algo)); } - for (uint32_t i = 0; i < keep_hashes_nr; i++) - fprintf(get_lock_file_fp(&lk), "%s\n", keep_hashes[i]); + free(layers); + + if (is_lock_file_locked(&lk)) + for (size_t i = 0; i < keep_hashes.nr; i++) + fprintf(get_lock_file_fp(&lk), "%s\n", + keep_hashes.v[i]); } else { - keep_hashes[ctx.num_multi_pack_indexes_before] = - xstrdup(hash_to_hex_algop(midx_hash, r->hash_algo)); + strvec_push(&keep_hashes, + hash_to_hex_algop(midx_hash, r->hash_algo)); } if (ctx.m || ctx.base_midx) odb_close(ctx.repo->objects); - if (commit_lock_file(&lk) < 0) - die_errno(_("could not write multi-pack-index")); + if (is_lock_file_locked(&lk)) { + if (commit_lock_file(&lk) < 0) + die_errno(_("could not write multi-pack-index")); - clear_midx_files(opts->source, keep_hashes, keep_hashes_nr, - ctx.incremental); + clear_midx_files(opts->source, &keep_hashes, ctx.incremental); + } result = 0; cleanup: @@ -1818,11 +1838,7 @@ static int write_midx_internal(struct write_midx_opts *opts) free(ctx.entries); free(ctx.pack_perm); free(ctx.pack_order); - if (keep_hashes) { - for (uint32_t i = 0; i < keep_hashes_nr; i++) - free((char *)keep_hashes[i]); - free(keep_hashes); - } + strvec_clear(&keep_hashes); strbuf_release(&midx_name); close_midx(midx_to_free); @@ -1833,7 +1849,8 @@ static int write_midx_internal(struct write_midx_opts *opts) int write_midx_file(struct odb_source *source, const char *preferred_pack_name, - const char *refs_snapshot, unsigned flags) + const char *refs_snapshot, + unsigned flags) { struct write_midx_opts opts = { .source = source, @@ -1848,13 +1865,16 @@ int write_midx_file(struct odb_source *source, int write_midx_file_only(struct odb_source *source, struct string_list *packs_to_include, const char *preferred_pack_name, - const char *refs_snapshot, unsigned flags) + const char *refs_snapshot, + const char *incremental_base, + unsigned flags) { struct write_midx_opts opts = { .source = source, .packs_to_include = packs_to_include, .preferred_pack_name = preferred_pack_name, .refs_snapshot = refs_snapshot, + .incremental_base = incremental_base, .flags = flags, }; @@ -1864,12 +1884,14 @@ int write_midx_file_only(struct odb_source *source, int write_midx_file_compact(struct odb_source *source, struct multi_pack_index *from, struct multi_pack_index *to, + const char *incremental_base, unsigned flags) { struct write_midx_opts opts = { .source = source, .compact_from = from, .compact_to = to, + .incremental_base = incremental_base, .flags = flags | MIDX_WRITE_COMPACT, };
diff --git a/midx.c b/midx.c index 81d6ab1..efbfbb1 100644 --- a/midx.c +++ b/midx.c
@@ -12,6 +12,7 @@ #include "chunk-format.h" #include "pack-bitmap.h" #include "pack-revindex.h" +#include "strvec.h" #define MIDX_PACK_ERROR ((void *)(intptr_t)-1) @@ -19,8 +20,7 @@ int midx_checksum_valid(struct multi_pack_index *m); void clear_midx_files_ext(struct odb_source *source, const char *ext, const char *keep_hash); void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext, - char **keep_hashes, - uint32_t hashes_nr); + const struct strvec *keep_hashes); int cmp_idx_or_pack_name(const char *idx_or_pack_name, const char *idx_name); @@ -667,8 +667,8 @@ static int midx_pack_names_cmp(const void *a, const void *b, void *m_) m->pack_names[*(const size_t *)b]); } -static int midx_contains_pack_1(struct multi_pack_index *m, - const char *idx_or_pack_name) +int midx_layer_contains_pack(struct multi_pack_index *m, + const char *idx_or_pack_name) { uint32_t first = 0, last = m->num_packs; @@ -709,7 +709,7 @@ static int midx_contains_pack_1(struct multi_pack_index *m, int midx_contains_pack(struct multi_pack_index *m, const char *idx_or_pack_name) { for (; m; m = m->base_midx) - if (midx_contains_pack_1(m, idx_or_pack_name)) + if (midx_layer_contains_pack(m, idx_or_pack_name)) return 1; return 0; } @@ -758,8 +758,7 @@ int midx_checksum_valid(struct multi_pack_index *m) } struct clear_midx_data { - char **keep; - uint32_t keep_nr; + struct strset keep; const char *ext; }; @@ -767,15 +766,12 @@ static void clear_midx_file_ext(const char *full_path, size_t full_path_len UNUS const char *file_name, void *_data) { struct clear_midx_data *data = _data; - uint32_t i; if (!(starts_with(file_name, "multi-pack-index-") && ends_with(file_name, data->ext))) return; - for (i = 0; i < data->keep_nr; i++) { - if (!strcmp(data->keep[i], file_name)) - return; - } + if (strset_contains(&data->keep, file_name)) + return; if (unlink(full_path)) die_errno(_("failed to remove %s"), full_path); } @@ -783,48 +779,49 @@ static void clear_midx_file_ext(const char *full_path, size_t full_path_len UNUS void clear_midx_files_ext(struct odb_source *source, const char *ext, const char *keep_hash) { - struct clear_midx_data data; - memset(&data, 0, sizeof(struct clear_midx_data)); + struct clear_midx_data data = { + .keep = STRSET_INIT, + .ext = ext, + }; if (keep_hash) { - ALLOC_ARRAY(data.keep, 1); + struct strbuf buf = STRBUF_INIT; + strbuf_addf(&buf, "multi-pack-index-%s.%s", keep_hash, ext); - data.keep[0] = xstrfmt("multi-pack-index-%s.%s", keep_hash, ext); - data.keep_nr = 1; + strset_add(&data.keep, buf.buf); + + strbuf_release(&buf); } - data.ext = ext; - for_each_file_in_pack_dir(source->path, - clear_midx_file_ext, - &data); + for_each_file_in_pack_dir(source->path, clear_midx_file_ext, &data); - if (keep_hash) - free(data.keep[0]); - free(data.keep); + strset_clear(&data.keep); } void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext, - char **keep_hashes, - uint32_t hashes_nr) + const struct strvec *keep_hashes) { - struct clear_midx_data data; - uint32_t i; + struct clear_midx_data data = { + .keep = STRSET_INIT, + .ext = ext, + }; + struct strbuf buf = STRBUF_INIT; - memset(&data, 0, sizeof(struct clear_midx_data)); + if (keep_hashes) { + for (size_t i = 0; i < keep_hashes->nr; i++) { + strbuf_reset(&buf); + strbuf_addf(&buf, "multi-pack-index-%s.%s", + keep_hashes->v[i], ext); - ALLOC_ARRAY(data.keep, hashes_nr); - for (i = 0; i < hashes_nr; i++) - data.keep[i] = xstrfmt("multi-pack-index-%s.%s", keep_hashes[i], - ext); - data.keep_nr = hashes_nr; - data.ext = ext; + strset_add(&data.keep, buf.buf); + } + } for_each_file_in_pack_subdir(source->path, "multi-pack-index.d", clear_midx_file_ext, &data); - for (i = 0; i < hashes_nr; i++) - free(data.keep[i]); - free(data.keep); + strbuf_release(&buf); + strset_clear(&data.keep); } void clear_midx_file(struct repository *r) @@ -853,6 +850,35 @@ void clear_midx_file(struct repository *r) strbuf_release(&midx); } +void clear_incremental_midx_files(struct repository *r, + const struct strvec *keep_hashes) +{ + struct odb_source *source = r->objects->sources; + struct strbuf chain = STRBUF_INIT; + + get_midx_chain_filename(source, &chain); + + for (; source; source = source->next) { + struct odb_source_files *files = odb_source_files_downcast(source); + if (files->packed->midx) + close_midx(files->packed->midx); + files->packed->midx = NULL; + } + + if (!keep_hashes && remove_path(chain.buf)) + die(_("failed to clear multi-pack-index chain at %s"), + chain.buf); + + clear_incremental_midx_files_ext(r->objects->sources, MIDX_EXT_BITMAP, + keep_hashes); + clear_incremental_midx_files_ext(r->objects->sources, MIDX_EXT_REV, + keep_hashes); + clear_incremental_midx_files_ext(r->objects->sources, MIDX_EXT_MIDX, + keep_hashes); + + strbuf_release(&chain); +} + static int verify_midx_error; __attribute__((format (printf, 1, 2)))
diff --git a/midx.h b/midx.h index 08f3728..63853a0 100644 --- a/midx.h +++ b/midx.h
@@ -9,6 +9,7 @@ struct repository; struct bitmapped_pack; struct git_hash_algo; struct odb_source; +struct strvec; #define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */ #define MIDX_VERSION_V1 1 @@ -83,6 +84,7 @@ struct multi_pack_index { #define MIDX_WRITE_BITMAP_LOOKUP_TABLE (1 << 4) #define MIDX_WRITE_INCREMENTAL (1 << 5) #define MIDX_WRITE_COMPACT (1 << 6) +#define MIDX_WRITE_NO_CHAIN (1 << 7) #define MIDX_EXT_REV "rev" #define MIDX_EXT_BITMAP "bitmap" @@ -118,6 +120,8 @@ struct object_id *nth_midxed_object_oid(struct object_id *oid, int fill_midx_entry(struct multi_pack_index *m, const struct object_id *oid, struct pack_entry *e); int midx_contains_pack(struct multi_pack_index *m, const char *idx_or_pack_name); +int midx_layer_contains_pack(struct multi_pack_index *m, + const char *idx_or_pack_name); int midx_preferred_pack(struct multi_pack_index *m, uint32_t *pack_int_id); int prepare_multi_pack_index_one(struct odb_source *source); @@ -131,12 +135,17 @@ int write_midx_file(struct odb_source *source, int write_midx_file_only(struct odb_source *source, struct string_list *packs_to_include, const char *preferred_pack_name, - const char *refs_snapshot, unsigned flags); + const char *refs_snapshot, + const char *incremental_base, + unsigned flags); int write_midx_file_compact(struct odb_source *source, struct multi_pack_index *from, struct multi_pack_index *to, + const char *incremental_base, unsigned flags); void clear_midx_file(struct repository *r); +void clear_incremental_midx_files(struct repository *r, + const struct strvec *keep_hashes); int verify_midx_file(struct odb_source *source, unsigned flags); int expire_midx_packs(struct odb_source *source, unsigned flags); int midx_repack(struct odb_source *source, size_t batch_size, unsigned flags);
diff --git a/negotiator/default.c b/negotiator/default.c index 3cac047..78d58d5 100644 --- a/negotiator/default.c +++ b/negotiator/default.c
@@ -175,6 +175,13 @@ static int ack(struct fetch_negotiator *n, struct commit *c) return known_to_be_common; } +static void have_sent(struct fetch_negotiator *n, struct commit *c) +{ + if (repo_parse_commit(the_repository, c)) + return; + mark_common(n->data, c, 0, 0); +} + static void release(struct fetch_negotiator *n) { clear_prio_queue(&((struct negotiation_state *)n->data)->rev_list); @@ -188,6 +195,7 @@ void default_negotiator_init(struct fetch_negotiator *negotiator) negotiator->add_tip = add_tip; negotiator->next = next; negotiator->ack = ack; + negotiator->have_sent = have_sent; negotiator->release = release; negotiator->data = CALLOC_ARRAY(ns, 1); ns->rev_list.compare = compare_commits_by_commit_date;
diff --git a/negotiator/noop.c b/negotiator/noop.c index 65e3c20..edf1b45 100644 --- a/negotiator/noop.c +++ b/negotiator/noop.c
@@ -29,6 +29,12 @@ static int ack(struct fetch_negotiator *n UNUSED, struct commit *c UNUSED) return 0; } +static void have_sent(struct fetch_negotiator *n UNUSED, + struct commit *c UNUSED) +{ + /* nothing to do */ +} + static void release(struct fetch_negotiator *n UNUSED) { /* nothing to release */ @@ -40,6 +46,7 @@ void noop_negotiator_init(struct fetch_negotiator *negotiator) negotiator->add_tip = add_tip; negotiator->next = next; negotiator->ack = ack; + negotiator->have_sent = have_sent; negotiator->release = release; negotiator->data = NULL; }
diff --git a/negotiator/skipping.c b/negotiator/skipping.c index fe4126c..68c9b3b 100644 --- a/negotiator/skipping.c +++ b/negotiator/skipping.c
@@ -243,6 +243,13 @@ static int ack(struct fetch_negotiator *n, struct commit *c) return known_to_be_common; } +static void have_sent(struct fetch_negotiator *n, struct commit *c) +{ + if (repo_parse_commit(the_repository, c)) + return; + mark_common(n->data, c); +} + static void release(struct fetch_negotiator *n) { struct data *data = n->data; @@ -259,6 +266,7 @@ void skipping_negotiator_init(struct fetch_negotiator *negotiator) negotiator->add_tip = add_tip; negotiator->next = next; negotiator->ack = ack; + negotiator->have_sent = have_sent; negotiator->release = release; negotiator->data = CALLOC_ARRAY(data, 1); data->rev_list.compare = compare;
diff --git a/object-file.c b/object-file.c index 0be2981..9afa842 100644 --- a/object-file.c +++ b/object-file.c
@@ -21,7 +21,7 @@ #include "object-file.h" #include "odb.h" #include "odb/streaming.h" -#include "oidtree.h" +#include "odb/transaction.h" #include "pack.h" #include "packfile.h" #include "path.h" @@ -30,12 +30,6 @@ #include "tempfile.h" #include "tmp-objdir.h" -/* The maximum size for an object header. */ -#define MAX_HEADER_LEN 32 - -static struct oidtree *odb_source_loose_cache(struct odb_source *source, - const struct object_id *oid); - static int get_conv_flags(unsigned flags) { if (flags & INDEX_RENORMALIZE) @@ -60,14 +54,14 @@ static void fill_loose_path(struct strbuf *buf, } } -const char *odb_loose_path(struct odb_source *source, +const char *odb_loose_path(struct odb_source_loose *loose, struct strbuf *buf, const struct object_id *oid) { strbuf_reset(buf); - strbuf_addstr(buf, source->path); + strbuf_addstr(buf, loose->base.path); strbuf_addch(buf, '/'); - fill_loose_path(buf, oid, source->odb->repo->hash_algo); + fill_loose_path(buf, oid, loose->base.odb->repo->hash_algo); return buf->buf; } @@ -93,21 +87,6 @@ int check_and_freshen_file(const char *fn, int freshen) return 1; } -static int check_and_freshen_source(struct odb_source *source, - const struct object_id *oid, - int freshen) -{ - static struct strbuf path = STRBUF_INIT; - odb_loose_path(source, &path, oid); - return check_and_freshen_file(path.buf, freshen); -} - -int odb_source_loose_has_object(struct odb_source *source, - const struct object_id *oid) -{ - return check_and_freshen_source(source, oid, 0); -} - int format_object_header(char *str, size_t size, enum object_type type, size_t objsize) { @@ -164,34 +143,6 @@ int stream_object_signature(struct repository *r, } /* - * Find "oid" as a loose object in given source, open the object and return its - * file descriptor. Returns the file descriptor on success, negative on failure. - * - * The "path" out-parameter will give the path of the object we found (if any). - * Note that it may point to static storage and is only valid until another - * call to stat_loose_object(). - */ -static int open_loose_object(struct odb_source_loose *loose, - const struct object_id *oid, const char **path) -{ - static struct strbuf buf = STRBUF_INIT; - int fd; - - *path = odb_loose_path(loose->source, &buf, oid); - fd = git_open(*path); - if (fd >= 0) - return fd; - - return -1; -} - -static int quick_has_loose(struct odb_source_loose *loose, - const struct object_id *oid) -{ - return !!oidtree_contains(odb_source_loose_cache(loose->source, oid), oid); -} - -/* * Map and close the given loose object fd. The path argument is used for * error reporting. */ @@ -214,42 +165,11 @@ static void *map_fd(int fd, const char *path, unsigned long *size) return map; } -static void *odb_source_loose_map_object(struct odb_source *source, - const struct object_id *oid, - unsigned long *size) -{ - struct odb_source_files *files = odb_source_files_downcast(source); - const char *p; - int fd = open_loose_object(files->loose, oid, &p); - - if (fd < 0) - return NULL; - return map_fd(fd, p, size); -} - -enum unpack_loose_header_result { - ULHR_OK, - ULHR_BAD, - ULHR_TOO_LONG, -}; - -/** - * unpack_loose_header() initializes the data stream needed to unpack - * a loose object header. - * - * Returns: - * - * - ULHR_OK on success - * - ULHR_BAD on error - * - ULHR_TOO_LONG if the header was too long - * - * It will only parse up to MAX_HEADER_LEN bytes. - */ -static enum unpack_loose_header_result unpack_loose_header(git_zstream *stream, - unsigned char *map, - unsigned long mapsize, - void *buffer, - unsigned long bufsiz) +enum unpack_loose_header_result unpack_loose_header(git_zstream *stream, + unsigned char *map, + unsigned long mapsize, + void *buffer, + unsigned long bufsiz) { int status; @@ -279,9 +199,9 @@ static enum unpack_loose_header_result unpack_loose_header(git_zstream *stream, return ULHR_TOO_LONG; } -static void *unpack_loose_rest(git_zstream *stream, - void *buffer, unsigned long size, - const struct object_id *oid) +void *unpack_loose_rest(git_zstream *stream, + void *buffer, unsigned long size, + const struct object_id *oid) { size_t bytes = strlen(buffer) + 1, n; unsigned char *buf = xmallocz(size); @@ -339,7 +259,7 @@ static void *unpack_loose_rest(git_zstream *stream, * too permissive for what we want to check. So do an anal * object header parse by hand. */ -static int parse_loose_header(const char *hdr, struct object_info *oi) +int parse_loose_header(const char *hdr, struct object_info *oi) { const char *type_buf = hdr; size_t size; @@ -395,170 +315,6 @@ static int parse_loose_header(const char *hdr, struct object_info *oi) return 0; } -static int read_object_info_from_path(struct odb_source *source, - const char *path, - const struct object_id *oid, - struct object_info *oi, - enum object_info_flags flags) -{ - struct odb_source_files *files = odb_source_files_downcast(source); - int ret; - int fd; - unsigned long mapsize; - void *map = NULL; - git_zstream stream, *stream_to_end = NULL; - char hdr[MAX_HEADER_LEN]; - unsigned long size_scratch; - enum object_type type_scratch; - struct stat st; - - /* - * If we don't care about type or size, then we don't - * need to look inside the object at all. Note that we - * do not optimize out the stat call, even if the - * caller doesn't care about the disk-size, since our - * return value implicitly indicates whether the - * object even exists. - */ - if (!oi || (!oi->typep && !oi->sizep && !oi->contentp)) { - struct stat st; - - if ((!oi || (!oi->disk_sizep && !oi->mtimep)) && (flags & OBJECT_INFO_QUICK)) { - ret = quick_has_loose(files->loose, oid) ? 0 : -1; - goto out; - } - - if (lstat(path, &st) < 0) { - ret = -1; - goto out; - } - - if (oi) { - if (oi->disk_sizep) - *oi->disk_sizep = st.st_size; - if (oi->mtimep) - *oi->mtimep = st.st_mtime; - } - - ret = 0; - goto out; - } - - fd = git_open(path); - if (fd < 0) { - if (errno != ENOENT) - error_errno(_("unable to open loose object %s"), oid_to_hex(oid)); - ret = -1; - goto out; - } - - if (fstat(fd, &st)) { - close(fd); - ret = -1; - goto out; - } - - mapsize = xsize_t(st.st_size); - if (!mapsize) { - close(fd); - ret = error(_("object file %s is empty"), path); - goto out; - } - - map = xmmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0); - close(fd); - if (!map) { - ret = -1; - goto out; - } - - if (oi->disk_sizep) - *oi->disk_sizep = mapsize; - if (oi->mtimep) - *oi->mtimep = st.st_mtime; - - stream_to_end = &stream; - - switch (unpack_loose_header(&stream, map, mapsize, hdr, sizeof(hdr))) { - case ULHR_OK: - if (!oi->sizep) - oi->sizep = &size_scratch; - if (!oi->typep) - oi->typep = &type_scratch; - - if (parse_loose_header(hdr, oi) < 0) { - ret = error(_("unable to parse %s header"), oid_to_hex(oid)); - goto corrupt; - } - - if (*oi->typep < 0) - die(_("invalid object type")); - - if (oi->contentp) { - *oi->contentp = unpack_loose_rest(&stream, hdr, *oi->sizep, oid); - if (!*oi->contentp) { - ret = -1; - goto corrupt; - } - } - - break; - case ULHR_BAD: - ret = error(_("unable to unpack %s header"), - oid_to_hex(oid)); - goto corrupt; - case ULHR_TOO_LONG: - ret = error(_("header for %s too long, exceeds %d bytes"), - oid_to_hex(oid), MAX_HEADER_LEN); - goto corrupt; - } - - ret = 0; - -corrupt: - if (ret && (flags & OBJECT_INFO_DIE_IF_CORRUPT)) - die(_("loose object %s (stored in %s) is corrupt"), - oid_to_hex(oid), path); - -out: - if (stream_to_end) - git_inflate_end(stream_to_end); - if (map) - munmap(map, mapsize); - if (oi) { - if (oi->sizep == &size_scratch) - oi->sizep = NULL; - if (oi->typep == &type_scratch) - oi->typep = NULL; - if (oi->delta_base_oid) - oidclr(oi->delta_base_oid, source->odb->repo->hash_algo); - if (!ret) - oi->whence = OI_LOOSE; - } - - return ret; -} - -int odb_source_loose_read_object_info(struct odb_source *source, - const struct object_id *oid, - struct object_info *oi, - enum object_info_flags flags) -{ - static struct strbuf buf = STRBUF_INIT; - - /* - * The second read shouldn't cause new loose objects to show up, unless - * there was a race condition with a secondary process. We don't care - * about this case though, so we simply skip reading loose objects a - * second time. - */ - if (flags & OBJECT_INFO_SECOND_READ) - return -1; - - odb_loose_path(source, &buf, oid); - return read_object_info_from_path(source, buf.buf, oid, oi, flags); -} - static void hash_object_body(const struct git_hash_algo *algo, struct git_hash_ctx *c, const void *buf, unsigned long len, struct object_id *oid, @@ -570,10 +326,10 @@ static void hash_object_body(const struct git_hash_algo *algo, struct git_hash_c git_hash_final_oid(oid, c); } -static void write_object_file_prepare(const struct git_hash_algo *algo, - const void *buf, unsigned long len, - enum object_type type, struct object_id *oid, - char *hdr, int *hdrlen) +void write_object_file_prepare(const struct git_hash_algo *algo, + const void *buf, unsigned long len, + enum object_type type, struct object_id *oid, + char *hdr, int *hdrlen) { struct git_hash_ctx c; @@ -819,14 +575,14 @@ static void flush_loose_object_transaction(struct odb_transaction_files *transac } /* Finalize a file on disk, and close it. */ -static void close_loose_object(struct odb_source *source, +static void close_loose_object(struct odb_source_loose *loose, int fd, const char *filename) { - if (source->will_destroy) + if (loose->base.will_destroy) goto out; if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT)) - fsync_loose_object_transaction(source->odb->transaction, fd, filename); + fsync_loose_object_transaction(loose->base.odb->transaction, fd, filename); else if (fsync_object_files > 0) fsync_or_die(fd, filename); else @@ -895,7 +651,7 @@ static int create_tmpfile(struct repository *repo, * Returns a "fd", which should later be provided to * end_loose_object_common(). */ -static int start_loose_object_common(struct odb_source *source, +static int start_loose_object_common(struct odb_source_loose *loose, struct strbuf *tmp_file, const char *filename, unsigned flags, git_zstream *stream, @@ -903,25 +659,26 @@ static int start_loose_object_common(struct odb_source *source, struct git_hash_ctx *c, struct git_hash_ctx *compat_c, char *hdr, int hdrlen) { - const struct git_hash_algo *algo = source->odb->repo->hash_algo; - const struct git_hash_algo *compat = source->odb->repo->compat_hash_algo; + const struct git_hash_algo *algo = loose->base.odb->repo->hash_algo; + const struct git_hash_algo *compat = loose->base.odb->repo->compat_hash_algo; int fd; + struct repo_config_values *cfg = repo_config_values(the_repository); - fd = create_tmpfile(source->odb->repo, tmp_file, filename); + fd = create_tmpfile(loose->base.odb->repo, tmp_file, filename); if (fd < 0) { if (flags & ODB_WRITE_OBJECT_SILENT) return -1; else if (errno == EACCES) return error(_("insufficient permission for adding " "an object to repository database %s"), - source->path); + loose->base.path); else return error_errno( _("unable to create temporary file")); } /* Setup zlib stream for compression */ - git_deflate_init(stream, zlib_compression_level); + git_deflate_init(stream, cfg->zlib_compression_level); stream->next_out = buf; stream->avail_out = buflen; algo->init_fn(c); @@ -944,14 +701,14 @@ static int start_loose_object_common(struct odb_source *source, * Common steps for the inner git_deflate() loop for writing loose * objects. Returns what git_deflate() returns. */ -static int write_loose_object_common(struct odb_source *source, +static int write_loose_object_common(struct odb_source_loose *loose, struct git_hash_ctx *c, struct git_hash_ctx *compat_c, git_zstream *stream, const int flush, unsigned char *in0, const int fd, unsigned char *compressed, const size_t compressed_len) { - const struct git_hash_algo *compat = source->odb->repo->compat_hash_algo; + const struct git_hash_algo *compat = loose->base.odb->repo->compat_hash_algo; int ret; ret = git_deflate(stream, flush ? Z_FINISH : 0); @@ -972,12 +729,12 @@ static int write_loose_object_common(struct odb_source *source, * - End the compression of zlib stream. * - Get the calculated oid to "oid". */ -static int end_loose_object_common(struct odb_source *source, +static int end_loose_object_common(struct odb_source_loose *loose, struct git_hash_ctx *c, struct git_hash_ctx *compat_c, git_zstream *stream, struct object_id *oid, struct object_id *compat_oid) { - const struct git_hash_algo *compat = source->odb->repo->compat_hash_algo; + const struct git_hash_algo *compat = loose->base.odb->repo->compat_hash_algo; int ret; ret = git_deflate_end_gently(stream); @@ -990,10 +747,10 @@ static int end_loose_object_common(struct odb_source *source, return Z_OK; } -static int write_loose_object(struct odb_source *source, - const struct object_id *oid, char *hdr, - int hdrlen, const void *buf, unsigned long len, - time_t mtime, unsigned flags) +int write_loose_object(struct odb_source_loose *loose, + const struct object_id *oid, char *hdr, + int hdrlen, const void *buf, unsigned long len, + time_t mtime, unsigned flags) { int fd, ret; unsigned char compressed[4096]; @@ -1004,11 +761,11 @@ static int write_loose_object(struct odb_source *source, static struct strbuf filename = STRBUF_INIT; if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT)) - prepare_loose_object_transaction(source->odb->transaction); + prepare_loose_object_transaction(loose->base.odb->transaction); - odb_loose_path(source, &filename, oid); + odb_loose_path(loose, &filename, oid); - fd = start_loose_object_common(source, &tmp_file, filename.buf, flags, + fd = start_loose_object_common(loose, &tmp_file, filename.buf, flags, &stream, compressed, sizeof(compressed), &c, NULL, hdr, hdrlen); if (fd < 0) @@ -1020,14 +777,14 @@ static int write_loose_object(struct odb_source *source, do { unsigned char *in0 = stream.next_in; - ret = write_loose_object_common(source, &c, NULL, &stream, 1, in0, fd, + ret = write_loose_object_common(loose, &c, NULL, &stream, 1, in0, fd, compressed, sizeof(compressed)); } while (ret == Z_OK); if (ret != Z_STREAM_END) die(_("unable to deflate new object %s (%d)"), oid_to_hex(oid), ret); - ret = end_loose_object_common(source, &c, NULL, &stream, ¶no_oid, NULL); + ret = end_loose_object_common(loose, &c, NULL, &stream, ¶no_oid, NULL); if (ret != Z_OK) die(_("deflateEnd on object %s failed (%d)"), oid_to_hex(oid), ret); @@ -1035,7 +792,7 @@ static int write_loose_object(struct odb_source *source, die(_("confused by unstable object source data for %s"), oid_to_hex(oid)); - close_loose_object(source, fd, tmp_file.buf); + close_loose_object(loose, fd, tmp_file.buf); if (mtime) { struct utimbuf utb; @@ -1046,21 +803,15 @@ static int write_loose_object(struct odb_source *source, warning_errno(_("failed utime() on %s"), tmp_file.buf); } - return finalize_object_file_flags(source->odb->repo, tmp_file.buf, filename.buf, + return finalize_object_file_flags(loose->base.odb->repo, tmp_file.buf, filename.buf, FOF_SKIP_COLLISION_CHECK); } -int odb_source_loose_freshen_object(struct odb_source *source, - const struct object_id *oid) -{ - return !!check_and_freshen_source(source, oid, 1); -} - -int odb_source_loose_write_stream(struct odb_source *source, +int odb_source_loose_write_stream(struct odb_source_loose *loose, struct odb_write_stream *in_stream, size_t len, struct object_id *oid) { - const struct git_hash_algo *compat = source->odb->repo->compat_hash_algo; + const struct git_hash_algo *compat = loose->base.odb->repo->compat_hash_algo; struct object_id compat_oid; int fd, ret, err = 0, flush = 0; unsigned char compressed[4096]; @@ -1068,15 +819,16 @@ int odb_source_loose_write_stream(struct odb_source *source, struct git_hash_ctx c, compat_c; struct strbuf tmp_file = STRBUF_INIT; struct strbuf filename = STRBUF_INIT; + unsigned char buf[8192]; int dirlen; char hdr[MAX_HEADER_LEN]; int hdrlen; if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT)) - prepare_loose_object_transaction(source->odb->transaction); + prepare_loose_object_transaction(loose->base.odb->transaction); /* Since oid is not determined, save tmp file to odb path. */ - strbuf_addf(&filename, "%s/", source->path); + strbuf_addf(&filename, "%s/", loose->base.path); hdrlen = format_object_header(hdr, sizeof(hdr), OBJ_BLOB, len); /* @@ -1087,7 +839,7 @@ int odb_source_loose_write_stream(struct odb_source *source, * - Setup zlib stream for compression. * - Start to feed header to zlib stream. */ - fd = start_loose_object_common(source, &tmp_file, filename.buf, 0, + fd = start_loose_object_common(loose, &tmp_file, filename.buf, 0, &stream, compressed, sizeof(compressed), &c, &compat_c, hdr, hdrlen); if (fd < 0) { @@ -1100,14 +852,22 @@ int odb_source_loose_write_stream(struct odb_source *source, unsigned char *in0 = stream.next_in; if (!stream.avail_in && !in_stream->is_finished) { - const void *in = in_stream->read(in_stream, &stream.avail_in); - stream.next_in = (void *)in; - in0 = (unsigned char *)in; + ssize_t read_len = odb_write_stream_read(in_stream, buf, + sizeof(buf)); + if (read_len < 0) { + close(fd); + err = -1; + goto cleanup; + } + + stream.avail_in = read_len; + stream.next_in = buf; + in0 = buf; /* All data has been read. */ if (in_stream->is_finished) flush = 1; } - ret = write_loose_object_common(source, &c, &compat_c, &stream, flush, in0, fd, + ret = write_loose_object_common(loose, &c, &compat_c, &stream, flush, in0, fd, compressed, sizeof(compressed)); /* * Unlike write_loose_object(), we do not have the entire @@ -1130,16 +890,16 @@ int odb_source_loose_write_stream(struct odb_source *source, */ if (ret != Z_STREAM_END) die(_("unable to stream deflate new object (%d)"), ret); - ret = end_loose_object_common(source, &c, &compat_c, &stream, oid, &compat_oid); + ret = end_loose_object_common(loose, &c, &compat_c, &stream, oid, &compat_oid); if (ret != Z_OK) die(_("deflateEnd on stream object failed (%d)"), ret); - close_loose_object(source, fd, tmp_file.buf); + close_loose_object(loose, fd, tmp_file.buf); - if (odb_freshen_object(source->odb, oid)) { + if (odb_freshen_object(loose->base.odb, oid)) { unlink_or_warn(tmp_file.buf); goto cleanup; } - odb_loose_path(source, &filename, oid); + odb_loose_path(loose, &filename, oid); /* We finally know the object path, and create the missing dir. */ dirlen = directory_size(filename.buf); @@ -1147,7 +907,7 @@ int odb_source_loose_write_stream(struct odb_source *source, struct strbuf dir = STRBUF_INIT; strbuf_add(&dir, filename.buf, dirlen); - if (safe_create_dir_in_gitdir(source->odb->repo, dir.buf) && + if (safe_create_dir_in_gitdir(loose->base.odb->repo, dir.buf) && errno != EEXIST) { err = error_errno(_("unable to create directory %s"), dir.buf); strbuf_release(&dir); @@ -1156,60 +916,20 @@ int odb_source_loose_write_stream(struct odb_source *source, strbuf_release(&dir); } - err = finalize_object_file_flags(source->odb->repo, tmp_file.buf, filename.buf, + err = finalize_object_file_flags(loose->base.odb->repo, tmp_file.buf, filename.buf, FOF_SKIP_COLLISION_CHECK); if (!err && compat) - err = repo_add_loose_object_map(source, oid, &compat_oid); + err = repo_add_loose_object_map(loose, oid, &compat_oid); cleanup: strbuf_release(&tmp_file); strbuf_release(&filename); return err; } -int odb_source_loose_write_object(struct odb_source *source, - const void *buf, unsigned long len, - enum object_type type, struct object_id *oid, - struct object_id *compat_oid_in, - enum odb_write_object_flags flags) -{ - const struct git_hash_algo *algo = source->odb->repo->hash_algo; - const struct git_hash_algo *compat = source->odb->repo->compat_hash_algo; - struct object_id compat_oid; - char hdr[MAX_HEADER_LEN]; - int hdrlen = sizeof(hdr); - - /* Generate compat_oid */ - if (compat) { - if (compat_oid_in) - oidcpy(&compat_oid, compat_oid_in); - else if (type == OBJ_BLOB) - hash_object_file(compat, buf, len, type, &compat_oid); - else { - struct strbuf converted = STRBUF_INIT; - convert_object_file(source->odb->repo, &converted, algo, compat, - buf, len, type, 0); - hash_object_file(compat, converted.buf, converted.len, - type, &compat_oid); - strbuf_release(&converted); - } - } - - /* Normally if we have it in the pack then we do not bother writing - * it out into .git/objects/??/?{38} file. - */ - write_object_file_prepare(algo, buf, len, type, oid, hdr, &hdrlen); - if (odb_freshen_object(source->odb, oid)) - return 0; - if (write_loose_object(source, oid, hdr, hdrlen, buf, len, 0, flags)) - return -1; - if (compat) - return repo_add_loose_object_map(source, oid, &compat_oid); - return 0; -} - int force_object_loose(struct odb_source *source, const struct object_id *oid, time_t mtime) { + struct odb_source_files *files = odb_source_files_downcast(source); const struct git_hash_algo *compat = source->odb->repo->compat_hash_algo; void *buf; unsigned long len; @@ -1220,9 +940,11 @@ int force_object_loose(struct odb_source *source, int hdrlen; int ret; - for (struct odb_source *s = source->odb->sources; s; s = s->next) - if (odb_source_loose_has_object(s, oid)) + for (struct odb_source *s = source->odb->sources; s; s = s->next) { + struct odb_source_files *files = odb_source_files_downcast(s); + if (!odb_source_read_object_info(&files->loose->base, oid, NULL, 0)) return 0; + } oi.typep = &type; oi.sizep = &len; @@ -1235,9 +957,9 @@ int force_object_loose(struct odb_source *source, oid_to_hex(oid), compat->name); } hdrlen = format_object_header(hdr, sizeof(hdr), type, len); - ret = write_loose_object(source, oid, hdr, hdrlen, buf, len, mtime, 0); + ret = write_loose_object(files->loose, oid, hdr, hdrlen, buf, len, mtime, 0); if (!ret && compat) - ret = repo_add_loose_object_map(source, oid, &compat_oid); + ret = repo_add_loose_object_map(files->loose, oid, &compat_oid); free(buf); return ret; @@ -1392,11 +1114,10 @@ static int already_written(struct odb_transaction_files *transaction, } /* Lazily create backing packfile for the state */ -static void prepare_packfile_transaction(struct odb_transaction_files *transaction, - unsigned flags) +static void prepare_packfile_transaction(struct odb_transaction_files *transaction) { struct transaction_packfile *state = &transaction->packfile; - if (!(flags & INDEX_WRITE_OBJECT) || state->f) + if (state->f) return; state->f = create_tmp_packfile(transaction->base.source->odb->repo, @@ -1409,80 +1130,83 @@ static void prepare_packfile_transaction(struct odb_transaction_files *transacti die_errno("unable to write pack header"); } +static int hash_blob_stream(struct odb_write_stream *stream, + const struct git_hash_algo *hash_algo, + struct object_id *result_oid, size_t size) +{ + unsigned char buf[16384]; + struct git_hash_ctx ctx; + unsigned header_len; + size_t bytes_hashed = 0; + + header_len = format_object_header((char *)buf, sizeof(buf), + OBJ_BLOB, size); + hash_algo->init_fn(&ctx); + git_hash_update(&ctx, buf, header_len); + + while (!stream->is_finished) { + ssize_t read_result = odb_write_stream_read(stream, buf, + sizeof(buf)); + + if (read_result < 0) + return -1; + + git_hash_update(&ctx, buf, read_result); + bytes_hashed += read_result; + } + + if (bytes_hashed != size) + return -1; + + git_hash_final_oid(result_oid, &ctx); + + return 0; +} + /* - * Read the contents from fd for size bytes, streaming it to the - * packfile in state while updating the hash in ctx. Signal a failure - * by returning a negative value when the resulting pack would exceed - * the pack size limit and this is not the first object in the pack, - * so that the caller can discard what we wrote from the current pack - * by truncating it and opening a new one. The caller will then call - * us again after rewinding the input fd. - * - * The already_hashed_to pointer is kept untouched by the caller to - * make sure we do not hash the same byte when we are called - * again. This way, the caller does not have to checkpoint its hash - * status before calling us just in case we ask it to call us again - * with a new pack. + * Read the contents from the stream provided, streaming it to the + * packfile in state while updating the hash in ctx. */ -static int stream_blob_to_pack(struct transaction_packfile *state, - struct git_hash_ctx *ctx, off_t *already_hashed_to, - int fd, size_t size, const char *path, - unsigned flags) +static void stream_blob_to_pack(struct transaction_packfile *state, + struct git_hash_ctx *ctx, size_t size, + struct odb_write_stream *stream) { git_zstream s; unsigned char ibuf[16384]; unsigned char obuf[16384]; unsigned hdrlen; int status = Z_OK; - int write_object = (flags & INDEX_WRITE_OBJECT); - off_t offset = 0; + struct repo_config_values *cfg = repo_config_values(the_repository); + size_t bytes_read = 0; - git_deflate_init(&s, pack_compression_level); + git_deflate_init(&s, cfg->pack_compression_level); hdrlen = encode_in_pack_object_header(obuf, sizeof(obuf), OBJ_BLOB, size); s.next_out = obuf + hdrlen; s.avail_out = sizeof(obuf) - hdrlen; while (status != Z_STREAM_END) { - if (size && !s.avail_in) { - size_t rsize = size < sizeof(ibuf) ? size : sizeof(ibuf); - ssize_t read_result = read_in_full(fd, ibuf, rsize); - if (read_result < 0) - die_errno("failed to read from '%s'", path); - if ((size_t)read_result != rsize) - die("failed to read %u bytes from '%s'", - (unsigned)rsize, path); - offset += rsize; - if (*already_hashed_to < offset) { - size_t hsize = offset - *already_hashed_to; - if (rsize < hsize) - hsize = rsize; - if (hsize) - git_hash_update(ctx, ibuf, hsize); - *already_hashed_to = offset; - } + if (!stream->is_finished && !s.avail_in) { + ssize_t rsize = odb_write_stream_read(stream, ibuf, + sizeof(ibuf)); + + if (rsize < 0) + die("failed to read blob data"); + + git_hash_update(ctx, ibuf, rsize); + s.next_in = ibuf; s.avail_in = rsize; - size -= rsize; + bytes_read += rsize; } - status = git_deflate(&s, size ? 0 : Z_FINISH); + status = git_deflate(&s, stream->is_finished ? Z_FINISH : 0); if (!s.avail_out || status == Z_STREAM_END) { - if (write_object) { - size_t written = s.next_out - obuf; + size_t written = s.next_out - obuf; - /* would we bust the size limit? */ - if (state->nr_written && - pack_size_limit_cfg && - pack_size_limit_cfg < state->offset + written) { - git_deflate_abort(&s); - return -1; - } - - hashwrite(state->f, obuf, written); - state->offset += written; - } + hashwrite(state->f, obuf, written); + state->offset += written; s.next_out = obuf; s.avail_out = sizeof(obuf); } @@ -1496,8 +1220,12 @@ static int stream_blob_to_pack(struct transaction_packfile *state, die("unexpected deflate failure: %d", status); } } + + if (bytes_read != size) + die("read %" PRIuMAX " bytes of blob data, but expected %" PRIuMAX " bytes", + (uintmax_t)bytes_read, (uintmax_t)size); + git_deflate_end(&s); - return 0; } static void flush_packfile_transaction(struct odb_transaction_files *transaction) @@ -1568,64 +1296,49 @@ static void flush_packfile_transaction(struct odb_transaction_files *transaction * binary blobs, they generally do not want to get any conversion, and * callers should avoid this code path when filters are requested. */ -static int index_blob_packfile_transaction(struct odb_transaction_files *transaction, - struct object_id *result_oid, int fd, - size_t size, const char *path, - unsigned flags) +static int odb_transaction_files_write_object_stream(struct odb_transaction *base, + struct odb_write_stream *stream, + size_t size, + struct object_id *result_oid) { + struct odb_transaction_files *transaction = container_of(base, + struct odb_transaction_files, + base); struct transaction_packfile *state = &transaction->packfile; - off_t seekback, already_hashed_to; struct git_hash_ctx ctx; unsigned char obuf[16384]; unsigned header_len; struct hashfile_checkpoint checkpoint; - struct pack_idx_entry *idx = NULL; - - seekback = lseek(fd, 0, SEEK_CUR); - if (seekback == (off_t)-1) - return error("cannot find the current offset"); + struct pack_idx_entry *idx; header_len = format_object_header((char *)obuf, sizeof(obuf), OBJ_BLOB, size); transaction->base.source->odb->repo->hash_algo->init_fn(&ctx); git_hash_update(&ctx, obuf, header_len); - /* Note: idx is non-NULL when we are writing */ - if ((flags & INDEX_WRITE_OBJECT) != 0) { - CALLOC_ARRAY(idx, 1); - - prepare_packfile_transaction(transaction, flags); - hashfile_checkpoint_init(state->f, &checkpoint); - } - - already_hashed_to = 0; - - while (1) { - prepare_packfile_transaction(transaction, flags); - if (idx) { - hashfile_checkpoint(state->f, &checkpoint); - idx->offset = state->offset; - crc32_begin(state->f); - } - if (!stream_blob_to_pack(state, &ctx, &already_hashed_to, - fd, size, path, flags)) - break; - /* - * Writing this object to the current pack will make - * it too big; we need to truncate it, start a new - * pack, and write into it. - */ - if (!idx) - BUG("should not happen"); - hashfile_truncate(state->f, &checkpoint); - state->offset = checkpoint.offset; + /* + * If writing another object to the packfile could result in it + * exceeding the configured size limit, flush the current packfile + * transaction. + * + * Note that this uses the inflated object size as an approximation. + * Blob objects written in this manner are not delta-compressed, so + * the difference between the inflated and on-disk size is limited + * to zlib compression and is sufficient for this check. + */ + if (state->nr_written && pack_size_limit_cfg && + pack_size_limit_cfg < state->offset + size) flush_packfile_transaction(transaction); - if (lseek(fd, seekback, SEEK_SET) == (off_t)-1) - return error("cannot seek back"); - } + + CALLOC_ARRAY(idx, 1); + prepare_packfile_transaction(transaction); + hashfile_checkpoint_init(state->f, &checkpoint); + + hashfile_checkpoint(state->f, &checkpoint); + idx->offset = state->offset; + crc32_begin(state->f); + stream_blob_to_pack(state, &ctx, size, stream); git_hash_final_oid(result_oid, &ctx); - if (!idx) - return 0; idx->crc32 = crc32_end(state->f); if (already_written(transaction, result_oid)) { @@ -1642,34 +1355,6 @@ static int index_blob_packfile_transaction(struct odb_transaction_files *transac return 0; } -static int hash_blob_stream(const struct git_hash_algo *hash_algo, - struct object_id *result_oid, int fd, size_t size) -{ - unsigned char buf[16384]; - struct git_hash_ctx ctx; - unsigned header_len; - - header_len = format_object_header((char *)buf, sizeof(buf), - OBJ_BLOB, size); - hash_algo->init_fn(&ctx); - git_hash_update(&ctx, buf, header_len); - - while (size) { - size_t rsize = size < sizeof(buf) ? size : sizeof(buf); - ssize_t read_result = read_in_full(fd, buf, rsize); - - if ((read_result < 0) || ((size_t)read_result != rsize)) - return -1; - - git_hash_update(&ctx, buf, rsize); - size -= read_result; - } - - git_hash_final_oid(result_oid, &ctx); - - return 0; -} - int index_fd(struct index_state *istate, struct object_id *oid, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags) @@ -1691,23 +1376,25 @@ int index_fd(struct index_state *istate, struct object_id *oid, ret = index_core(istate, oid, fd, xsize_t(st->st_size), type, path, flags); } else { + struct odb_write_stream stream; + odb_write_stream_from_fd(&stream, fd, xsize_t(st->st_size)); + if (flags & INDEX_WRITE_OBJECT) { struct object_database *odb = the_repository->objects; - struct odb_transaction_files *files_transaction; - struct odb_transaction *transaction; + struct odb_transaction *transaction = odb_transaction_begin(odb); - transaction = odb_transaction_begin(odb); - files_transaction = container_of(odb->transaction, - struct odb_transaction_files, - base); - ret = index_blob_packfile_transaction(files_transaction, oid, fd, - xsize_t(st->st_size), - path, flags); + ret = odb_transaction_write_object_stream(odb->transaction, + &stream, + xsize_t(st->st_size), + oid); odb_transaction_commit(transaction); } else { - ret = hash_blob_stream(the_repository->hash_algo, oid, - fd, xsize_t(st->st_size)); + ret = hash_blob_stream(&stream, + the_repository->hash_algo, oid, + xsize_t(st->st_size)); } + + odb_write_stream_release(&stream); } close(fd); @@ -1767,13 +1454,13 @@ int read_pack_header(int fd, struct pack_header *header) return 0; } -static int for_each_file_in_obj_subdir(unsigned int subdir_nr, - struct strbuf *path, - const struct git_hash_algo *algop, - each_loose_object_fn obj_cb, - each_loose_cruft_fn cruft_cb, - each_loose_subdir_fn subdir_cb, - void *data) +int for_each_file_in_obj_subdir(unsigned int subdir_nr, + struct strbuf *path, + const struct git_hash_algo *algop, + each_loose_object_fn obj_cb, + each_loose_cruft_fn cruft_cb, + each_loose_subdir_fn subdir_cb, + void *data) { size_t origlen, baselen; DIR *dir; @@ -1858,228 +1545,6 @@ int for_each_loose_file_in_source(struct odb_source *source, return r; } -struct for_each_object_wrapper_data { - struct odb_source *source; - const struct object_info *request; - odb_for_each_object_cb cb; - void *cb_data; -}; - -static int for_each_object_wrapper_cb(const struct object_id *oid, - const char *path, - void *cb_data) -{ - struct for_each_object_wrapper_data *data = cb_data; - - if (data->request) { - struct object_info oi = *data->request; - - if (read_object_info_from_path(data->source, path, oid, &oi, 0) < 0) - return -1; - - return data->cb(oid, &oi, data->cb_data); - } else { - return data->cb(oid, NULL, data->cb_data); - } -} - -static int for_each_prefixed_object_wrapper_cb(const struct object_id *oid, - void *cb_data) -{ - struct for_each_object_wrapper_data *data = cb_data; - if (data->request) { - struct object_info oi = *data->request; - - if (odb_source_loose_read_object_info(data->source, - oid, &oi, 0) < 0) - return -1; - - return data->cb(oid, &oi, data->cb_data); - } else { - return data->cb(oid, NULL, data->cb_data); - } -} - -int odb_source_loose_for_each_object(struct odb_source *source, - const struct object_info *request, - odb_for_each_object_cb cb, - void *cb_data, - const struct odb_for_each_object_options *opts) -{ - struct for_each_object_wrapper_data data = { - .source = source, - .request = request, - .cb = cb, - .cb_data = cb_data, - }; - - /* There are no loose promisor objects, so we can return immediately. */ - if ((opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY)) - return 0; - if ((opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !source->local) - return 0; - - if (opts->prefix) - return oidtree_each(odb_source_loose_cache(source, opts->prefix), - opts->prefix, opts->prefix_hex_len, - for_each_prefixed_object_wrapper_cb, &data); - - return for_each_loose_file_in_source(source, for_each_object_wrapper_cb, - NULL, NULL, &data); -} - -static int count_loose_object(const struct object_id *oid UNUSED, - struct object_info *oi UNUSED, - void *payload) -{ - unsigned long *count = payload; - (*count)++; - return 0; -} - -int odb_source_loose_count_objects(struct odb_source *source, - enum odb_count_objects_flags flags, - unsigned long *out) -{ - const unsigned hexsz = source->odb->repo->hash_algo->hexsz - 2; - char *path = NULL; - DIR *dir = NULL; - int ret; - - if (flags & ODB_COUNT_OBJECTS_APPROXIMATE) { - unsigned long count = 0; - struct dirent *ent; - - path = xstrfmt("%s/17", source->path); - - dir = opendir(path); - if (!dir) { - if (errno == ENOENT) { - *out = 0; - ret = 0; - goto out; - } - - ret = error_errno("cannot open object shard '%s'", path); - goto out; - } - - while ((ent = readdir(dir)) != NULL) { - if (strspn(ent->d_name, "0123456789abcdef") != hexsz || - ent->d_name[hexsz] != '\0') - continue; - count++; - } - - *out = count * 256; - ret = 0; - } else { - struct odb_for_each_object_options opts = { 0 }; - *out = 0; - ret = odb_source_loose_for_each_object(source, NULL, count_loose_object, - out, &opts); - } - -out: - if (dir) - closedir(dir); - free(path); - return ret; -} - -struct find_abbrev_len_data { - const struct object_id *oid; - unsigned len; -}; - -static int find_abbrev_len_cb(const struct object_id *oid, - struct object_info *oi UNUSED, - void *cb_data) -{ - struct find_abbrev_len_data *data = cb_data; - unsigned len = oid_common_prefix_hexlen(oid, data->oid); - if (len != hash_algos[oid->algo].hexsz && len >= data->len) - data->len = len + 1; - return 0; -} - -int odb_source_loose_find_abbrev_len(struct odb_source *source, - const struct object_id *oid, - unsigned min_len, - unsigned *out) -{ - struct odb_for_each_object_options opts = { - .prefix = oid, - .prefix_hex_len = min_len, - }; - struct find_abbrev_len_data data = { - .oid = oid, - .len = min_len, - }; - int ret; - - ret = odb_source_loose_for_each_object(source, NULL, find_abbrev_len_cb, - &data, &opts); - *out = data.len; - - return ret; -} - -static int append_loose_object(const struct object_id *oid, - const char *path UNUSED, - void *data) -{ - oidtree_insert(data, oid); - return 0; -} - -static struct oidtree *odb_source_loose_cache(struct odb_source *source, - const struct object_id *oid) -{ - struct odb_source_files *files = odb_source_files_downcast(source); - int subdir_nr = oid->hash[0]; - struct strbuf buf = STRBUF_INIT; - size_t word_bits = bitsizeof(files->loose->subdir_seen[0]); - size_t word_index = subdir_nr / word_bits; - size_t mask = (size_t)1u << (subdir_nr % word_bits); - uint32_t *bitmap; - - if (subdir_nr < 0 || - (size_t) subdir_nr >= bitsizeof(files->loose->subdir_seen)) - BUG("subdir_nr out of range"); - - bitmap = &files->loose->subdir_seen[word_index]; - if (*bitmap & mask) - return files->loose->cache; - if (!files->loose->cache) { - ALLOC_ARRAY(files->loose->cache, 1); - oidtree_init(files->loose->cache); - } - strbuf_addstr(&buf, source->path); - for_each_file_in_obj_subdir(subdir_nr, &buf, - source->odb->repo->hash_algo, - append_loose_object, - NULL, NULL, - files->loose->cache); - *bitmap |= mask; - strbuf_release(&buf); - return files->loose->cache; -} - -static void odb_source_loose_clear_cache(struct odb_source_loose *loose) -{ - oidtree_clear(loose->cache); - FREE_AND_NULL(loose->cache); - memset(&loose->subdir_seen, 0, - sizeof(loose->subdir_seen)); -} - -void odb_source_loose_reprepare(struct odb_source *source) -{ - struct odb_source_files *files = odb_source_files_downcast(source); - odb_source_loose_clear_cache(files->loose); -} - static int check_stream_oid(git_zstream *stream, const char *hdr, unsigned long size, @@ -2225,158 +1690,7 @@ struct odb_transaction *odb_transaction_files_begin(struct odb_source *source) transaction = xcalloc(1, sizeof(*transaction)); transaction->base.source = source; transaction->base.commit = odb_transaction_files_commit; + transaction->base.write_object_stream = odb_transaction_files_write_object_stream; return &transaction->base; } - -struct odb_source_loose *odb_source_loose_new(struct odb_source *source) -{ - struct odb_source_loose *loose; - CALLOC_ARRAY(loose, 1); - loose->source = source; - return loose; -} - -void odb_source_loose_free(struct odb_source_loose *loose) -{ - if (!loose) - return; - odb_source_loose_clear_cache(loose); - loose_object_map_clear(&loose->map); - free(loose); -} - -struct odb_loose_read_stream { - struct odb_read_stream base; - git_zstream z; - enum { - ODB_LOOSE_READ_STREAM_INUSE, - ODB_LOOSE_READ_STREAM_DONE, - ODB_LOOSE_READ_STREAM_ERROR, - } z_state; - void *mapped; - unsigned long mapsize; - char hdr[32]; - int hdr_avail; - int hdr_used; -}; - -static ssize_t read_istream_loose(struct odb_read_stream *_st, char *buf, size_t sz) -{ - struct odb_loose_read_stream *st = - container_of(_st, struct odb_loose_read_stream, base); - size_t total_read = 0; - - switch (st->z_state) { - case ODB_LOOSE_READ_STREAM_DONE: - return 0; - case ODB_LOOSE_READ_STREAM_ERROR: - return -1; - default: - break; - } - - if (st->hdr_used < st->hdr_avail) { - size_t to_copy = st->hdr_avail - st->hdr_used; - if (sz < to_copy) - to_copy = sz; - memcpy(buf, st->hdr + st->hdr_used, to_copy); - st->hdr_used += to_copy; - total_read += to_copy; - } - - while (total_read < sz) { - int status; - - st->z.next_out = (unsigned char *)buf + total_read; - st->z.avail_out = sz - total_read; - status = git_inflate(&st->z, Z_FINISH); - - total_read = st->z.next_out - (unsigned char *)buf; - - if (status == Z_STREAM_END) { - git_inflate_end(&st->z); - st->z_state = ODB_LOOSE_READ_STREAM_DONE; - break; - } - if (status != Z_OK && (status != Z_BUF_ERROR || total_read < sz)) { - git_inflate_end(&st->z); - st->z_state = ODB_LOOSE_READ_STREAM_ERROR; - return -1; - } - } - return total_read; -} - -static int close_istream_loose(struct odb_read_stream *_st) -{ - struct odb_loose_read_stream *st = - container_of(_st, struct odb_loose_read_stream, base); - - if (st->z_state == ODB_LOOSE_READ_STREAM_INUSE) - git_inflate_end(&st->z); - munmap(st->mapped, st->mapsize); - return 0; -} - -int odb_source_loose_read_object_stream(struct odb_read_stream **out, - struct odb_source *source, - const struct object_id *oid) -{ - struct object_info oi = OBJECT_INFO_INIT; - struct odb_loose_read_stream *st; - unsigned long mapsize; - unsigned long size_ul; - void *mapped; - - mapped = odb_source_loose_map_object(source, oid, &mapsize); - if (!mapped) - return -1; - - /* - * Note: we must allocate this structure early even though we may still - * fail. This is because we need to initialize the zlib stream, and it - * is not possible to copy the stream around after the fact because it - * has self-referencing pointers. - */ - CALLOC_ARRAY(st, 1); - - switch (unpack_loose_header(&st->z, mapped, mapsize, st->hdr, - sizeof(st->hdr))) { - case ULHR_OK: - break; - case ULHR_BAD: - case ULHR_TOO_LONG: - goto error; - } - - /* - * object_info.sizep is unsigned long* (32-bit on Windows), but - * st->base.size is size_t (64-bit). Use temporary variable. - * Note: loose objects >4GB would still truncate here, but such - * large loose objects are uncommon (they'd normally be packed). - */ - oi.sizep = &size_ul; - oi.typep = &st->base.type; - - if (parse_loose_header(st->hdr, &oi) < 0 || st->base.type < 0) - goto error; - st->base.size = size_ul; - - st->mapped = mapped; - st->mapsize = mapsize; - st->hdr_used = strlen(st->hdr) + 1; - st->hdr_avail = st->z.total_out; - st->z_state = ODB_LOOSE_READ_STREAM_INUSE; - st->base.close = close_istream_loose; - st->base.read = read_istream_loose; - - *out = &st->base; - - return 0; -error: - git_inflate_end(&st->z); - munmap(mapped, mapsize); - free(st); - return -1; -}
diff --git a/object-file.h b/object-file.h index 5241b8d..528c4e6 100644 --- a/object-file.h +++ b/object-file.h
@@ -4,6 +4,10 @@ #include "git-zlib.h" #include "object.h" #include "odb.h" +#include "odb/source-loose.h" + +/* The maximum size for an object header. */ +#define MAX_HEADER_LEN 32 struct index_state; @@ -17,61 +21,19 @@ int index_fd(struct index_state *istate, struct object_id *oid, int fd, struct s int index_path(struct index_state *istate, struct object_id *oid, const char *path, struct stat *st, unsigned flags); struct object_info; -struct odb_read_stream; struct odb_source; -struct odb_source_loose { - struct odb_source *source; - - /* - * Used to store the results of readdir(3) calls when we are OK - * sacrificing accuracy due to races for speed. That includes - * object existence with OBJECT_INFO_QUICK, as well as - * our search for unique abbreviated hashes. Don't use it for tasks - * requiring greater accuracy! - * - * Be sure to call odb_load_loose_cache() before using. - */ - uint32_t subdir_seen[8]; /* 256 bits */ - struct oidtree *cache; - - /* Map between object IDs for loose objects. */ - struct loose_object_map *map; -}; - -struct odb_source_loose *odb_source_loose_new(struct odb_source *source); -void odb_source_loose_free(struct odb_source_loose *loose); - -/* Reprepare the loose source by emptying the loose object cache. */ -void odb_source_loose_reprepare(struct odb_source *source); - -int odb_source_loose_read_object_info(struct odb_source *source, - const struct object_id *oid, - struct object_info *oi, - enum object_info_flags flags); - -int odb_source_loose_read_object_stream(struct odb_read_stream **out, - struct odb_source *source, - const struct object_id *oid); - /* - * Return true iff an object database source has a loose object - * with the specified name. This function does not respect replace - * references. + * Write the given stream into the loose object source. The only difference + * from the generic implementation of this function is that we don't perform an + * object existence check here. + * + * TODO: We should stop exposing this function altogether and move it into + * "odb/source-loose.c". This requires a couple of refactorings though to make + * `force_object_loose()` generic and is thus postponed to a later point in + * time. */ -int odb_source_loose_has_object(struct odb_source *source, - const struct object_id *oid); - -int odb_source_loose_freshen_object(struct odb_source *source, - const struct object_id *oid); - -int odb_source_loose_write_object(struct odb_source *source, - const void *buf, unsigned long len, - enum object_type type, struct object_id *oid, - struct object_id *compat_oid_in, - enum odb_write_object_flags flags); - -int odb_source_loose_write_stream(struct odb_source *source, +int odb_source_loose_write_stream(struct odb_source_loose *source, struct odb_write_stream *stream, size_t len, struct object_id *oid); @@ -79,7 +41,7 @@ int odb_source_loose_write_stream(struct odb_source *source, * Put in `buf` the name of the file in the local object database that * would be used to store a loose object with the specified oid. */ -const char *odb_loose_path(struct odb_source *source, +const char *odb_loose_path(struct odb_source_loose *source, struct strbuf *buf, const struct object_id *oid); @@ -119,45 +81,13 @@ int for_each_loose_file_in_source(struct odb_source *source, each_loose_cruft_fn cruft_cb, each_loose_subdir_fn subdir_cb, void *data); - -/* - * Iterate through all loose objects in the given object database source and - * invoke the callback function for each of them. If an object info request is - * given, then the object info will be read for every individual object and - * passed to the callback as if `odb_source_loose_read_object_info()` was - * called for the object. - */ -int odb_source_loose_for_each_object(struct odb_source *source, - const struct object_info *request, - odb_for_each_object_cb cb, - void *cb_data, - const struct odb_for_each_object_options *opts); - -/* - * Count the number of loose objects in this source. - * - * The object count is approximated by opening a single sharding directory for - * loose objects and scanning its contents. The result is then extrapolated by - * 256. This should generally work as a reasonable estimate given that the - * object hash is supposed to be indistinguishable from random. - * - * Returns 0 on success, a negative error code otherwise. - */ -int odb_source_loose_count_objects(struct odb_source *source, - enum odb_count_objects_flags flags, - unsigned long *out); - -/* - * Find the shortest unique prefix for the given object ID, where `min_len` is - * the minimum length that the prefix should have. - * - * Returns 0 on success, in which case the computed length will be written to - * `out`. Otherwise, a negative error code is returned. - */ -int odb_source_loose_find_abbrev_len(struct odb_source *source, - const struct object_id *oid, - unsigned min_len, - unsigned *out); +int for_each_file_in_obj_subdir(unsigned int subdir_nr, + struct strbuf *path, + const struct git_hash_algo *algop, + each_loose_object_fn obj_cb, + each_loose_cruft_fn cruft_cb, + each_loose_subdir_fn subdir_cb, + void *data); /** * format_object_header() is a thin wrapper around s xsnprintf() that @@ -203,6 +133,14 @@ int finalize_object_file_flags(struct repository *repo, void hash_object_file(const struct git_hash_algo *algo, const void *buf, unsigned long len, enum object_type type, struct object_id *oid); +void write_object_file_prepare(const struct git_hash_algo *algo, + const void *buf, unsigned long len, + enum object_type type, struct object_id *oid, + char *hdr, int *hdrlen); +int write_loose_object(struct odb_source_loose *loose, + const struct object_id *oid, char *hdr, + int hdrlen, const void *buf, unsigned long len, + time_t mtime, unsigned flags); /* Helper to check and "touch" a file */ int check_and_freshen_file(const char *fn, int freshen); @@ -222,6 +160,35 @@ int read_loose_object(struct repository *repo, void **contents, struct object_info *oi); +enum unpack_loose_header_result { + ULHR_OK, + ULHR_BAD, + ULHR_TOO_LONG, +}; + +/** + * unpack_loose_header() initializes the data stream needed to unpack + * a loose object header. + * + * Returns: + * + * - ULHR_OK on success + * - ULHR_BAD on error + * - ULHR_TOO_LONG if the header was too long + * + * It will only parse up to MAX_HEADER_LEN bytes. + */ +enum unpack_loose_header_result unpack_loose_header(git_zstream *stream, + unsigned char *map, + unsigned long mapsize, + void *buffer, + unsigned long bufsiz); +void *unpack_loose_rest(git_zstream *stream, + void *buffer, unsigned long size, + const struct object_id *oid); + +int parse_loose_header(const char *hdr, struct object_info *oi); + struct odb_transaction; /*
diff --git a/object-name.c b/object-name.c index 21dcdc4..4615946 100644 --- a/object-name.c +++ b/object-name.c
@@ -684,11 +684,12 @@ static int get_oid_basic(struct repository *r, const char *str, int len, int refs_found = 0; int at, reflog_len, nth_prior = 0; int fatal = !(flags & GET_OID_QUIETLY); + struct repo_config_values *cfg = repo_config_values(the_repository); if (len == r->hash_algo->hexsz && !get_oid_hex(str, oid)) { if (!(flags & GET_OID_SKIP_AMBIGUITY_CHECK) && repo_settings_get_warn_ambiguous_refs(r) && - warn_on_object_refname_ambiguity) { + cfg->warn_on_object_refname_ambiguity) { refs_found = repo_dwim_ref(r, str, len, &tmp_oid, &real_ref, 0); if (refs_found > 0) { warning(warn_msg, len, str); @@ -1703,11 +1704,11 @@ static char *resolve_relative_path(struct repository *r, const char *rel) if (!starts_with(rel, "./") && !starts_with(rel, "../")) return NULL; - if (r != the_repository || !is_inside_work_tree()) + if (r != the_repository || !is_inside_work_tree(the_repository)) die(_("relative path syntax can't be used outside working tree")); /* die() inside prefix_path() if resolved path is outside worktree */ - return prefix_path(startup_info->prefix, + return prefix_path(the_repository, startup_info->prefix, startup_info->prefix ? strlen(startup_info->prefix) : 0, rel); }
diff --git a/object.h b/object.h index d814647..8fb03ff 100644 --- a/object.h +++ b/object.h
@@ -67,6 +67,7 @@ void object_array_init(struct object_array *array); * revision.h: 0---------10 15 23--------28 * fetch-pack.c: 01 67 * negotiator/default.c: 2--5 + * negotiator/skipping.c: 2--5 * walker.c: 0-2 * upload-pack.c: 4 11-----14 16-----19 * builtin/blame.c: 12-13 @@ -74,15 +75,15 @@ void object_array_init(struct object_array *array); * bundle.c: 16 * http-push.c: 11-----14 * commit-graph.c: 15 - * commit-reach.c: 16-----19 + * commit-reach.c: 16-------20 * builtin/last-modified.c: 1617 - * sha1-name.c: 20 + * object-name.c: 20 * list-objects-filter.c: 21 * bloom.c: 2122 * builtin/fsck.c: 0--3 * builtin/index-pack.c: 2021 * reflog.c: 10--12 - * builtin/show-branch.c: 0-------------------------------------------26 + * builtin/show-branch.c: 0-----------------------------------------------28 * builtin/unpack-objects.c: 2021 * pack-bitmap.h: 2122 */
diff --git a/odb.c b/odb.c index 9b28fe2..965ef68 100644 --- a/odb.c +++ b/odb.c
@@ -14,6 +14,7 @@ #include "object-file.h" #include "object-name.h" #include "odb.h" +#include "odb/source-inmemory.h" #include "packfile.h" #include "path.h" #include "promisor-remote.h" @@ -31,40 +32,6 @@ KHASH_INIT(odb_path_map, const char * /* key: odb_path */, struct odb_source *, 1, fspathhash, fspatheq) -/* - * This is meant to hold a *small* number of objects that you would - * want odb_read_object() to be able to return, but yet you do not want - * to write them into the object store (e.g. a browse-only - * application). - */ -struct cached_object_entry { - struct object_id oid; - struct cached_object { - enum object_type type; - const void *buf; - unsigned long size; - } value; -}; - -static const struct cached_object *find_cached_object(struct object_database *object_store, - const struct object_id *oid) -{ - static const struct cached_object empty_tree = { - .type = OBJ_TREE, - .buf = "", - }; - const struct cached_object_entry *co = object_store->cached_objects; - - for (size_t i = 0; i < object_store->cached_object_nr; i++, co++) - if (oideq(&co->oid, oid)) - return &co->value; - - if (oid->algo && oideq(oid, hash_algos[oid->algo].empty_tree)) - return &empty_tree; - - return NULL; -} - int odb_mkstemp(struct object_database *odb, struct strbuf *temp_filename, const char *pattern) { @@ -584,7 +551,6 @@ static int do_oid_object_info_extended(struct object_database *odb, const struct object_id *oid, struct object_info *oi, unsigned flags) { - const struct cached_object *co; const struct object_id *real = oid; int already_retried = 0; @@ -594,25 +560,8 @@ static int do_oid_object_info_extended(struct object_database *odb, if (is_null_oid(real)) return -1; - co = find_cached_object(odb, real); - if (co) { - if (oi) { - if (oi->typep) - *(oi->typep) = co->type; - if (oi->sizep) - *(oi->sizep) = co->size; - if (oi->disk_sizep) - *(oi->disk_sizep) = 0; - if (oi->delta_base_oid) - oidclr(oi->delta_base_oid, odb->repo->hash_algo); - if (oi->contentp) - *oi->contentp = xmemdupz(co->buf, co->size); - if (oi->mtimep) - *oi->mtimep = 0; - oi->whence = OI_CACHED; - } + if (!odb_source_read_object_info(odb->inmemory_objects, oid, oi, flags)) return 0; - } odb_prepare_alternates(odb); @@ -784,24 +733,12 @@ int odb_pretend_object(struct object_database *odb, void *buf, unsigned long len, enum object_type type, struct object_id *oid) { - struct cached_object_entry *co; - char *co_buf; - hash_object_file(odb->repo->hash_algo, buf, len, type, oid); - if (odb_has_object(odb, oid, 0) || - find_cached_object(odb, oid)) + if (odb_has_object(odb, oid, 0)) return 0; - ALLOC_GROW(odb->cached_objects, - odb->cached_object_nr + 1, odb->cached_object_alloc); - co = &odb->cached_objects[odb->cached_object_nr++]; - co->value.size = len; - co->value.type = type; - co_buf = xmalloc(len); - memcpy(co_buf, buf, len); - co->value.buf = co_buf; - oidcpy(&co->oid, oid); - return 0; + return odb_source_write_object(odb->inmemory_objects, + buf, len, type, oid, NULL, 0); } void *odb_read_object(struct object_database *odb, @@ -1083,6 +1020,7 @@ struct object_database *odb_new(struct repository *repo, o->sources = odb_source_new(o, primary_source, true); o->sources_tail = &o->sources->next; o->alternate_db = xstrdup_or_null(secondary_sources); + o->inmemory_objects = &odb_source_inmemory_new(o)->base; free(to_free); @@ -1106,6 +1044,10 @@ static void odb_free_sources(struct object_database *o) odb_source_free(o->sources); o->sources = next; } + + odb_source_free(o->inmemory_objects); + o->inmemory_objects = NULL; + kh_destroy_odb_path_map(o->source_by_path); o->source_by_path = NULL; } @@ -1123,10 +1065,6 @@ void odb_free(struct object_database *o) odb_close(o); odb_free_sources(o); - for (size_t i = 0; i < o->cached_object_nr; i++) - free((char *) o->cached_objects[i].value.buf); - free(o->cached_objects); - string_list_clear(&o->submodule_source_paths, 0); free(o); @@ -1154,28 +1092,3 @@ void odb_reprepare(struct object_database *o) obj_read_unlock(); } - -struct odb_transaction *odb_transaction_begin(struct object_database *odb) -{ - if (odb->transaction) - return NULL; - - odb->transaction = odb_transaction_files_begin(odb->sources); - - return odb->transaction; -} - -void odb_transaction_commit(struct odb_transaction *transaction) -{ - if (!transaction) - return; - - /* - * Ensure the transaction ending matches the pending transaction. - */ - ASSERT(transaction == transaction->source->odb->transaction); - - transaction->commit(transaction); - transaction->source->odb->transaction = NULL; - free(transaction); -}
diff --git a/odb.h b/odb.h index 3a711f6..0030467 100644 --- a/odb.h +++ b/odb.h
@@ -8,6 +8,7 @@ #include "thread-utils.h" struct cached_object_entry; +struct odb_source_inmemory; struct packed_git; struct repository; struct strbuf; @@ -30,24 +31,6 @@ extern int fetch_if_missing; char *compute_alternate_path(const char *path, struct strbuf *err); /* - * A transaction may be started for an object database prior to writing new - * objects via odb_transaction_begin(). These objects are not committed until - * odb_transaction_commit() is invoked. Only a single transaction may be pending - * at a time. - * - * Each ODB source is expected to implement its own transaction handling. - */ -struct odb_transaction; -typedef void (*odb_transaction_commit_fn)(struct odb_transaction *transaction); -struct odb_transaction { - /* The ODB source the transaction is opened against. */ - struct odb_source *source; - - /* The ODB source specific callback invoked to commit a transaction. */ - odb_transaction_commit_fn commit; -}; - -/* * The object database encapsulates access to objects in a repository. It * manages one or more sources that store the actual objects which are * configured via alternates. @@ -57,7 +40,7 @@ struct object_database { struct repository *repo; /* - * State of current current object database transaction. Only one + * State of current object database transaction. Only one * transaction may be pending at a time. Is NULL when no transaction is * configured. */ @@ -98,8 +81,7 @@ struct object_database { * to write them into the object store (e.g. a browse-only * application). */ - struct cached_object_entry *cached_objects; - size_t cached_object_nr, cached_object_alloc; + struct odb_source *inmemory_objects; /* * A fast, rough count of the number of objects in the repository. @@ -149,19 +131,6 @@ void odb_close(struct object_database *o); void odb_reprepare(struct object_database *o); /* - * Starts an ODB transaction. Subsequent objects are written to the transaction - * and not committed until odb_transaction_commit() is invoked on the - * transaction. If the ODB already has a pending transaction, NULL is returned. - */ -struct odb_transaction *odb_transaction_begin(struct object_database *odb); - -/* - * Commits an ODB transaction making the written objects visible. If the - * specified transaction is NULL, the function is a no-op. - */ -void odb_transaction_commit(struct odb_transaction *transaction); - -/* * Find source by its object directory path. Returns a `NULL` pointer in case * the source could not be found. */ @@ -593,11 +562,7 @@ static inline int odb_write_object(struct object_database *odb, return odb_write_object_ext(odb, buf, len, type, oid, NULL, 0); } -struct odb_write_stream { - const void *(*read)(struct odb_write_stream *, unsigned long *len); - void *data; - int is_finished; -}; +struct odb_write_stream; int odb_write_object_stream(struct object_database *odb, struct odb_write_stream *stream, size_t len,
diff --git a/odb/source-files.c b/odb/source-files.c index b5abd20..5bdd042 100644 --- a/odb/source-files.c +++ b/odb/source-files.c
@@ -7,6 +7,7 @@ #include "odb.h" #include "odb/source.h" #include "odb/source-files.h" +#include "odb/source-loose.h" #include "packfile.h" #include "strbuf.h" #include "write-or-die.h" @@ -27,7 +28,7 @@ static void odb_source_files_free(struct odb_source *source) { struct odb_source_files *files = odb_source_files_downcast(source); chdir_notify_unregister(NULL, odb_source_files_reparent, files); - odb_source_loose_free(files->loose); + odb_source_free(&files->loose->base); packfile_store_free(files->packed); odb_source_release(&files->base); free(files); @@ -36,13 +37,14 @@ static void odb_source_files_free(struct odb_source *source) static void odb_source_files_close(struct odb_source *source) { struct odb_source_files *files = odb_source_files_downcast(source); + odb_source_close(&files->loose->base); packfile_store_close(files->packed); } static void odb_source_files_reprepare(struct odb_source *source) { struct odb_source_files *files = odb_source_files_downcast(source); - odb_source_loose_reprepare(&files->base); + odb_source_reprepare(&files->loose->base); packfile_store_reprepare(files->packed); } @@ -54,7 +56,7 @@ static int odb_source_files_read_object_info(struct odb_source *source, struct odb_source_files *files = odb_source_files_downcast(source); if (!packfile_store_read_object_info(files->packed, oid, oi, flags) || - !odb_source_loose_read_object_info(source, oid, oi, flags)) + !odb_source_read_object_info(&files->loose->base, oid, oi, flags)) return 0; return -1; @@ -66,7 +68,7 @@ static int odb_source_files_read_object_stream(struct odb_read_stream **out, { struct odb_source_files *files = odb_source_files_downcast(source); if (!packfile_store_read_object_stream(out, files->packed, oid) || - !odb_source_loose_read_object_stream(out, source, oid)) + !odb_source_read_object_stream(out, &files->loose->base, oid)) return 0; return -1; } @@ -81,7 +83,7 @@ static int odb_source_files_for_each_object(struct odb_source *source, int ret; if (!(opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY)) { - ret = odb_source_loose_for_each_object(source, request, cb, cb_data, opts); + ret = odb_source_for_each_object(&files->loose->base, request, cb, cb_data, opts); if (ret) return ret; } @@ -108,7 +110,7 @@ static int odb_source_files_count_objects(struct odb_source *source, if (!(flags & ODB_COUNT_OBJECTS_APPROXIMATE)) { unsigned long loose_count; - ret = odb_source_loose_count_objects(source, flags, &loose_count); + ret = odb_source_count_objects(&files->loose->base, flags, &loose_count); if (ret < 0) goto out; @@ -135,7 +137,7 @@ static int odb_source_files_find_abbrev_len(struct odb_source *source, if (ret < 0) goto out; - ret = odb_source_loose_find_abbrev_len(source, oid, len, &len); + ret = odb_source_find_abbrev_len(&files->loose->base, oid, len, &len); if (ret < 0) goto out; @@ -151,7 +153,7 @@ static int odb_source_files_freshen_object(struct odb_source *source, { struct odb_source_files *files = odb_source_files_downcast(source); if (packfile_store_freshen_object(files->packed, oid) || - odb_source_loose_freshen_object(source, oid)) + odb_source_freshen_object(&files->loose->base, oid)) return 1; return 0; } @@ -163,8 +165,9 @@ static int odb_source_files_write_object(struct odb_source *source, struct object_id *compat_oid, enum odb_write_object_flags flags) { - return odb_source_loose_write_object(source, buf, len, type, - oid, compat_oid, flags); + struct odb_source_files *files = odb_source_files_downcast(source); + return odb_source_write_object(&files->loose->base, buf, len, type, + oid, compat_oid, flags); } static int odb_source_files_write_object_stream(struct odb_source *source, @@ -172,7 +175,8 @@ static int odb_source_files_write_object_stream(struct odb_source *source, size_t len, struct object_id *oid) { - return odb_source_loose_write_stream(source, stream, len, oid); + struct odb_source_files *files = odb_source_files_downcast(source); + return odb_source_write_object_stream(&files->loose->base, stream, len, oid); } static int odb_source_files_begin_transaction(struct odb_source *source, @@ -264,7 +268,7 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb, CALLOC_ARRAY(files, 1); odb_source_init(&files->base, odb, ODB_SOURCE_FILES, path, local); - files->loose = odb_source_loose_new(&files->base); + files->loose = odb_source_loose_new(odb, path, local); files->packed = packfile_store_new(&files->base); files->base.free = odb_source_files_free;
diff --git a/odb/source-inmemory.c b/odb/source-inmemory.c new file mode 100644 index 0000000..e004566 --- /dev/null +++ b/odb/source-inmemory.c
@@ -0,0 +1,382 @@ +#include "git-compat-util.h" +#include "object-file.h" +#include "odb.h" +#include "odb/source-inmemory.h" +#include "odb/streaming.h" +#include "oidtree.h" +#include "repository.h" + +struct inmemory_object { + enum object_type type; + const void *buf; + unsigned long size; +}; + +static const struct inmemory_object *find_cached_object(struct odb_source_inmemory *source, + const struct object_id *oid) +{ + static const struct inmemory_object empty_tree = { + .type = OBJ_TREE, + .buf = "", + }; + const struct inmemory_object *object; + + if (source->objects) { + object = oidtree_get(source->objects, oid); + if (object) + return object; + } + + if (oid->algo && oideq(oid, hash_algos[oid->algo].empty_tree)) + return &empty_tree; + + return NULL; +} + +static void populate_object_info(struct odb_source_inmemory *source, + struct object_info *oi, + const struct inmemory_object *object) +{ + if (!oi) + return; + + if (oi->typep) + *(oi->typep) = object->type; + if (oi->sizep) + *(oi->sizep) = object->size; + if (oi->disk_sizep) + *(oi->disk_sizep) = 0; + if (oi->delta_base_oid) + oidclr(oi->delta_base_oid, source->base.odb->repo->hash_algo); + if (oi->contentp) + *oi->contentp = xmemdupz(object->buf, object->size); + if (oi->mtimep) + *oi->mtimep = 0; + oi->whence = OI_CACHED; +} + +static int odb_source_inmemory_read_object_info(struct odb_source *source, + const struct object_id *oid, + struct object_info *oi, + enum object_info_flags flags UNUSED) +{ + struct odb_source_inmemory *inmemory = odb_source_inmemory_downcast(source); + const struct inmemory_object *object; + + object = find_cached_object(inmemory, oid); + if (!object) + return -1; + + populate_object_info(inmemory, oi, object); + return 0; +} + +struct odb_read_stream_inmemory { + struct odb_read_stream base; + const unsigned char *buf; + size_t offset; +}; + +static ssize_t odb_read_stream_inmemory_read(struct odb_read_stream *stream, + char *buf, size_t buf_len) +{ + struct odb_read_stream_inmemory *inmemory = + container_of(stream, struct odb_read_stream_inmemory, base); + size_t bytes = buf_len; + + if (buf_len > inmemory->base.size - inmemory->offset) + bytes = inmemory->base.size - inmemory->offset; + + memcpy(buf, inmemory->buf + inmemory->offset, bytes); + inmemory->offset += bytes; + + return bytes; +} + +static int odb_read_stream_inmemory_close(struct odb_read_stream *stream UNUSED) +{ + return 0; +} + +static int odb_source_inmemory_read_object_stream(struct odb_read_stream **out, + struct odb_source *source, + const struct object_id *oid) +{ + struct odb_source_inmemory *inmemory = odb_source_inmemory_downcast(source); + struct odb_read_stream_inmemory *stream; + const struct inmemory_object *object; + + object = find_cached_object(inmemory, oid); + if (!object) + return -1; + + CALLOC_ARRAY(stream, 1); + stream->base.read = odb_read_stream_inmemory_read; + stream->base.close = odb_read_stream_inmemory_close; + stream->base.size = object->size; + stream->base.type = object->type; + stream->buf = object->buf; + + *out = &stream->base; + return 0; +} + +struct odb_source_inmemory_for_each_object_data { + struct odb_source_inmemory *inmemory; + const struct object_info *request; + odb_for_each_object_cb cb; + void *cb_data; +}; + +static int odb_source_inmemory_for_each_object_cb(const struct object_id *oid, + void *node_data, void *cb_data) +{ + struct odb_source_inmemory_for_each_object_data *data = cb_data; + struct inmemory_object *object = node_data; + + if (data->request) { + struct object_info oi = *data->request; + populate_object_info(data->inmemory, &oi, object); + return data->cb(oid, &oi, data->cb_data); + } else { + return data->cb(oid, NULL, data->cb_data); + } +} + +static int odb_source_inmemory_for_each_object(struct odb_source *source, + const struct object_info *request, + odb_for_each_object_cb cb, + void *cb_data, + const struct odb_for_each_object_options *opts) +{ + struct odb_source_inmemory *inmemory = odb_source_inmemory_downcast(source); + struct odb_source_inmemory_for_each_object_data payload = { + .inmemory = inmemory, + .request = request, + .cb = cb, + .cb_data = cb_data, + }; + struct object_id null_oid = { 0 }; + + if ((opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) || + (opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY && !source->local)) + return 0; + if (!inmemory->objects) + return 0; + + return oidtree_each(inmemory->objects, + opts->prefix ? opts->prefix : &null_oid, opts->prefix_hex_len, + odb_source_inmemory_for_each_object_cb, &payload); +} + +struct find_abbrev_len_data { + const struct object_id *oid; + unsigned len; +}; + +static int find_abbrev_len_cb(const struct object_id *oid, + struct object_info *oi UNUSED, + void *cb_data) +{ + struct find_abbrev_len_data *data = cb_data; + unsigned len = oid_common_prefix_hexlen(oid, data->oid); + if (len != hash_algos[oid->algo].hexsz && len >= data->len) + data->len = len + 1; + return 0; +} + +static int odb_source_inmemory_find_abbrev_len(struct odb_source *source, + const struct object_id *oid, + unsigned min_len, + unsigned *out) +{ + struct odb_for_each_object_options opts = { + .prefix = oid, + .prefix_hex_len = min_len, + }; + struct find_abbrev_len_data data = { + .oid = oid, + .len = min_len, + }; + int ret; + + ret = odb_source_inmemory_for_each_object(source, NULL, find_abbrev_len_cb, + &data, &opts); + *out = data.len; + + return ret; +} + +static int count_objects_cb(const struct object_id *oid UNUSED, + struct object_info *oi UNUSED, + void *cb_data) +{ + unsigned long *counter = cb_data; + (*counter)++; + return 0; +} + +static int odb_source_inmemory_count_objects(struct odb_source *source, + enum odb_count_objects_flags flags UNUSED, + unsigned long *out) +{ + struct odb_for_each_object_options opts = { 0 }; + *out = 0; + return odb_source_inmemory_for_each_object(source, NULL, count_objects_cb, + out, &opts); +} + +static int odb_source_inmemory_write_object(struct odb_source *source, + const void *buf, unsigned long len, + enum object_type type, + struct object_id *oid, + struct object_id *compat_oid UNUSED, + enum odb_write_object_flags flags UNUSED) +{ + struct odb_source_inmemory *inmemory = odb_source_inmemory_downcast(source); + struct inmemory_object *object; + + hash_object_file(source->odb->repo->hash_algo, buf, len, type, oid); + + if (!inmemory->objects) { + CALLOC_ARRAY(inmemory->objects, 1); + oidtree_init(inmemory->objects); + } else if (oidtree_contains(inmemory->objects, oid)) { + return 0; + } + + CALLOC_ARRAY(object, 1); + object->size = len; + object->type = type; + object->buf = xmemdupz(buf, len); + + oidtree_insert(inmemory->objects, oid, object); + + return 0; +} + +static int odb_source_inmemory_write_object_stream(struct odb_source *source, + struct odb_write_stream *stream, + size_t len, + struct object_id *oid) +{ + char buf[16384]; + size_t total_read = 0; + char *data; + int ret; + + CALLOC_ARRAY(data, len); + while (!stream->is_finished) { + ssize_t bytes_read; + + bytes_read = odb_write_stream_read(stream, buf, sizeof(buf)); + if (total_read + bytes_read > len) { + ret = error("object stream yielded more bytes than expected"); + goto out; + } + + memcpy(data + total_read, buf, bytes_read); + total_read += bytes_read; + } + + if (total_read != len) { + ret = error("object stream yielded less bytes than expected"); + goto out; + } + + ret = odb_source_inmemory_write_object(source, data, len, OBJ_BLOB, oid, + NULL, 0); + if (ret < 0) + goto out; + +out: + free(data); + return ret; +} + +static int odb_source_inmemory_freshen_object(struct odb_source *source, + const struct object_id *oid) +{ + struct odb_source_inmemory *inmemory = odb_source_inmemory_downcast(source); + if (find_cached_object(inmemory, oid)) + return 1; + return 0; +} + +static int odb_source_inmemory_begin_transaction(struct odb_source *source UNUSED, + struct odb_transaction **out UNUSED) +{ + return error("in-memory source does not support transactions"); +} + +static int odb_source_inmemory_read_alternates(struct odb_source *source UNUSED, + struct strvec *out UNUSED) +{ + return 0; +} + +static int odb_source_inmemory_write_alternate(struct odb_source *source UNUSED, + const char *alternate UNUSED) +{ + return error("in-memory source does not support alternates"); +} + +static void odb_source_inmemory_close(struct odb_source *source UNUSED) +{ +} + +static void odb_source_inmemory_reprepare(struct odb_source *source UNUSED) +{ +} + +static int inmemory_object_free(const struct object_id *oid UNUSED, + void *node_data, + void *cb_data UNUSED) +{ + struct inmemory_object *object = node_data; + free((void *) object->buf); + free(object); + return 0; +} + +static void odb_source_inmemory_free(struct odb_source *source) +{ + struct odb_source_inmemory *inmemory = odb_source_inmemory_downcast(source); + + if (inmemory->objects) { + struct object_id null_oid = { 0 }; + + oidtree_each(inmemory->objects, &null_oid, 0, + inmemory_object_free, NULL); + oidtree_clear(inmemory->objects); + free(inmemory->objects); + } + + free(inmemory->base.path); + free(inmemory); +} + +struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb) +{ + struct odb_source_inmemory *source; + + CALLOC_ARRAY(source, 1); + odb_source_init(&source->base, odb, ODB_SOURCE_INMEMORY, "source", false); + + source->base.free = odb_source_inmemory_free; + source->base.close = odb_source_inmemory_close; + source->base.reprepare = odb_source_inmemory_reprepare; + source->base.read_object_info = odb_source_inmemory_read_object_info; + source->base.read_object_stream = odb_source_inmemory_read_object_stream; + source->base.for_each_object = odb_source_inmemory_for_each_object; + source->base.find_abbrev_len = odb_source_inmemory_find_abbrev_len; + source->base.count_objects = odb_source_inmemory_count_objects; + source->base.write_object = odb_source_inmemory_write_object; + source->base.write_object_stream = odb_source_inmemory_write_object_stream; + source->base.freshen_object = odb_source_inmemory_freshen_object; + source->base.begin_transaction = odb_source_inmemory_begin_transaction; + source->base.read_alternates = odb_source_inmemory_read_alternates; + source->base.write_alternate = odb_source_inmemory_write_alternate; + + return source; +}
diff --git a/odb/source-inmemory.h b/odb/source-inmemory.h new file mode 100644 index 0000000..a88fc2e --- /dev/null +++ b/odb/source-inmemory.h
@@ -0,0 +1,33 @@ +#ifndef ODB_SOURCE_INMEMORY_H +#define ODB_SOURCE_INMEMORY_H + +#include "odb/source.h" + +struct oidtree; + +/* + * An in-memory source that you can write objects to that shall be made + * available for reading, but that shouldn't ever be persisted to disk. Note + * that any objects written to this source will be stored in memory, so the + * number of objects you can store is limited by available system memory. + */ +struct odb_source_inmemory { + struct odb_source base; + struct oidtree *objects; +}; + +/* Create a new in-memory object database source. */ +struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb); + +/* + * Cast the given object database source to the in-memory backend. This will + * cause a BUG in case the source doesn't use this backend. + */ +static inline struct odb_source_inmemory *odb_source_inmemory_downcast(struct odb_source *source) +{ + if (source->type != ODB_SOURCE_INMEMORY) + BUG("trying to downcast source of type '%d' to in-memory", source->type); + return container_of(source, struct odb_source_inmemory, base); +} + +#endif
diff --git a/odb/source-loose.c b/odb/source-loose.c new file mode 100644 index 0000000..7d7ea2f --- /dev/null +++ b/odb/source-loose.c
@@ -0,0 +1,736 @@ +#include "git-compat-util.h" +#include "abspath.h" +#include "chdir-notify.h" +#include "gettext.h" +#include "hex.h" +#include "loose.h" +#include "object-file.h" +#include "object-file-convert.h" +#include "odb.h" +#include "odb/source-files.h" +#include "odb/source-loose.h" +#include "odb/streaming.h" +#include "oidtree.h" +#include "repository.h" +#include "strbuf.h" + +static int append_loose_object(const struct object_id *oid, + const char *path UNUSED, + void *data) +{ + oidtree_insert(data, oid, NULL); + return 0; +} + +static struct oidtree *odb_source_loose_cache(struct odb_source_loose *loose, + const struct object_id *oid) +{ + int subdir_nr = oid->hash[0]; + struct strbuf buf = STRBUF_INIT; + size_t word_bits = bitsizeof(loose->subdir_seen[0]); + size_t word_index = subdir_nr / word_bits; + size_t mask = (size_t)1u << (subdir_nr % word_bits); + uint32_t *bitmap; + + if (subdir_nr < 0 || + (size_t) subdir_nr >= bitsizeof(loose->subdir_seen)) + BUG("subdir_nr out of range"); + + bitmap = &loose->subdir_seen[word_index]; + if (*bitmap & mask) + return loose->cache; + if (!loose->cache) { + ALLOC_ARRAY(loose->cache, 1); + oidtree_init(loose->cache); + } + strbuf_addstr(&buf, loose->base.path); + for_each_file_in_obj_subdir(subdir_nr, &buf, + loose->base.odb->repo->hash_algo, + append_loose_object, + NULL, NULL, + loose->cache); + *bitmap |= mask; + strbuf_release(&buf); + return loose->cache; +} + +static int quick_has_loose(struct odb_source_loose *loose, + const struct object_id *oid) +{ + return !!oidtree_contains(odb_source_loose_cache(loose, oid), oid); +} + +static int read_object_info_from_path(struct odb_source_loose *loose, + const char *path, + const struct object_id *oid, + struct object_info *oi, + enum object_info_flags flags) +{ + int ret; + int fd; + unsigned long mapsize; + void *map = NULL; + git_zstream stream, *stream_to_end = NULL; + char hdr[MAX_HEADER_LEN]; + unsigned long size_scratch; + enum object_type type_scratch; + struct stat st; + + /* + * If we don't care about type or size, then we don't + * need to look inside the object at all. Note that we + * do not optimize out the stat call, even if the + * caller doesn't care about the disk-size, since our + * return value implicitly indicates whether the + * object even exists. + */ + if (!oi || (!oi->typep && !oi->sizep && !oi->contentp)) { + struct stat st; + + if ((!oi || (!oi->disk_sizep && !oi->mtimep)) && (flags & OBJECT_INFO_QUICK)) { + ret = quick_has_loose(loose, oid) ? 0 : -1; + goto out; + } + + if (lstat(path, &st) < 0) { + ret = -1; + goto out; + } + + if (oi) { + if (oi->disk_sizep) + *oi->disk_sizep = st.st_size; + if (oi->mtimep) + *oi->mtimep = st.st_mtime; + } + + ret = 0; + goto out; + } + + fd = git_open(path); + if (fd < 0) { + if (errno != ENOENT) + error_errno(_("unable to open loose object %s"), oid_to_hex(oid)); + ret = -1; + goto out; + } + + if (fstat(fd, &st)) { + close(fd); + ret = -1; + goto out; + } + + mapsize = xsize_t(st.st_size); + if (!mapsize) { + close(fd); + ret = error(_("object file %s is empty"), path); + goto out; + } + + map = xmmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); + if (!map) { + ret = -1; + goto out; + } + + if (oi->disk_sizep) + *oi->disk_sizep = mapsize; + if (oi->mtimep) + *oi->mtimep = st.st_mtime; + + stream_to_end = &stream; + + switch (unpack_loose_header(&stream, map, mapsize, hdr, sizeof(hdr))) { + case ULHR_OK: + if (!oi->sizep) + oi->sizep = &size_scratch; + if (!oi->typep) + oi->typep = &type_scratch; + + if (parse_loose_header(hdr, oi) < 0) { + ret = error(_("unable to parse %s header"), oid_to_hex(oid)); + goto corrupt; + } + + if (*oi->typep < 0) + die(_("invalid object type")); + + if (oi->contentp) { + *oi->contentp = unpack_loose_rest(&stream, hdr, *oi->sizep, oid); + if (!*oi->contentp) { + ret = -1; + goto corrupt; + } + } + + break; + case ULHR_BAD: + ret = error(_("unable to unpack %s header"), + oid_to_hex(oid)); + goto corrupt; + case ULHR_TOO_LONG: + ret = error(_("header for %s too long, exceeds %d bytes"), + oid_to_hex(oid), MAX_HEADER_LEN); + goto corrupt; + } + + ret = 0; + +corrupt: + if (ret && (flags & OBJECT_INFO_DIE_IF_CORRUPT)) + die(_("loose object %s (stored in %s) is corrupt"), + oid_to_hex(oid), path); + +out: + if (stream_to_end) + git_inflate_end(stream_to_end); + if (map) + munmap(map, mapsize); + if (oi) { + if (oi->sizep == &size_scratch) + oi->sizep = NULL; + if (oi->typep == &type_scratch) + oi->typep = NULL; + if (oi->delta_base_oid) + oidclr(oi->delta_base_oid, loose->base.odb->repo->hash_algo); + if (!ret) + oi->whence = OI_LOOSE; + } + + return ret; +} + +static int odb_source_loose_read_object_info(struct odb_source *source, + const struct object_id *oid, + struct object_info *oi, + enum object_info_flags flags) +{ + struct odb_source_loose *loose = odb_source_loose_downcast(source); + static struct strbuf buf = STRBUF_INIT; + + /* + * The second read shouldn't cause new loose objects to show up, unless + * there was a race condition with a secondary process. We don't care + * about this case though, so we simply skip reading loose objects a + * second time. + */ + if (flags & OBJECT_INFO_SECOND_READ) + return -1; + + odb_loose_path(loose, &buf, oid); + return read_object_info_from_path(loose, buf.buf, oid, oi, flags); +} + +/* + * Find "oid" as a loose object in given source, open the object and return its + * file descriptor. Returns the file descriptor on success, negative on failure. + * + * The "path" out-parameter will give the path of the object we found (if any). + * Note that it may point to static storage and is only valid until another + * call to open_loose_object(). + */ +static int open_loose_object(struct odb_source_loose *loose, + const struct object_id *oid, const char **path) +{ + static struct strbuf buf = STRBUF_INIT; + int fd; + + *path = odb_loose_path(loose, &buf, oid); + fd = git_open(*path); + if (fd >= 0) + return fd; + + return -1; +} + +static void *odb_source_loose_map_object(struct odb_source_loose *loose, + const struct object_id *oid, + unsigned long *size) +{ + const char *p; + int fd = open_loose_object(loose, oid, &p); + void *map = NULL; + struct stat st; + + if (fd < 0) + return NULL; + + if (!fstat(fd, &st)) { + *size = xsize_t(st.st_size); + if (!*size) { + /* mmap() is forbidden on empty files */ + error(_("object file %s is empty"), p); + goto out; + } + + map = xmmap(NULL, *size, PROT_READ, MAP_PRIVATE, fd, 0); + } + +out: + close(fd); + return map; +} + +struct odb_loose_read_stream { + struct odb_read_stream base; + git_zstream z; + enum { + ODB_LOOSE_READ_STREAM_INUSE, + ODB_LOOSE_READ_STREAM_DONE, + ODB_LOOSE_READ_STREAM_ERROR, + } z_state; + void *mapped; + unsigned long mapsize; + char hdr[32]; + int hdr_avail; + int hdr_used; +}; + +static ssize_t read_istream_loose(struct odb_read_stream *_st, char *buf, size_t sz) +{ + struct odb_loose_read_stream *st = + container_of(_st, struct odb_loose_read_stream, base); + size_t total_read = 0; + + switch (st->z_state) { + case ODB_LOOSE_READ_STREAM_DONE: + return 0; + case ODB_LOOSE_READ_STREAM_ERROR: + return -1; + default: + break; + } + + if (st->hdr_used < st->hdr_avail) { + size_t to_copy = st->hdr_avail - st->hdr_used; + if (sz < to_copy) + to_copy = sz; + memcpy(buf, st->hdr + st->hdr_used, to_copy); + st->hdr_used += to_copy; + total_read += to_copy; + } + + while (total_read < sz) { + int status; + + st->z.next_out = (unsigned char *)buf + total_read; + st->z.avail_out = sz - total_read; + status = git_inflate(&st->z, Z_FINISH); + + total_read = st->z.next_out - (unsigned char *)buf; + + if (status == Z_STREAM_END) { + git_inflate_end(&st->z); + st->z_state = ODB_LOOSE_READ_STREAM_DONE; + break; + } + if (status != Z_OK && (status != Z_BUF_ERROR || total_read < sz)) { + git_inflate_end(&st->z); + st->z_state = ODB_LOOSE_READ_STREAM_ERROR; + return -1; + } + } + return total_read; +} + +static int close_istream_loose(struct odb_read_stream *_st) +{ + struct odb_loose_read_stream *st = + container_of(_st, struct odb_loose_read_stream, base); + + if (st->z_state == ODB_LOOSE_READ_STREAM_INUSE) + git_inflate_end(&st->z); + munmap(st->mapped, st->mapsize); + return 0; +} + +static int odb_source_loose_read_object_stream(struct odb_read_stream **out, + struct odb_source *source, + const struct object_id *oid) +{ + struct odb_source_loose *loose = odb_source_loose_downcast(source); + struct object_info oi = OBJECT_INFO_INIT; + struct odb_loose_read_stream *st; + unsigned long mapsize; + unsigned long size_ul; + void *mapped; + + mapped = odb_source_loose_map_object(loose, oid, &mapsize); + if (!mapped) + return -1; + + /* + * Note: we must allocate this structure early even though we may still + * fail. This is because we need to initialize the zlib stream, and it + * is not possible to copy the stream around after the fact because it + * has self-referencing pointers. + */ + CALLOC_ARRAY(st, 1); + + switch (unpack_loose_header(&st->z, mapped, mapsize, st->hdr, + sizeof(st->hdr))) { + case ULHR_OK: + break; + case ULHR_BAD: + case ULHR_TOO_LONG: + goto error; + } + + /* + * object_info.sizep is unsigned long* (32-bit on Windows), but + * st->base.size is size_t (64-bit). Use temporary variable. + * Note: loose objects >4GB would still truncate here, but such + * large loose objects are uncommon (they'd normally be packed). + */ + oi.sizep = &size_ul; + oi.typep = &st->base.type; + + if (parse_loose_header(st->hdr, &oi) < 0 || st->base.type < 0) + goto error; + st->base.size = size_ul; + + st->mapped = mapped; + st->mapsize = mapsize; + st->hdr_used = strlen(st->hdr) + 1; + st->hdr_avail = st->z.total_out; + st->z_state = ODB_LOOSE_READ_STREAM_INUSE; + st->base.close = close_istream_loose; + st->base.read = read_istream_loose; + + *out = &st->base; + + return 0; +error: + git_inflate_end(&st->z); + munmap(mapped, mapsize); + free(st); + return -1; +} + +struct for_each_object_wrapper_data { + struct odb_source_loose *loose; + const struct object_info *request; + odb_for_each_object_cb cb; + void *cb_data; +}; + +static int for_each_object_wrapper_cb(const struct object_id *oid, + const char *path, + void *cb_data) +{ + struct for_each_object_wrapper_data *data = cb_data; + + if (data->request) { + struct object_info oi = *data->request; + + if (read_object_info_from_path(data->loose, path, oid, &oi, 0) < 0) + return -1; + + return data->cb(oid, &oi, data->cb_data); + } else { + return data->cb(oid, NULL, data->cb_data); + } +} + +static int for_each_prefixed_object_wrapper_cb(const struct object_id *oid, + void *node_data UNUSED, + void *cb_data) +{ + struct for_each_object_wrapper_data *data = cb_data; + if (data->request) { + struct object_info oi = *data->request; + + if (odb_source_read_object_info(&data->loose->base, + oid, &oi, 0) < 0) + return -1; + + return data->cb(oid, &oi, data->cb_data); + } else { + return data->cb(oid, NULL, data->cb_data); + } +} + +static int odb_source_loose_for_each_object(struct odb_source *source, + const struct object_info *request, + odb_for_each_object_cb cb, + void *cb_data, + const struct odb_for_each_object_options *opts) +{ + struct odb_source_loose *loose = odb_source_loose_downcast(source); + struct for_each_object_wrapper_data data = { + .loose = loose, + .request = request, + .cb = cb, + .cb_data = cb_data, + }; + + /* There are no loose promisor objects, so we can return immediately. */ + if ((opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY)) + return 0; + if ((opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !source->local) + return 0; + + if (opts->prefix) + return oidtree_each(odb_source_loose_cache(loose, opts->prefix), + opts->prefix, opts->prefix_hex_len, + for_each_prefixed_object_wrapper_cb, &data); + + return for_each_loose_file_in_source(source, for_each_object_wrapper_cb, + NULL, NULL, &data); +} + +struct find_abbrev_len_data { + const struct object_id *oid; + unsigned len; +}; + +static int find_abbrev_len_cb(const struct object_id *oid, + struct object_info *oi UNUSED, + void *cb_data) +{ + struct find_abbrev_len_data *data = cb_data; + unsigned len = oid_common_prefix_hexlen(oid, data->oid); + if (len != hash_algos[oid->algo].hexsz && len >= data->len) + data->len = len + 1; + return 0; +} + +static int odb_source_loose_find_abbrev_len(struct odb_source *source, + const struct object_id *oid, + unsigned min_len, + unsigned *out) +{ + struct odb_source_loose *loose = odb_source_loose_downcast(source); + struct odb_for_each_object_options opts = { + .prefix = oid, + .prefix_hex_len = min_len, + }; + struct find_abbrev_len_data data = { + .oid = oid, + .len = min_len, + }; + int ret; + + ret = odb_source_for_each_object(&loose->base, NULL, find_abbrev_len_cb, + &data, &opts); + *out = data.len; + + return ret; +} + +static int count_loose_object(const struct object_id *oid UNUSED, + struct object_info *oi UNUSED, + void *payload) +{ + unsigned long *count = payload; + (*count)++; + return 0; +} + +static int odb_source_loose_count_objects(struct odb_source *source, + enum odb_count_objects_flags flags, + unsigned long *out) +{ + struct odb_source_loose *loose = odb_source_loose_downcast(source); + const unsigned hexsz = source->odb->repo->hash_algo->hexsz - 2; + char *path = NULL; + DIR *dir = NULL; + int ret; + + if (flags & ODB_COUNT_OBJECTS_APPROXIMATE) { + unsigned long count = 0; + struct dirent *ent; + + path = xstrfmt("%s/17", source->path); + + dir = opendir(path); + if (!dir) { + if (errno == ENOENT) { + *out = 0; + ret = 0; + goto out; + } + + ret = error_errno("cannot open object shard '%s'", path); + goto out; + } + + while ((ent = readdir(dir)) != NULL) { + if (strspn(ent->d_name, "0123456789abcdef") != hexsz || + ent->d_name[hexsz] != '\0') + continue; + count++; + } + + *out = count * 256; + ret = 0; + } else { + struct odb_for_each_object_options opts = { 0 }; + *out = 0; + ret = odb_source_for_each_object(&loose->base, NULL, count_loose_object, + out, &opts); + } + +out: + if (dir) + closedir(dir); + free(path); + return ret; +} + +static int odb_source_loose_freshen_object(struct odb_source *source, + const struct object_id *oid) +{ + struct odb_source_loose *loose = odb_source_loose_downcast(source); + static struct strbuf path = STRBUF_INIT; + odb_loose_path(loose, &path, oid); + return !!check_and_freshen_file(path.buf, 1); +} + +static int odb_source_loose_write_object(struct odb_source *source, + const void *buf, unsigned long len, + enum object_type type, struct object_id *oid, + struct object_id *compat_oid_in, + enum odb_write_object_flags flags) +{ + struct odb_source_loose *loose = odb_source_loose_downcast(source); + const struct git_hash_algo *algo = source->odb->repo->hash_algo; + const struct git_hash_algo *compat = source->odb->repo->compat_hash_algo; + struct object_id compat_oid; + char hdr[MAX_HEADER_LEN]; + int hdrlen = sizeof(hdr); + + /* Generate compat_oid */ + if (compat) { + if (compat_oid_in) + oidcpy(&compat_oid, compat_oid_in); + else if (type == OBJ_BLOB) + hash_object_file(compat, buf, len, type, &compat_oid); + else { + struct strbuf converted = STRBUF_INIT; + convert_object_file(source->odb->repo, &converted, algo, compat, + buf, len, type, 0); + hash_object_file(compat, converted.buf, converted.len, + type, &compat_oid); + strbuf_release(&converted); + } + } + + /* Normally if we have it in the pack then we do not bother writing + * it out into .git/objects/??/?{38} file. + */ + write_object_file_prepare(algo, buf, len, type, oid, hdr, &hdrlen); + if (odb_freshen_object(source->odb, oid)) + return 0; + if (write_loose_object(loose, oid, hdr, hdrlen, buf, len, 0, flags)) + return -1; + if (compat) + return repo_add_loose_object_map(loose, oid, &compat_oid); + return 0; +} + +static int odb_source_loose_write_object_stream(struct odb_source *source, + struct odb_write_stream *in_stream, + size_t len, + struct object_id *oid) +{ + /* + * TODO: the implementation should be moved here, see the comment on + * the called function in "object-file.h". + */ + struct odb_source_loose *loose = odb_source_loose_downcast(source); + return odb_source_loose_write_stream(loose, in_stream, len, oid); +} + +static int odb_source_loose_begin_transaction(struct odb_source *source UNUSED, + struct odb_transaction **out UNUSED) +{ + /* TODO: this is a known omission that we'll want to address eventually. */ + return error("loose source does not support transactions"); +} + +static int odb_source_loose_read_alternates(struct odb_source *source UNUSED, + struct strvec *out UNUSED) +{ + return 0; +} + +static int odb_source_loose_write_alternate(struct odb_source *source UNUSED, + const char *alternate UNUSED) +{ + return error("loose source does not support alternates"); +} + +static void odb_source_loose_clear_cache(struct odb_source_loose *loose) +{ + oidtree_clear(loose->cache); + FREE_AND_NULL(loose->cache); + memset(&loose->subdir_seen, 0, + sizeof(loose->subdir_seen)); +} + +static void odb_source_loose_reprepare(struct odb_source *source) +{ + struct odb_source_loose *loose = odb_source_loose_downcast(source); + odb_source_loose_clear_cache(loose); +} + +static void odb_source_loose_close(struct odb_source *source UNUSED) +{ + /* Nothing to do. */ +} + +static void odb_source_loose_reparent(const char *name UNUSED, + const char *old_cwd, + const char *new_cwd, + void *cb_data) +{ + struct odb_source_loose *loose = cb_data; + char *path = reparent_relative_path(old_cwd, new_cwd, + loose->base.path); + free(loose->base.path); + loose->base.path = path; +} + +static void odb_source_loose_free(struct odb_source *source) +{ + struct odb_source_loose *loose = odb_source_loose_downcast(source); + odb_source_loose_clear_cache(loose); + loose_object_map_clear(&loose->map); + chdir_notify_unregister(NULL, odb_source_loose_reparent, loose); + odb_source_release(&loose->base); + free(loose); +} + +struct odb_source_loose *odb_source_loose_new(struct object_database *odb, + const char *path, + bool local) +{ + struct odb_source_loose *loose; + + CALLOC_ARRAY(loose, 1); + odb_source_init(&loose->base, odb, ODB_SOURCE_LOOSE, path, local); + + loose->base.free = odb_source_loose_free; + loose->base.close = odb_source_loose_close; + loose->base.reprepare = odb_source_loose_reprepare; + loose->base.read_object_info = odb_source_loose_read_object_info; + loose->base.read_object_stream = odb_source_loose_read_object_stream; + loose->base.for_each_object = odb_source_loose_for_each_object; + loose->base.find_abbrev_len = odb_source_loose_find_abbrev_len; + loose->base.count_objects = odb_source_loose_count_objects; + loose->base.freshen_object = odb_source_loose_freshen_object; + loose->base.write_object = odb_source_loose_write_object; + loose->base.write_object_stream = odb_source_loose_write_object_stream; + loose->base.begin_transaction = odb_source_loose_begin_transaction; + loose->base.read_alternates = odb_source_loose_read_alternates; + loose->base.write_alternate = odb_source_loose_write_alternate; + + if (!is_absolute_path(loose->base.path)) + chdir_notify_register(NULL, odb_source_loose_reparent, loose); + + return loose; +}
diff --git a/odb/source-loose.h b/odb/source-loose.h new file mode 100644 index 0000000..6070aaf --- /dev/null +++ b/odb/source-loose.h
@@ -0,0 +1,48 @@ +#ifndef ODB_SOURCE_LOOSE_H +#define ODB_SOURCE_LOOSE_H + +#include "odb/source.h" + +struct odb_source_files; +struct object_database; +struct oidtree; + +/* + * An object database source that stores its objects in loose format, one + * file per object. + */ +struct odb_source_loose { + struct odb_source base; + + /* + * Used to store the results of readdir(3) calls when we are OK + * sacrificing accuracy due to races for speed. That includes + * object existence with OBJECT_INFO_QUICK, as well as + * our search for unique abbreviated hashes. Don't use it for tasks + * requiring greater accuracy! + * + * Be sure to call odb_load_loose_cache() before using. + */ + uint32_t subdir_seen[8]; /* 256 bits */ + struct oidtree *cache; + + /* Map between object IDs for loose objects. */ + struct loose_object_map *map; +}; + +struct odb_source_loose *odb_source_loose_new(struct object_database *odb, + const char *path, + bool local); + +/* + * Cast the given object database source to the loose backend. This will cause + * a BUG in case the source doesn't use this backend. + */ +static inline struct odb_source_loose *odb_source_loose_downcast(struct odb_source *source) +{ + if (source->type != ODB_SOURCE_LOOSE) + BUG("trying to downcast source of type '%d' to loose", source->type); + return container_of(source, struct odb_source_loose, base); +} + +#endif
diff --git a/odb/source.h b/odb/source.h index f706e06..2192a10 100644 --- a/odb/source.h +++ b/odb/source.h
@@ -13,6 +13,12 @@ enum odb_source_type { /* The "files" backend that uses loose objects and packfiles. */ ODB_SOURCE_FILES, + + /* The "loose" backend that uses loose objects, only. */ + ODB_SOURCE_LOOSE, + + /* The "in-memory" backend that stores objects in memory. */ + ODB_SOURCE_INMEMORY, }; struct object_id; @@ -338,7 +344,7 @@ static inline int odb_source_read_object_stream(struct odb_read_stream **out, * are only iterated over once. * * The optional `request` structure serves as a template for retrieving the - * object info for each indvidual iterated object and will be populated as if + * object info for each individual iterated object and will be populated as if * `odb_source_read_object_info()` was called on the object. It will not be * modified, the callback will instead be invoked with a separate `struct * object_info` for every object. Object info will not be read when passing a
diff --git a/odb/streaming.c b/odb/streaming.c index af2adf5..7602a8d 100644 --- a/odb/streaming.c +++ b/odb/streaming.c
@@ -243,6 +243,16 @@ struct odb_read_stream *odb_read_stream_open(struct object_database *odb, return st; } +ssize_t odb_write_stream_read(struct odb_write_stream *st, void *buf, size_t sz) +{ + return st->read(st, buf, sz); +} + +void odb_write_stream_release(struct odb_write_stream *st) +{ + free(st->data); +} + int odb_stream_blob_to_fd(struct object_database *odb, int fd, const struct object_id *oid, @@ -298,3 +308,44 @@ int odb_stream_blob_to_fd(struct object_database *odb, odb_read_stream_close(st); return result; } + +struct read_object_fd_data { + int fd; + size_t remaining; +}; + +static ssize_t read_object_fd(struct odb_write_stream *stream, + unsigned char *buf, size_t len) +{ + struct read_object_fd_data *data = stream->data; + ssize_t read_result; + size_t count; + + if (stream->is_finished) + return 0; + + count = data->remaining < len ? data->remaining : len; + read_result = read_in_full(data->fd, buf, count); + if (read_result < 0 || (size_t)read_result != count) + return -1; + + data->remaining -= count; + if (!data->remaining) + stream->is_finished = 1; + + return read_result; +} + +void odb_write_stream_from_fd(struct odb_write_stream *stream, int fd, + size_t size) +{ + struct read_object_fd_data *data; + + CALLOC_ARRAY(data, 1); + data->fd = fd; + data->remaining = size; + + stream->data = data; + stream->read = read_object_fd; + stream->is_finished = 0; +}
diff --git a/odb/streaming.h b/odb/streaming.h index 517e2ea..c023671 100644 --- a/odb/streaming.h +++ b/odb/streaming.h
@@ -5,6 +5,7 @@ #define STREAMING_H 1 #include "object.h" +#include "odb.h" struct object_database; struct odb_read_stream; @@ -48,6 +49,29 @@ int odb_read_stream_close(struct odb_read_stream *stream); ssize_t odb_read_stream_read(struct odb_read_stream *stream, void *buf, size_t len); /* + * A stream that provides an object to be written to the object database without + * loading all of it into memory. + */ +struct odb_write_stream { + ssize_t (*read)(struct odb_write_stream *, unsigned char *, size_t); + void *data; + int is_finished; +}; + +/* + * Read data from the stream into the buffer. Returns 0 when finished and the + * number of bytes read on success. Returns a negative error code in case + * reading from the stream fails. + */ +ssize_t odb_write_stream_read(struct odb_write_stream *stream, void *buf, + size_t len); + +/* + * Releases memory allocated for underlying stream data. + */ +void odb_write_stream_release(struct odb_write_stream *stream); + +/* * Look up the object by its ID and write the full contents to the file * descriptor. The object must be a blob, or the function will fail. When * provided, the filter is used to transform the blob contents. @@ -64,4 +88,10 @@ int odb_stream_blob_to_fd(struct object_database *odb, struct stream_filter *filter, int can_seek); +/* + * Sets up an ODB write stream that reads from an fd. + */ +void odb_write_stream_from_fd(struct odb_write_stream *stream, int fd, + size_t size); + #endif /* STREAMING_H */
diff --git a/odb/transaction.c b/odb/transaction.c new file mode 100644 index 0000000..b16e07a --- /dev/null +++ b/odb/transaction.c
@@ -0,0 +1,35 @@ +#include "git-compat-util.h" +#include "odb/source.h" +#include "odb/transaction.h" + +struct odb_transaction *odb_transaction_begin(struct object_database *odb) +{ + if (odb->transaction) + return NULL; + + odb_source_begin_transaction(odb->sources, &odb->transaction); + + return odb->transaction; +} + +void odb_transaction_commit(struct odb_transaction *transaction) +{ + if (!transaction) + return; + + /* + * Ensure the transaction ending matches the pending transaction. + */ + ASSERT(transaction == transaction->source->odb->transaction); + + transaction->commit(transaction); + transaction->source->odb->transaction = NULL; + free(transaction); +} + +int odb_transaction_write_object_stream(struct odb_transaction *transaction, + struct odb_write_stream *stream, + size_t len, struct object_id *oid) +{ + return transaction->write_object_stream(transaction, stream, len, oid); +}
diff --git a/odb/transaction.h b/odb/transaction.h new file mode 100644 index 0000000..854fda0 --- /dev/null +++ b/odb/transaction.h
@@ -0,0 +1,57 @@ +#ifndef ODB_TRANSACTION_H +#define ODB_TRANSACTION_H + +#include "odb.h" +#include "odb/source.h" + +/* + * A transaction may be started for an object database prior to writing new + * objects via odb_transaction_begin(). These objects are not committed until + * odb_transaction_commit() is invoked. Only a single transaction may be pending + * at a time. + * + * Each ODB source is expected to implement its own transaction handling. + */ +struct odb_transaction { + /* The ODB source the transaction is opened against. */ + struct odb_source *source; + + /* The ODB source specific callback invoked to commit a transaction. */ + void (*commit)(struct odb_transaction *transaction); + + /* + * This callback is expected to write the given object stream into + * the ODB transaction. Note that for now, only blobs support streaming. + * + * The resulting object ID shall be written into the out pointer. The + * callback is expected to return 0 on success, a negative error code + * otherwise. + */ + int (*write_object_stream)(struct odb_transaction *transaction, + struct odb_write_stream *stream, size_t len, + struct object_id *oid); +}; + +/* + * Starts an ODB transaction. Subsequent objects are written to the transaction + * and not committed until odb_transaction_commit() is invoked on the + * transaction. If the ODB already has a pending transaction, NULL is returned. + */ +struct odb_transaction *odb_transaction_begin(struct object_database *odb); + +/* + * Commits an ODB transaction making the written objects visible. If the + * specified transaction is NULL, the function is a no-op. + */ +void odb_transaction_commit(struct odb_transaction *transaction); + +/* + * Writes the object in the provided stream into the transaction. The resulting + * object ID is written into the out pointer. Returns 0 on success, a negative + * error code otherwise. + */ +int odb_transaction_write_object_stream(struct odb_transaction *transaction, + struct odb_write_stream *stream, + size_t len, struct object_id *oid); + +#endif
diff --git a/oidtree.c b/oidtree.c index ab9fe7e..e43f180 100644 --- a/oidtree.c +++ b/oidtree.c
@@ -6,9 +6,15 @@ #include "oidtree.h" #include "hash.h" +struct oidtree_node { + struct cb_node base; + struct object_id key; + void *data; +}; + void oidtree_init(struct oidtree *ot) { - cb_init(&ot->tree); + cb_init(&ot->tree, offsetof(struct oidtree_node, key)); mem_pool_init(&ot->mem_pool, 0); } @@ -20,22 +26,22 @@ void oidtree_clear(struct oidtree *ot) } } -void oidtree_insert(struct oidtree *ot, const struct object_id *oid) +struct oidtree_data { + struct object_id oid; +}; + +void oidtree_insert(struct oidtree *ot, const struct object_id *oid, + void *data) { - struct cb_node *on; - struct object_id k; + struct oidtree_node *on; + struct cb_node *node; if (!oid->algo) BUG("oidtree_insert requires oid->algo"); - on = mem_pool_alloc(&ot->mem_pool, sizeof(*on) + sizeof(*oid)); - - /* - * Clear the padding and copy the result in separate steps to - * respect the 4-byte alignment needed by struct object_id. - */ - oidcpy(&k, oid); - memcpy(on->k, &k, sizeof(k)); + on = mem_pool_alloc(&ot->mem_pool, sizeof(*on)); + oidcpy(&on->key, oid); + on->data = data; /* * n.b. Current callers won't get us duplicates, here. If a @@ -43,13 +49,19 @@ void oidtree_insert(struct oidtree *ot, const struct object_id *oid) * that won't be freed until oidtree_clear. Currently it's not * worth maintaining a free list */ - cb_insert(&ot->tree, on, sizeof(*oid)); + node = cb_insert(&ot->tree, &on->base, sizeof(*oid)); + if (node) { + struct oidtree_node *preexisting = container_of(node, struct oidtree_node, base); + preexisting->data = data; + } } -bool oidtree_contains(struct oidtree *ot, const struct object_id *oid) +static struct oidtree_node *oidtree_lookup(struct oidtree *ot, + const struct object_id *oid) { struct object_id k; size_t klen = sizeof(k); + struct cb_node *node; oidcpy(&k, oid); @@ -60,7 +72,20 @@ bool oidtree_contains(struct oidtree *ot, const struct object_id *oid) 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); + node = cb_lookup(&ot->tree, (const uint8_t *)&k, klen); + return node ? container_of(node, struct oidtree_node, base) : NULL; +} + +bool oidtree_contains(struct oidtree *ot, const struct object_id *oid) +{ + struct oidtree_node *node = oidtree_lookup(ot, oid); + return node ? 1 : 0; +} + +void *oidtree_get(struct oidtree *ot, const struct object_id *oid) +{ + struct oidtree_node *node = oidtree_lookup(ot, oid); + return node ? node->data : NULL; } struct oidtree_each_data { @@ -73,21 +98,18 @@ struct oidtree_each_data { static int iter(struct cb_node *n, void *cb_data) { + struct oidtree_node *node = container_of(n, struct oidtree_node, base); struct oidtree_each_data *data = cb_data; - struct object_id k; - /* Copy to provide 4-byte alignment needed by struct object_id. */ - memcpy(&k, n->k, sizeof(k)); - - if (data->algo != GIT_HASH_UNKNOWN && data->algo != k.algo) + if (data->algo != GIT_HASH_UNKNOWN && data->algo != node->key.algo) return 0; if (data->last_nibble_at) { - if ((k.hash[*data->last_nibble_at] ^ data->last_byte) & 0xf0) + if ((node->key.hash[*data->last_nibble_at] ^ data->last_byte) & 0xf0) return 0; } - return data->cb(&k, data->cb_data); + return data->cb(&node->key, node->data, data->cb_data); } int oidtree_each(struct oidtree *ot, const struct object_id *prefix,
diff --git a/oidtree.h b/oidtree.h index 2b7bad2..baa5a43 100644 --- a/oidtree.h +++ b/oidtree.h
@@ -29,18 +29,26 @@ void oidtree_init(struct oidtree *ot); */ void oidtree_clear(struct oidtree *ot); -/* Insert the object ID into the tree. */ -void oidtree_insert(struct oidtree *ot, const struct object_id *oid); +/* + * Insert the object ID into the tree and store the given pointer alongside + * with it. The data pointer of any preexisting entry will be overwritten. + */ +void oidtree_insert(struct oidtree *ot, const struct object_id *oid, + void *data); /* Check whether the tree contains the given object ID. */ bool oidtree_contains(struct oidtree *ot, const struct object_id *oid); +/* Get the payload stored with the given object ID. */ +void *oidtree_get(struct oidtree *ot, const struct object_id *oid); + /* * Callback function used for `oidtree_each()`. Returning a non-zero exit code * will cause iteration to stop. The exit code will be propagated to the caller * of `oidtree_each()`. */ typedef int (*oidtree_each_cb)(const struct object_id *oid, + void *node_data, void *cb_data); /*
diff --git a/pack-bitmap-write.c b/pack-bitmap-write.c index 8338d72..1bcb3f9 100644 --- a/pack-bitmap-write.c +++ b/pack-bitmap-write.c
@@ -32,6 +32,7 @@ struct bitmapped_commit { struct commit *commit; struct ewah_bitmap *bitmap; struct ewah_bitmap *write_as; + struct ewah_bitmap *pseudo_merge_parents; int flags; int xor_offset; uint32_t commit_pos; @@ -89,6 +90,7 @@ void bitmap_writer_free(struct bitmap_writer *writer) ewah_free(writer->tags); kh_destroy_oid_map(writer->bitmaps); + free(writer->pos_cache); kh_foreach_value(writer->pseudo_merge_commits, idx, free_pseudo_merge_commit_idx(idx)); @@ -101,6 +103,7 @@ void bitmap_writer_free(struct bitmap_writer *writer) if (bc->write_as != bc->bitmap) ewah_free(bc->write_as); ewah_free(bc->bitmap); + ewah_free(bc->pseudo_merge_parents); } free(writer->selected); } @@ -209,38 +212,116 @@ void bitmap_writer_push_commit(struct bitmap_writer *writer, writer->selected[writer->selected_nr].write_as = NULL; writer->selected[writer->selected_nr].flags = 0; writer->selected[writer->selected_nr].pseudo_merge = pseudo_merge; + writer->selected[writer->selected_nr].pseudo_merge_parents = NULL; writer->selected_nr++; } +struct bitmap_pos_cache_entry { + struct object_id oid; + uint32_t pos; +}; + +#define BITMAP_POS_MIN_CACHE_SIZE (1U << 10) +#define BITMAP_POS_MAX_CACHE_SIZE (1U << 21) +#define BITMAP_POS_CACHE_VALID (1U << 31) + +static void bitmap_writer_init_pos_cache(struct bitmap_writer *writer) +{ + if (writer->pos_cache) + return; + + writer->pos_cache_nr = BITMAP_POS_MIN_CACHE_SIZE; + + while (writer->pos_cache_nr < writer->to_pack->nr_objects && + writer->pos_cache_nr < BITMAP_POS_MAX_CACHE_SIZE) + writer->pos_cache_nr <<= 1; + + CALLOC_ARRAY(writer->pos_cache, writer->pos_cache_nr); +} + +static size_t bitmap_writer_pos_cache_slot(struct bitmap_writer *writer, + const struct object_id *oid) +{ + return oidhash(oid) & (writer->pos_cache_nr - 1); +} + +static bool bitmap_writer_pos_cache_valid(struct bitmap_writer *writer, + size_t slot) +{ + return !!(writer->pos_cache[slot].pos & BITMAP_POS_CACHE_VALID); +} + +static int find_cached_object_pos(struct bitmap_writer *writer, + const struct object_id *oid, uint32_t *pos) +{ + size_t slot = bitmap_writer_pos_cache_slot(writer, oid); + + if (bitmap_writer_pos_cache_valid(writer, slot) && + oideq(&writer->pos_cache[slot].oid, oid)) { + writer->pos_cache_hits++; + *pos = writer->pos_cache[slot].pos & ~BITMAP_POS_CACHE_VALID; + return 1; + } + + writer->pos_cache_misses++; + return 0; +} + +static uint32_t store_cached_object_pos(struct bitmap_writer *writer, + const struct object_id *oid, + uint32_t pos) +{ + size_t slot; + + if (pos & BITMAP_POS_CACHE_VALID) + return pos; /* too large to cache */ + + slot = bitmap_writer_pos_cache_slot(writer, oid); + + oidcpy(&writer->pos_cache[slot].oid, oid); + writer->pos_cache[slot].pos = pos | BITMAP_POS_CACHE_VALID; + + return pos; +} + static uint32_t find_object_pos(struct bitmap_writer *writer, const struct object_id *oid, int *found) { struct object_entry *entry; + uint32_t pos; + + bitmap_writer_init_pos_cache(writer); + + if (find_cached_object_pos(writer, oid, &pos)) { + if (found) + *found = 1; + return pos; + } entry = packlist_find(writer->to_pack, oid); if (entry) { uint32_t base_objects = 0; + if (writer->midx) base_objects = writer->midx->num_objects + writer->midx->num_objects_in_base; - - if (found) - *found = 1; - return oe_in_pack_pos(writer->to_pack, entry) + base_objects; + pos = oe_in_pack_pos(writer->to_pack, entry) + base_objects; } else if (writer->midx) { - uint32_t at, pos; + uint32_t at; if (!bsearch_midx(oid, writer->midx, &at)) goto missing; if (midx_to_pack_pos(writer->midx, at, &pos) < 0) goto missing; - - if (found) - *found = 1; - return pos; + } else { + goto missing; } + if (found) + *found = 1; + return store_cached_object_pos(writer, oid, pos); + missing: if (found) *found = 0; @@ -249,11 +330,40 @@ static uint32_t find_object_pos(struct bitmap_writer *writer, return 0; } +static int bitmapped_commit_date_cmp(const void *_a, const void *_b) +{ + const struct bitmapped_commit *a = _a; + const struct bitmapped_commit *b = _b; + + if (a->commit->date < b->commit->date) + return -1; + if (a->commit->date > b->commit->date) + return 1; + return 0; +} + static void compute_xor_offsets(struct bitmap_writer *writer) { static const int MAX_XOR_OFFSET_SEARCH = 10; int i, next = 0; + int nr = bitmap_writer_nr_selected_commits(writer); + + if (nr > 1) { + QSORT(writer->selected, nr, bitmapped_commit_date_cmp); + + for (i = 0; i < nr; i++) { + struct bitmapped_commit *stored = &writer->selected[i]; + khiter_t hash_pos = kh_get_oid_map(writer->bitmaps, + stored->commit->object.oid); + + if (hash_pos == kh_end(writer->bitmaps)) + BUG("selected commit missing from bitmap map: %s", + oid_to_hex(&stored->commit->object.oid)); + + kh_value(writer->bitmaps, hash_pos) = stored; + } + } while (next < writer->selected_nr) { struct bitmapped_commit *stored = &writer->selected[next]; @@ -336,13 +446,17 @@ static void bitmap_builder_init(struct bitmap_builder *bb, revs.topo_order = 1; revs.first_parent_only = 1; - for (i = 0; i < writer->selected_nr; i++) { + for (i = 0; i < bitmap_writer_nr_selected_commits(writer); i++) { struct bitmapped_commit *bc = &writer->selected[i]; struct bb_commit *ent = bb_data_at(&bb->data, bc->commit); + if (bc->pseudo_merge) + BUG("unexpected pseudo-merge at %"PRIuMAX, + (uintmax_t)i); + ent->selected = 1; ent->maximal = 1; - ent->pseudo_merge = bc->pseudo_merge; + ent->pseudo_merge = 0; ent->idx = i; ent->commit_mask = bitmap_new(); @@ -456,22 +570,13 @@ static void bitmap_builder_clear(struct bitmap_builder *bb) static int fill_bitmap_tree(struct bitmap_writer *writer, struct bitmap *bitmap, - struct tree *tree) + struct tree *tree, + uint32_t pos) { int found; - uint32_t pos; struct tree_desc desc; struct name_entry entry; - /* - * If our bit is already set, then there is nothing to do. Both this - * tree and all of its children will be set. - */ - pos = find_object_pos(writer, &tree->object.oid, &found); - if (!found) - return -1; - if (bitmap_get(bitmap, pos)) - return 0; bitmap_set(bitmap, pos); if (repo_parse_tree(writer->repo, tree) < 0) @@ -482,8 +587,21 @@ static int fill_bitmap_tree(struct bitmap_writer *writer, while (tree_entry(&desc, &entry)) { switch (object_type(entry.mode)) { case OBJ_TREE: + pos = find_object_pos(writer, &entry.oid, &found); + if (!found) + return -1; + if (bitmap_get(bitmap, pos)) { + /* + * If our bit is already set, then there + * is nothing to do. Both this tree and + * all of its children will be set. + */ + break; + } + if (fill_bitmap_tree(writer, bitmap, - lookup_tree(writer->repo, &entry.oid)) < 0) + lookup_tree(writer->repo, + &entry.oid), pos) < 0) return -1; break; case OBJ_BLOB: @@ -504,6 +622,11 @@ static int fill_bitmap_tree(struct bitmap_writer *writer, static int reused_bitmaps_nr; static int reused_pseudo_merge_bitmaps_nr; +static int pseudo_merge_bitmap_nr; +static int pseudo_merge_bitmap_parents; + +static int fill_bitmap_commit_calls_nr; +static int fill_bitmap_commit_found_ancestor_nr; static int fill_bitmap_commit(struct bitmap_writer *writer, struct bb_commit *ent, @@ -514,7 +637,14 @@ static int fill_bitmap_commit(struct bitmap_writer *writer, const uint32_t *mapping) { int found; + int from_pseudo_merge = commit->object.flags & BITMAP_PSEUDO_MERGE; uint32_t pos; + + if (ent->pseudo_merge) + BUG("unexpected pseudo-merge commit in fill_bitmap_commit()"); + + fill_bitmap_commit_calls_nr++; + if (!ent->bitmap) ent->bitmap = bitmap_new(); @@ -528,10 +658,7 @@ static int fill_bitmap_commit(struct bitmap_writer *writer, struct ewah_bitmap *old; struct bitmap *remapped = bitmap_new(); - if (commit->object.flags & BITMAP_PSEUDO_MERGE) - old = pseudo_merge_bitmap_for_commit(old_bitmap, c); - else - old = bitmap_for_commit(old_bitmap, c); + old = bitmap_for_commit(old_bitmap, c); /* * If this commit has an old bitmap, then translate that * bitmap and add its bits to this one. No need to walk @@ -540,26 +667,65 @@ static int fill_bitmap_commit(struct bitmap_writer *writer, if (old && !rebuild_bitmap(mapping, old, remapped)) { bitmap_or(ent->bitmap, remapped); bitmap_free(remapped); - if (commit->object.flags & BITMAP_PSEUDO_MERGE) - reused_pseudo_merge_bitmaps_nr++; - else - reused_bitmaps_nr++; + reused_bitmaps_nr++; continue; } bitmap_free(remapped); } /* + * If we encounter an ancestor for which we have already + * computed a bitmap during this build (i.e. a regular + * selected commit processed earlier in topo order), we can + * short-circuit the walk: its stored bitmap already covers + * the commit itself, its tree, and all of its ancestors. + */ + if (c != commit) { + khiter_t hash_pos = kh_get_oid_map(writer->bitmaps, + c->object.oid); + if (hash_pos != kh_end(writer->bitmaps)) { + struct bitmapped_commit *stored = + kh_value(writer->bitmaps, hash_pos); + if (stored && stored->bitmap) { + fill_bitmap_commit_found_ancestor_nr++; + bitmap_or_ewah(ent->bitmap, + stored->bitmap); + continue; + } + } + } + + /* * Mark ourselves and queue our tree. The commit * walk ensures we cover all parents. */ if (!(c->object.flags & BITMAP_PSEUDO_MERGE)) { + struct tree *tree; + + if (from_pseudo_merge && !c->object.parsed) { + /* + * Commits reachable from selected + * non-pseudo-merges are already parsed + * by the regular bitmap build. + * + * However, pseudo-merge fills can also + * reach commits that were not covered + * there, so parse any such leftovers + * before reading their tree or parents. + */ + if (repo_parse_commit(writer->repo, c)) + return -1; + } + pos = find_object_pos(writer, &c->object.oid, &found); if (!found) return -1; bitmap_set(ent->bitmap, pos); - prio_queue_put(tree_queue, - repo_get_commit_tree(writer->repo, c)); + + tree = repo_get_commit_tree(writer->repo, c); + if (!tree) + return -1; + prio_queue_put(tree_queue, tree); } for (p = c->parents; p; p = p->next) { @@ -575,13 +741,158 @@ static int fill_bitmap_commit(struct bitmap_writer *writer, } while (tree_queue->nr) { - if (fill_bitmap_tree(writer, ent->bitmap, - prio_queue_get(tree_queue)) < 0) + struct tree *t = prio_queue_get(tree_queue); + int found; + + pos = find_object_pos(writer, &t->object.oid, &found); + if (!found) + return -1; + if (bitmap_get(ent->bitmap, pos)) { + /* + * If our bit is already set, then there is + * nothing to do. Both this tree and all of its + * children will be set. + */ + continue; + } + + if (fill_bitmap_tree(writer, ent->bitmap, t, pos) < 0) return -1; } return 0; } +static int reuse_pseudo_merge_bitmap(struct bitmap_index *old_bitmap, + const uint32_t *mapping, + struct commit *merge, + struct ewah_bitmap **out) +{ + struct ewah_bitmap *old; + struct bitmap *remapped; + + if (!old_bitmap || !mapping) + return 0; + + old = pseudo_merge_bitmap_for_commit(old_bitmap, merge); + if (!old) + return 0; + + remapped = bitmap_new(); + if (rebuild_bitmap(mapping, old, remapped) < 0) { + bitmap_free(remapped); + return 0; + } + + *out = bitmap_to_ewah(remapped); + bitmap_free(remapped); + reused_pseudo_merge_bitmaps_nr++; + return 1; +} + +static int build_pseudo_merge_bitmap(struct bitmap_writer *writer, + struct bitmap_index *old_bitmap, + const uint32_t *mapping, + struct commit *merge, + struct ewah_bitmap **out) +{ + struct bb_commit ent = { 0 }; + struct prio_queue queue = { NULL }; + struct prio_queue tree_queue = { NULL }; + unsigned parents = commit_list_count(merge->parents); + int ret; + + ent.bitmap = bitmap_new(); + + pseudo_merge_bitmap_nr++; + pseudo_merge_bitmap_parents += parents; + + if (reuse_pseudo_merge_bitmap(old_bitmap, mapping, merge, out)) { + ret = 0; + goto done; + } + + ret = fill_bitmap_commit(writer, &ent, merge, &queue, &tree_queue, + old_bitmap, mapping); + + if (!ret) + *out = bitmap_to_ewah(ent.bitmap); + +done: + bitmap_free(ent.bitmap); + clear_prio_queue(&queue); + clear_prio_queue(&tree_queue); + + return ret; +} + +static int build_pseudo_merge_bitmaps(struct bitmap_writer *writer, + struct bitmap_index *old_bitmap, + const uint32_t *mapping, + int *nr_stored) +{ + size_t i = bitmap_writer_nr_selected_commits(writer); + int ret = 0; + + if (!writer->pseudo_merges_nr) + return 0; + + trace2_region_enter("pack-bitmap-write", "building_pseudo_merge_bitmaps", + writer->repo); + + for (; i < writer->selected_nr; i++) { + struct bitmapped_commit *merge = &writer->selected[i]; + struct commit_list *p; + struct bitmap *parents = bitmap_new(); + struct ewah_bitmap *objects = NULL; + + if (!merge->pseudo_merge) + BUG("found non-pseudo merge commit at %"PRIuMAX, + (uintmax_t)i); + + for (p = merge->commit->parents; p; p = p->next) { + int found; + uint32_t pos = find_object_pos(writer, + &p->item->object.oid, + &found); + if (!found) { + bitmap_free(parents); + ret = -1; + goto done; + } + bitmap_set(parents, pos); + } + + merge->pseudo_merge_parents = bitmap_to_ewah(parents); + bitmap_free(parents); + + if (build_pseudo_merge_bitmap(writer, old_bitmap, mapping, + merge->commit, &objects) < 0) { + ret = -1; + goto done; + } + merge->bitmap = objects; + + (*nr_stored)++; + display_progress(writer->progress, *nr_stored); + } + +done: + trace2_region_leave("pack-bitmap-write", "building_pseudo_merge_bitmaps", + writer->repo); + + trace2_data_intmax("pack-bitmap-write", writer->repo, + "pseudo_merge_bitmap_nr", + pseudo_merge_bitmap_nr); + trace2_data_intmax("pack-bitmap-write", writer->repo, + "building_bitmaps_pseudo_merge_reused", + reused_pseudo_merge_bitmaps_nr); + trace2_data_intmax("pack-bitmap-write", writer->repo, + "pseudo_merge_bitmap_parents", + pseudo_merge_bitmap_parents); + + return ret; +} + static void store_selected(struct bitmap_writer *writer, struct bb_commit *ent, struct commit *commit) { @@ -616,6 +927,10 @@ int bitmap_writer_build(struct bitmap_writer *writer) writer->progress = start_progress(writer->repo, "Building bitmaps", writer->selected_nr); + + writer->pos_cache_hits = 0; + writer->pos_cache_misses = 0; + trace2_region_enter("pack-bitmap-write", "building_bitmaps_total", writer->repo); @@ -661,6 +976,10 @@ int bitmap_writer_build(struct bitmap_writer *writer) bitmap_free(ent->bitmap); ent->bitmap = NULL; } + if (closed && + build_pseudo_merge_bitmaps(writer, old_bitmap, mapping, + &nr_stored) < 0) + closed = 0; clear_prio_queue(&queue); clear_prio_queue(&tree_queue); bitmap_builder_clear(&bb); @@ -672,8 +991,15 @@ int bitmap_writer_build(struct bitmap_writer *writer) trace2_data_intmax("pack-bitmap-write", writer->repo, "building_bitmaps_reused", reused_bitmaps_nr); trace2_data_intmax("pack-bitmap-write", writer->repo, - "building_bitmaps_pseudo_merge_reused", - reused_pseudo_merge_bitmaps_nr); + "fill_bitmap_commit_calls_nr", + fill_bitmap_commit_calls_nr); + trace2_data_intmax("pack-bitmap-write", writer->repo, + "fill_bitmap_commit_found_ancestor_nr", + fill_bitmap_commit_found_ancestor_nr); + trace2_data_intmax("pack-bitmap-write", writer->repo, + "bitmap_pos_cache_hits", writer->pos_cache_hits); + trace2_data_intmax("pack-bitmap-write", writer->repo, + "bitmap_pos_cache_misses", writer->pos_cache_misses); stop_progress(&writer->progress); @@ -819,51 +1145,52 @@ static void write_selected_commits_v1(struct bitmap_writer *writer, } } +static int pseudo_merge_commit_pos_cmp(const void *_va, const void *_vb, + void *_data) +{ + struct bitmap_writer *writer = _data; + uint32_t pos_a = find_object_pos(writer, _va, NULL); + uint32_t pos_b = find_object_pos(writer, _vb, NULL); + + if (pos_a < pos_b) + return -1; + if (pos_a > pos_b) + return 1; + return 0; +} + static void write_pseudo_merges(struct bitmap_writer *writer, struct hashfile *f) { struct oid_array commits = OID_ARRAY_INIT; - struct bitmap **commits_bitmap = NULL; off_t *pseudo_merge_ofs = NULL; off_t start, table_start, next_ext; uint32_t base = bitmap_writer_nr_selected_commits(writer); size_t i, j = 0; - CALLOC_ARRAY(commits_bitmap, writer->pseudo_merges_nr); CALLOC_ARRAY(pseudo_merge_ofs, writer->pseudo_merges_nr); - for (i = 0; i < writer->pseudo_merges_nr; i++) { - struct bitmapped_commit *merge = &writer->selected[base + i]; - struct commit_list *p; - - if (!merge->pseudo_merge) - BUG("found non-pseudo merge commit at %"PRIuMAX, (uintmax_t)i); - - commits_bitmap[i] = bitmap_new(); - - for (p = merge->commit->parents; p; p = p->next) - bitmap_set(commits_bitmap[i], - find_object_pos(writer, &p->item->object.oid, - NULL)); - } - start = hashfile_total(f); for (i = 0; i < writer->pseudo_merges_nr; i++) { - struct ewah_bitmap *commits_ewah = bitmap_to_ewah(commits_bitmap[i]); + struct bitmapped_commit *merge = &writer->selected[base + i]; + + if (!merge->pseudo_merge) + BUG("found non-pseudo merge commit at %"PRIuMAX, (uintmax_t)i); + + if (!merge->pseudo_merge_parents || !merge->bitmap) + BUG("missing pseudo-merge bitmap for commit %s", + oid_to_hex(&merge->commit->object.oid)); pseudo_merge_ofs[i] = hashfile_total(f); - - dump_bitmap(f, commits_ewah); - dump_bitmap(f, writer->selected[base+i].write_as); - - ewah_free(commits_ewah); + dump_bitmap(f, merge->pseudo_merge_parents); + dump_bitmap(f, merge->bitmap); } next_ext = st_add(hashfile_total(f), st_mult(kh_size(writer->pseudo_merge_commits), - sizeof(uint64_t))); + sizeof(uint32_t) + sizeof(uint64_t))); table_start = hashfile_total(f); @@ -876,7 +1203,12 @@ static void write_pseudo_merges(struct bitmap_writer *writer, oid_array_append(&commits, &kh_key(writer->pseudo_merge_commits, i)); } - oid_array_sort(&commits); + /* + * Sort the commits by their bit position so that the lookup + * table can be binary searched by the reader (see + * find_pseudo_merge()). + */ + QSORT_S(commits.oid, commits.nr, pseudo_merge_commit_pos_cmp, writer); /* write lookup table (non-extended) */ for (i = 0; i < commits.nr; i++) { @@ -936,12 +1268,8 @@ static void write_pseudo_merges(struct bitmap_writer *writer, hashwrite_be64(f, table_start - start); hashwrite_be64(f, hashfile_total(f) - start + sizeof(uint64_t)); - for (i = 0; i < writer->pseudo_merges_nr; i++) - bitmap_free(commits_bitmap[i]); - oid_array_clear(&commits); free(pseudo_merge_ofs); - free(commits_bitmap); } static int table_cmp(const void *_va, const void *_vb, void *_data)
diff --git a/pack-bitmap.h b/pack-bitmap.h index a95e1c2..19a8655 100644 --- a/pack-bitmap.h +++ b/pack-bitmap.h
@@ -132,6 +132,8 @@ int bitmap_has_oid_in_uninteresting(struct bitmap_index *, const struct object_i off_t get_disk_usage_from_bitmap(struct bitmap_index *, struct rev_info *); +struct bitmap_pos_cache_entry; + struct bitmap_writer { struct repository *repo; struct ewah_bitmap *commits; @@ -143,6 +145,11 @@ struct bitmap_writer { struct packing_data *to_pack; struct multi_pack_index *midx; /* if appending to a MIDX chain */ + struct bitmap_pos_cache_entry *pos_cache; + size_t pos_cache_nr; + uint64_t pos_cache_hits; + uint64_t pos_cache_misses; + struct bitmapped_commit *selected; unsigned int selected_nr, selected_alloc;
diff --git a/packfile.c b/packfile.c index 4208f53..89366ab 100644 --- a/packfile.c +++ b/packfile.c
@@ -420,6 +420,8 @@ void close_pack_index(struct packed_git *p) static void close_pack_revindex(struct packed_git *p) { + FREE_AND_NULL(p->revindex); + if (!p->revindex_map) return;
diff --git a/packfile.h b/packfile.h index 49d6bde..5729a37 100644 --- a/packfile.h +++ b/packfile.h
@@ -124,7 +124,7 @@ struct packfile_store { * that packs that contain a lot of accessed objects will be located * towards the front. * - * This is usually desireable, but there are exceptions. One exception + * This is usually desirable, but there are exceptions. One exception * is when the looking up multiple objects in a loop for each packfile. * In that case, we may easily end up with an infinite loop as the * packfiles get reordered to the front repeatedly.
diff --git a/parse-options.c b/parse-options.c index a676da8..f4647e0 100644 --- a/parse-options.c +++ b/parse-options.c
@@ -1149,7 +1149,7 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx, (ctx->flags & PARSE_OPT_KEEP_UNKNOWN_OPT)) { /* * Found an unknown option given to a command with - * subcommands that has a default operation mode: + * subcommands that have a default operation mode: * we treat this option and all remaining args as * arguments meant to that default operation mode. * So we are done parsing.
diff --git a/parse-options.h b/parse-options.h index 706de97..0d1f738 100644 --- a/parse-options.h +++ b/parse-options.h
@@ -117,6 +117,7 @@ typedef int parse_opt_subcommand_fn(int argc, const char **argv, * PARSE_OPT_OPTARG: says that the argument is optional (not for BOOLEANs) * PARSE_OPT_NOARG: says that this option does not take an argument * PARSE_OPT_NONEG: says that this option cannot be negated + * (i.e. rejects "--no-<option>") * PARSE_OPT_HIDDEN: this option is skipped in the default usage, and * shown only in the full usage. * PARSE_OPT_LASTARG_DEFAULT: says that this option will take the default
diff --git a/parse.c b/parse.c index 4831357..d77f280 100644 --- a/parse.c +++ b/parse.c
@@ -107,6 +107,15 @@ int git_parse_int64(const char *value, int64_t *ret) return 1; } +int git_parse_uint(const char *value, unsigned int *ret) +{ + uintmax_t tmp; + if (!git_parse_unsigned(value, &tmp, maximum_unsigned_value_of_type(unsigned int))) + return 0; + *ret = tmp; + return 1; +} + int git_parse_ulong(const char *value, unsigned long *ret) { uintmax_t tmp;
diff --git a/parse.h b/parse.h index ea32de9..a6dd37c 100644 --- a/parse.h +++ b/parse.h
@@ -5,6 +5,7 @@ int git_parse_signed(const char *value, intmax_t *ret, intmax_t max); int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max); int git_parse_ssize_t(const char *, ssize_t *); int git_parse_ulong(const char *, unsigned long *); +int git_parse_uint(const char *value, unsigned int *ret); int git_parse_int(const char *value, int *ret); int git_parse_int64(const char *value, int64_t *ret); int git_parse_double(const char *value, double *ret);
diff --git a/patch-ids.h b/patch-ids.h index 490d739..57534ee72 100644 --- a/patch-ids.h +++ b/patch-ids.h
@@ -37,7 +37,7 @@ int has_commit_patch_id(struct commit *commit, struct patch_ids *); * struct patch_id *cur; * for (cur = patch_id_iter_first(commit, ids); * cur; - * cur = patch_id_iter_next(cur, ids) { + * cur = patch_id_iter_next(cur, ids)) { * ... look at cur->commit * } */
diff --git a/path-walk.c b/path-walk.c index 6e426af..94ff90b 100644 --- a/path-walk.c +++ b/path-walk.c
@@ -9,6 +9,9 @@ #include "hashmap.h" #include "hex.h" #include "list-objects.h" +#include "list-objects-filter-options.h" +#include "object-name.h" +#include "odb.h" #include "object.h" #include "oid-array.h" #include "path.h" @@ -178,11 +181,6 @@ static int add_tree_entries(struct path_walk_context *ctx, return -1; } - /* Skip this object if already seen. */ - if (o->flags & SEEN) - continue; - o->flags |= SEEN; - strbuf_setlen(&path, base_len); strbuf_add(&path, entry.path, entry.pathlen); @@ -193,6 +191,40 @@ static int add_tree_entries(struct path_walk_context *ctx, if (type == OBJ_TREE) strbuf_addch(&path, '/'); + if (o->flags & SEEN) { + /* + * A tree with a shared OID may appear at multiple + * paths. Even though we already added this tree to + * the output at some other path, we still need to + * walk into it at this in-cone path to discover + * blobs that were not found at the earlier + * out-of-cone path. + * + * Only do this for paths not yet in our map, to + * avoid duplicate entries when the same tree OID + * appears at the same path across multiple commits. + */ + if (type == OBJ_TREE && ctx->info->pl && + ctx->info->pl->use_cone_patterns && + !ctx->info->pl_sparse_trees && + !strmap_contains(&ctx->paths_to_lists, path.buf)) { + int dtype; + enum pattern_match_result m; + m = path_matches_pattern_list(path.buf, path.len, + path.buf + base_len, + &dtype, + ctx->info->pl, + ctx->repo->index); + if (m != NOT_MATCHED) { + add_path_to_list(ctx, path.buf, type, + &entry.oid, + !(o->flags & UNINTERESTING)); + push_to_stack(ctx, path.buf); + } + } + continue; + } + if (ctx->info->pl) { int dtype; enum pattern_match_result match; @@ -202,7 +234,8 @@ static int add_tree_entries(struct path_walk_context *ctx, ctx->repo->index); if (ctx->info->pl->use_cone_patterns && - match == NOT_MATCHED) + match == NOT_MATCHED && + (type == OBJ_BLOB || ctx->info->pl_sparse_trees)) continue; else if (!ctx->info->pl->use_cone_patterns && type == OBJ_BLOB && @@ -237,6 +270,7 @@ static int add_tree_entries(struct path_walk_context *ctx, continue; } + o->flags |= SEEN; add_path_to_list(ctx, path.buf, type, &entry.oid, !(o->flags & UNINTERESTING)); @@ -249,6 +283,17 @@ static int add_tree_entries(struct path_walk_context *ctx, } /* + * Paths starting with '/' (e.g., "/tags", "/tagged-blobs") hold objects that + * were directly requested by 'pending' objects rather than discovered during + * tree traversal. + */ +static int path_is_for_direct_objects(const char *path) +{ + ASSERT(path); + return path[0] == '/'; +} + +/* * For each path in paths_to_explore, walk the trees another level * and add any found blobs to the batch (but only if they exist and * haven't been added yet). @@ -306,23 +351,57 @@ static int walk_path(struct path_walk_context *ctx, if (list->type == OBJ_BLOB && ctx->revs->prune_data.nr && + !path_is_for_direct_objects(path) && !match_pathspec(ctx->repo->index, &ctx->revs->prune_data, path, strlen(path), 0, NULL, 0)) return 0; - /* Evaluate function pointer on this data, if requested. */ - if ((list->type == OBJ_TREE && ctx->info->trees) || - (list->type == OBJ_BLOB && ctx->info->blobs) || - (list->type == OBJ_TAG && ctx->info->tags)) + /* + * Evaluate function pointer on this data, if requested. + * Ignore object type filters for tagged objects (path starts + * with `/`), first for blobs and then other types. + */ + if (list->type == OBJ_BLOB && + ctx->info->blob_limit && + !path_is_for_direct_objects(path)) { + struct oid_array filtered = OID_ARRAY_INIT; + + for (size_t i = 0; i < list->oids.nr; i++) { + unsigned long size; + + if (odb_read_object_info(ctx->repo->objects, + &list->oids.oid[i], + &size) != OBJ_BLOB || + size < ctx->info->blob_limit) + oid_array_append(&filtered, + &list->oids.oid[i]); + } + + if (filtered.nr) + ret = ctx->info->path_fn(path, &filtered, list->type, + ctx->info->path_fn_data); + oid_array_clear(&filtered); + } else if ((!ctx->info->strict_types && path_is_for_direct_objects(path)) || + (list->type == OBJ_TREE && ctx->info->trees) || + (list->type == OBJ_BLOB && ctx->info->blobs) || + (list->type == OBJ_TAG && ctx->info->tags)) { ret = ctx->info->path_fn(path, &list->oids, list->type, ctx->info->path_fn_data); + } - /* Expand data for children. */ - if (list->type == OBJ_TREE) { + /* + * Expand tree children, except when the set is directly requested + * _and_ we are otherwise filtering out trees. + */ + if (list->type == OBJ_TREE && + (!path_is_for_direct_objects(path) || ctx->info->trees)) { + /* Use root path if expanding from tagged/direct trees. */ + const char *expand_path = !strcmp(path, "/tagged-trees") + ? root_path : path; for (size_t i = 0; i < list->oids.nr; i++) { ret |= add_tree_entries(ctx, - path, + expand_path, &list->oids.oid[i]); } } @@ -370,14 +449,12 @@ static int setup_pending_objects(struct path_walk_info *info, { struct type_and_oid_list *tags = NULL; struct type_and_oid_list *tagged_blobs = NULL; - struct type_and_oid_list *root_tree_list = NULL; + struct type_and_oid_list *tagged_trees = NULL; if (info->tags) CALLOC_ARRAY(tags, 1); - if (info->blobs) - CALLOC_ARRAY(tagged_blobs, 1); - if (info->trees) - root_tree_list = strmap_get(&ctx->paths_to_lists, root_path); + CALLOC_ARRAY(tagged_blobs, 1); + CALLOC_ARRAY(tagged_trees, 1); /* * Pending objects include: @@ -421,22 +498,19 @@ static int setup_pending_objects(struct path_walk_info *info, switch (obj->type) { case OBJ_TREE: - if (!info->trees) - continue; - if (pending->path) { - char *path = *pending->path ? xstrfmt("%s/", pending->path) - : xstrdup(""); + if (pending->path && *pending->path) { + char *path = xstrfmt("%s/", pending->path); add_path_to_list(ctx, path, OBJ_TREE, &obj->oid, 1); free(path); + } else if (!pending->path || !info->trees) { + oid_array_append(&tagged_trees->oids, &obj->oid); } else { - /* assume a root tree, such as a lightweight tag. */ - oid_array_append(&root_tree_list->oids, &obj->oid); + add_path_to_list(ctx, root_path, OBJ_TREE, + &obj->oid, 1); } break; case OBJ_BLOB: - if (!info->blobs) - continue; if (pending->path) add_path_to_list(ctx, pending->path, OBJ_BLOB, &obj->oid, 1); else @@ -469,6 +543,18 @@ static int setup_pending_objects(struct path_walk_info *info, free(tagged_blobs); } } + if (tagged_trees) { + if (tagged_trees->oids.nr) { + const char *tagged_tree_path = "/tagged-trees"; + tagged_trees->type = OBJ_TREE; + tagged_trees->maybe_interesting = 1; + strmap_put(&ctx->paths_to_lists, tagged_tree_path, tagged_trees); + push_to_stack(ctx, tagged_tree_path); + } else { + oid_array_clear(&tagged_trees->oids); + free(tagged_trees); + } + } if (tags) { if (tags->oids.nr) { const char *tag_path = "/tags"; @@ -485,6 +571,123 @@ static int setup_pending_objects(struct path_walk_info *info, return 0; } +static int prepare_filters_one(struct path_walk_info *info, + struct list_objects_filter_options *options) +{ + switch (options->choice) { + case LOFC_DISABLED: + return 1; + + case LOFC_BLOB_NONE: + if (info) { + info->blobs = 0; + list_objects_filter_release(options); + } + return 1; + + case LOFC_BLOB_LIMIT: + if (info) { + if (!options->blob_limit_value) + info->blobs = 0; + else if (!info->blob_limit || + info->blob_limit > options->blob_limit_value) + info->blob_limit = options->blob_limit_value; + list_objects_filter_release(options); + } + return 1; + + case LOFC_TREE_DEPTH: + if (options->tree_exclude_depth) { + error(_("tree:%lu filter not supported by the path-walk API"), + options->tree_exclude_depth); + return 0; + } + if (info) { + info->trees = 0; + info->blobs = 0; + } + return 1; + + case LOFC_OBJECT_TYPE: + if (info) { + info->commits &= options->object_type == OBJ_COMMIT; + info->tags &= options->object_type == OBJ_TAG; + info->trees &= options->object_type == OBJ_TREE; + info->blobs &= options->object_type == OBJ_BLOB; + info->strict_types = 1; + list_objects_filter_release(options); + } + return 1; + + case LOFC_SPARSE_OID: + if (info) { + struct object_id sparse_oid; + struct repository *repo = info->revs->repo; + + if (info->pl) { + warning(_("sparse filter cannot be combined with existing sparse patterns")); + return 0; + } + + if (repo_get_oid_with_flags(repo, + options->sparse_oid_name, + &sparse_oid, + GET_OID_BLOB)) { + error(_("unable to access sparse blob in '%s'"), + options->sparse_oid_name); + return 0; + } + + CALLOC_ARRAY(info->pl, 1); + info->pl->use_cone_patterns = 1; + + if (add_patterns_from_blob_to_list(&sparse_oid, "", 0, + info->pl) < 0) { + clear_pattern_list(info->pl); + FREE_AND_NULL(info->pl); + error(_("unable to parse sparse filter data in '%s'"), + oid_to_hex(&sparse_oid)); + return 0; + } + + if (!info->pl->use_cone_patterns) { + clear_pattern_list(info->pl); + FREE_AND_NULL(info->pl); + warning(_("sparse filter is not cone-mode compatible")); + return 0; + } + } + return 1; + + case LOFC_COMBINE: + for (size_t i = 0; i < options->sub_nr; i++) { + if (!prepare_filters_one(info, &options->sub[i])) + return 0; + } + return 1; + + default: + error(_("object filter '%s' not supported by the path-walk API"), + list_objects_filter_spec(options)); + return 0; + } +} + +static int prepare_filters(struct path_walk_info *info, + struct list_objects_filter_options *options) +{ + if (!prepare_filters_one(info, options)) + return 0; + if (info) + list_objects_filter_release(options); + return 1; +} + +int path_walk_filter_compatible(struct list_objects_filter_options *options) +{ + return prepare_filters(NULL, options); +} + /** * Given the configuration of 'info', walk the commits based on 'info->revs' and * call 'info->path_fn' on each discovered path. @@ -512,6 +715,9 @@ int walk_objects_by_path(struct path_walk_info *info) trace2_region_enter("path-walk", "commit-walk", info->revs->repo); + if (!prepare_filters(info, &info->revs->filter)) + return -1; + CALLOC_ARRAY(commit_list, 1); commit_list->type = OBJ_COMMIT; @@ -532,15 +738,17 @@ int walk_objects_by_path(struct path_walk_info *info) push_to_stack(&ctx, root_path); /* - * Set these values before preparing the walk to catch - * lightweight tags pointing to non-commits and indexed objects. + * Ensure that prepare_revision_walk() keeps all pending objects + * even through an object type filter. */ - info->revs->blob_objects = info->blobs; - info->revs->tree_objects = info->trees; + info->revs->blob_objects = info->revs->tree_objects = 1; if (prepare_revision_walk(info->revs)) die(_("failed to setup revision walk")); + info->revs->blob_objects = info->blobs; + info->revs->tree_objects = info->trees; + /* * Walk trees to mark them as UNINTERESTING. * This is particularly important when 'edge_aggressive' is set.
diff --git a/path-walk.h b/path-walk.h index 5ef5a84..a2652b2 100644 --- a/path-walk.h +++ b/path-walk.h
@@ -36,6 +36,11 @@ struct path_walk_info { /** * Initialize which object types the path_fn should be called on. This * could also limit the walk to skip blobs if not set. + * + * Note: even when 'blobs' or 'trees' is disabled, objects that are + * directly requested as pending objects will still be emitted to + * path_fn. Only objects discovered during the tree walk are filtered by + * these flags. */ int commits; int trees; @@ -43,6 +48,19 @@ struct path_walk_info { int tags; /** + * If 'strict_types' is 0, then direct object requests will no longer + * override the object type restrictions. + */ + int strict_types; + + /** + * If non-zero, specifies a maximum blob size. Blobs with a + * size equal to or greater than this limit will not be + * emitted unless included in 'pending'. + */ + unsigned long blob_limit; + + /** * When 'prune_all_uninteresting' is set and a path has all objects * marked as UNINTERESTING, then the path-walk will not visit those * objects. It will not call path_fn on those objects and will not @@ -64,8 +82,14 @@ struct path_walk_info { * of the cone. If not in cone mode, then all tree paths will be * explored but the path_fn will only be called when the path matches * the sparse-checkout patterns. + * + * When 'pl_sparse_trees' is zero, the sparse patterns only restrict + * blobs and all trees are included in the walk output. This matches + * the behavior of the sparse:oid object filter. When nonzero, trees + * are also pruned by the sparse patterns (as used by backfill). */ struct pattern_list *pl; + int pl_sparse_trees; }; #define PATH_WALK_INFO_INIT { \ @@ -85,3 +109,10 @@ void path_walk_info_clear(struct path_walk_info *info); * Returns nonzero on an error. */ int walk_objects_by_path(struct path_walk_info *info); + +struct list_objects_filter_options; +/** + * Given a set of options for filtering objects, return 1 if the options + * are compatible with the path-walk API and 0 otherwise. + */ +int path_walk_filter_compatible(struct list_objects_filter_options *options);
diff --git a/path.h b/path.h index 0434ba5..4c2958a 100644 --- a/path.h +++ b/path.h
@@ -217,7 +217,7 @@ void safe_create_dir(struct repository *repo, const char *dir, int share); * * - It always adjusts shared permissions. * - * Returns a negative erorr code on error, 0 on success. + * Returns a negative error code on error, 0 on success. */ int safe_create_dir_in_gitdir(struct repository *repo, const char *path);
diff --git a/pathspec.c b/pathspec.c index 5993c4a..f78b227 100644 --- a/pathspec.c +++ b/pathspec.c
@@ -486,7 +486,7 @@ static void init_pathspec_item(struct pathspec_item *item, unsigned flags, match = xstrdup(copyfrom); prefixlen = 0; } else { - match = prefix_path_gently(prefix, prefixlen, + match = prefix_path_gently(the_repository, prefix, prefixlen, &prefixlen, copyfrom); if (!match) { const char *hint_path;
diff --git a/pretty.c b/pretty.c index 8148039..d8a9f37 100644 --- a/pretty.c +++ b/pretty.c
@@ -399,7 +399,6 @@ static void add_rfc2047(struct strbuf *sb, const char *line, size_t len, int i; int line_len = last_line_length(sb); - strbuf_grow(sb, len * 3 + strlen(encoding) + 100); strbuf_addf(sb, "=?%s?q?", encoding); line_len += strlen(encoding) + 5; /* 5 for =??q? */ @@ -662,7 +661,7 @@ static void add_merge_info(const struct pretty_print_context *pp, if (pp->abbrev) strbuf_add_unique_abbrev(sb, oidp, pp->abbrev); else - strbuf_addstr(sb, oid_to_hex(oidp)); + strbuf_add_oid_hex(sb, oidp); parent = parent->next; } strbuf_addch(sb, '\n'); @@ -1567,7 +1566,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ switch (placeholder[0]) { case 'H': /* commit hash */ strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT)); - strbuf_addstr(sb, oid_to_hex(&commit->object.oid)); + strbuf_add_oid_hex(sb, &commit->object.oid); strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET)); return 1; case 'h': /* abbreviated commit hash */ @@ -1577,7 +1576,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET)); return 1; case 'T': /* tree hash */ - strbuf_addstr(sb, oid_to_hex(get_commit_tree_oid(commit))); + strbuf_add_oid_hex(sb, get_commit_tree_oid(commit)); return 1; case 't': /* abbreviated tree hash */ strbuf_add_unique_abbrev(sb, @@ -1588,7 +1587,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ for (p = commit->parents; p; p = p->next) { if (p != commit->parents) strbuf_addch(sb, ' '); - strbuf_addstr(sb, oid_to_hex(&p->item->object.oid)); + strbuf_add_oid_hex(sb, &p->item->object.oid); } return 1; case 'p': /* abbreviated parent hashes */
diff --git a/promisor-remote.c b/promisor-remote.c index 96fa215..f34856d 100644 --- a/promisor-remote.c +++ b/promisor-remote.c
@@ -46,7 +46,7 @@ static int fetch_objects(struct repository *repo, "fetch", remote_name, "--no-tags", "--no-write-fetch-head", "--recurse-submodules=no", "--filter=blob:none", "--stdin", NULL); - if (!repo_config_get_bool(the_repository, "promisor.quiet", &quiet) && quiet) + if (!repo_config_get_bool(repo, "promisor.quiet", &quiet) && quiet) strvec_push(&child.args, "--quiet"); if (start_command(&child)) die(_("promisor-remote: unable to fork off fetch subprocess")); @@ -268,11 +268,35 @@ static int remove_fetched_oids(struct repository *repo, return remaining_nr; } +static int try_promisor_remotes(struct repository *repo, + struct object_id **remaining_oids, + int *remaining_nr, int *to_free, + bool accepted_only) +{ + struct promisor_remote *r = repo->promisor_remote_config->promisors; + + for (; r; r = r->next) { + if (accepted_only != r->accepted) + continue; + if (fetch_objects(repo, r->name, *remaining_oids, *remaining_nr) < 0) { + if (*remaining_nr == 1) + continue; + *remaining_nr = remove_fetched_oids(repo, remaining_oids, + *remaining_nr, *to_free); + if (*remaining_nr) { + *to_free = 1; + continue; + } + } + return 1; /* all fetched */ + } + return 0; +} + void promisor_remote_get_direct(struct repository *repo, const struct object_id *oids, int oid_nr) { - struct promisor_remote *r; struct object_id *remaining_oids = (struct object_id *)oids; int remaining_nr = oid_nr; int to_free = 0; @@ -283,19 +307,13 @@ void promisor_remote_get_direct(struct repository *repo, promisor_remote_init(repo); - for (r = repo->promisor_remote_config->promisors; r; r = r->next) { - if (fetch_objects(repo, r->name, remaining_oids, remaining_nr) < 0) { - if (remaining_nr == 1) - continue; - remaining_nr = remove_fetched_oids(repo, &remaining_oids, - remaining_nr, to_free); - if (remaining_nr) { - to_free = 1; - continue; - } - } + /* Try accepted remotes first (those the server told us to use) */ + if (try_promisor_remotes(repo, &remaining_oids, &remaining_nr, + &to_free, true)) goto all_fetched; - } + if (try_promisor_remotes(repo, &remaining_oids, &remaining_nr, + &to_free, false)) + goto all_fetched; for (i = 0; i < remaining_nr; i++) { if (is_promisor_object(repo, &remaining_oids[i])) @@ -557,6 +575,12 @@ enum accept_promisor { ACCEPT_ALL }; +/* + * Check if a specific field and its advertised value match the local + * configuration of a given promisor remote. + * + * Returns 1 if they match, 0 otherwise. + */ static int match_field_against_config(const char *field, const char *value, struct promisor_info *config_info) { @@ -568,9 +592,18 @@ static int match_field_against_config(const char *field, const char *value, return 0; } +/* + * Check that the advertised fields match the local configuration. + * + * When 'config_entry' is NULL (ACCEPT_ALL mode), every checked field + * must match at least one remote in 'config_info'. + * + * When 'config_entry' points to a specific remote's config, the + * checked fields are compared against that single remote only. + */ static int all_fields_match(struct promisor_info *advertised, struct string_list *config_info, - int in_list) + struct promisor_info *config_entry) { struct string_list *fields = fields_checked(); struct string_list_item *item_checked; @@ -579,7 +612,6 @@ static int all_fields_match(struct promisor_info *advertised, int match = 0; const char *field = item_checked->string; const char *value = NULL; - struct string_list_item *item; if (!strcasecmp(field, promisor_field_filter)) value = advertised->filter; @@ -589,7 +621,11 @@ static int all_fields_match(struct promisor_info *advertised, if (!value) return 0; - if (in_list) { + if (config_entry) { + match = match_field_against_config(field, value, + config_entry); + } else { + struct string_list_item *item; for_each_string_list_item(item, config_info) { struct promisor_info *p = item->util; if (match_field_against_config(field, value, p)) { @@ -597,12 +633,6 @@ static int all_fields_match(struct promisor_info *advertised, break; } } - } else { - item = string_list_lookup(config_info, advertised->name); - if (item) { - struct promisor_info *p = item->util; - match = match_field_against_config(field, value, p); - } } if (!match) @@ -612,6 +642,14 @@ static int all_fields_match(struct promisor_info *advertised, return 1; } +static bool has_control_char(const char *s) +{ + for (const char *c = s; *c; c++) + if (iscntrl(*c)) + return true; + return false; +} + static int should_accept_remote(enum accept_promisor accept, struct promisor_info *advertised, struct string_list *config_info) @@ -621,8 +659,13 @@ static int should_accept_remote(enum accept_promisor accept, const char *remote_name = advertised->name; const char *remote_url = advertised->url; + if (!remote_url || !*remote_url) + BUG("no or empty URL advertised for remote '%s'; " + "this remote should have been rejected earlier", + remote_name); + if (accept == ACCEPT_ALL) - return all_fields_match(advertised, config_info, 1); + return all_fields_match(advertised, config_info, NULL); /* Get config info for that promisor remote */ item = string_list_lookup(config_info, remote_name); @@ -634,23 +677,19 @@ static int should_accept_remote(enum accept_promisor accept, p = item->util; if (accept == ACCEPT_KNOWN_NAME) - return all_fields_match(advertised, config_info, 0); + return all_fields_match(advertised, config_info, p); if (accept != ACCEPT_KNOWN_URL) BUG("Unhandled 'enum accept_promisor' value '%d'", accept); - if (!remote_url || !*remote_url) { - warning(_("no or empty URL advertised for remote '%s'"), remote_name); + if (strcmp(p->url, remote_url)) { + warning(_("known remote named '%s' but with URL '%s' instead of '%s', " + "ignoring this remote"), + remote_name, p->url, remote_url); return 0; } - if (!strcmp(p->url, remote_url)) - return all_fields_match(advertised, config_info, 0); - - warning(_("known remote named '%s' but with URL '%s' instead of '%s'"), - remote_name, p->url, remote_url); - - return 0; + return all_fields_match(advertised, config_info, p); } static int skip_field_name_prefix(const char *elem, const char *field_name, const char **value) @@ -691,9 +730,9 @@ static struct promisor_info *parse_one_advertised_remote(const char *remote_info string_list_clear(&elem_list, 0); - if (!info->name || !info->url) { - warning(_("server advertised a promisor remote without a name or URL: %s"), - remote_info); + if (!info->name || !*info->name || !info->url || !*info->url) { + warning(_("server advertised a promisor remote without a name or URL: '%s', " + "ignoring this remote"), remote_info); promisor_info_free(info); return NULL; } @@ -741,18 +780,14 @@ static bool valid_filter(const char *filter, const char *remote_name) return !res; } -/* Check that a token doesn't contain any control character */ static bool valid_token(const char *token, const char *remote_name) { - const char *c = token; - - for (; *c; c++) - if (iscntrl(*c)) { - warning(_("invalid token '%s' for remote '%s' " - "will not be stored"), - token, remote_name); - return false; - } + if (has_control_char(token)) { + warning(_("invalid token '%s' for remote '%s' " + "will not be stored"), + token, remote_name); + return false; + } return true; } @@ -827,20 +862,12 @@ static bool promisor_store_advertised_fields(struct promisor_info *advertised, return reload_config; } -static void filter_promisor_remote(struct repository *repo, - struct strvec *accepted, - const char *info) +static enum accept_promisor accept_from_server(struct repository *repo) { const char *accept_str; enum accept_promisor accept = ACCEPT_NONE; - struct string_list config_info = STRING_LIST_INIT_NODUP; - struct string_list remote_info = STRING_LIST_INIT_DUP; - struct store_info *store_info = NULL; - struct string_list_item *item; - bool reload_config = false; - struct string_list accepted_filters = STRING_LIST_INIT_DUP; - if (!repo_config_get_string_tmp(the_repository, "promisor.acceptfromserver", &accept_str)) { + if (!repo_config_get_string_tmp(repo, "promisor.acceptfromserver", &accept_str)) { if (!*accept_str || !strcasecmp("None", accept_str)) accept = ACCEPT_NONE; else if (!strcasecmp("KnownUrl", accept_str)) @@ -854,6 +881,20 @@ static void filter_promisor_remote(struct repository *repo, accept_str, "promisor.acceptfromserver"); } + return accept; +} + +static void filter_promisor_remote(struct repository *repo, + struct string_list *accepted_remotes, + const char *info) +{ + struct string_list config_info = STRING_LIST_INIT_NODUP; + struct string_list remote_info = STRING_LIST_INIT_DUP; + struct store_info *store_info = NULL; + struct string_list_item *item; + bool reload_config = false; + enum accept_promisor accept = accept_from_server(repo); + if (accept == ACCEPT_NONE) return; @@ -880,17 +921,10 @@ static void filter_promisor_remote(struct repository *repo, if (promisor_store_advertised_fields(advertised, store_info)) reload_config = true; - strvec_push(accepted, advertised->name); - - /* Capture advertised filters for accepted remotes */ - if (advertised->filter) { - struct string_list_item *i; - i = string_list_append(&accepted_filters, advertised->name); - i->util = xstrdup(advertised->filter); - } + string_list_append(accepted_remotes, advertised->name)->util = advertised; + } else { + promisor_info_free(advertised); } - - promisor_info_free(advertised); } promisor_info_list_clear(&config_info); @@ -900,39 +934,36 @@ static void filter_promisor_remote(struct repository *repo, if (reload_config) repo_promisor_remote_reinit(repo); - /* Apply accepted remote filters to the stable repo state */ - for_each_string_list_item(item, &accepted_filters) { - struct promisor_remote *r = repo_promisor_remote_find(repo, item->string); + /* Apply accepted remotes to the stable repo state */ + for_each_string_list_item(item, accepted_remotes) { + struct promisor_info *info = item->util; + struct promisor_remote *r = repo_promisor_remote_find(repo, info->name); + if (r) { - free(r->advertised_filter); - r->advertised_filter = item->util; - item->util = NULL; - } - } - - string_list_clear(&accepted_filters, 1); - - /* Mark the remotes as accepted in the repository state */ - for (size_t i = 0; i < accepted->nr; i++) { - struct promisor_remote *r = repo_promisor_remote_find(repo, accepted->v[i]); - if (r) r->accepted = 1; + if (info->filter) { + free(r->advertised_filter); + r->advertised_filter = xstrdup(info->filter); + } + } } } void promisor_remote_reply(const char *info, char **accepted_out) { - struct strvec accepted = STRVEC_INIT; + struct string_list accepted_remotes = STRING_LIST_INIT_NODUP; - filter_promisor_remote(the_repository, &accepted, info); + filter_promisor_remote(the_repository, &accepted_remotes, info); if (accepted_out) { - if (accepted.nr) { + if (accepted_remotes.nr) { struct strbuf reply = STRBUF_INIT; - for (size_t i = 0; i < accepted.nr; i++) { - if (i) + struct string_list_item *item; + + for_each_string_list_item(item, &accepted_remotes) { + if (reply.len) strbuf_addch(&reply, ';'); - strbuf_addstr_urlencode(&reply, accepted.v[i], allow_unsanitized); + strbuf_addstr_urlencode(&reply, item->string, allow_unsanitized); } *accepted_out = strbuf_detach(&reply, NULL); } else { @@ -940,7 +971,7 @@ void promisor_remote_reply(const char *info, char **accepted_out) } } - strvec_clear(&accepted); + promisor_info_list_clear(&accepted_remotes); } void mark_promisor_remotes_as_accepted(struct repository *r, const char *remotes)
diff --git a/promisor-remote.h b/promisor-remote.h index 3d4d2de..301f5ac 100644 --- a/promisor-remote.h +++ b/promisor-remote.h
@@ -29,6 +29,17 @@ int repo_has_promisor_remote(struct repository *r); * Fetches all requested objects from all promisor remotes, trying them one at * a time until all objects are fetched. * + * Callers are responsible for filtering out OIDs that are already present + * locally before calling this function: every supplied OID is sent in the + * fetch request, even if the object already exists in the local object + * store. (Only after a fetch failure does this function fall back to + * stripping already-present OIDs from the list before trying the next + * configured promisor remote.) Callers should also deduplicate the OIDs. + * + * To test for local presence without triggering a lazy fetch (which would + * defeat the purpose of batching), use odb_has_object(..., 0) or + * odb_read_object_info_extended() with OBJECT_INFO_FOR_PREFETCH. + * * If oid_nr is 0, this function returns immediately. */ void promisor_remote_get_direct(struct repository *repo,
diff --git a/pseudo-merge.c b/pseudo-merge.c index ff18b6c..22b8600 100644 --- a/pseudo-merge.c +++ b/pseudo-merge.c
@@ -150,7 +150,10 @@ static int pseudo_merge_config(const char *var, const char *value, if (!strcmp(key, "pattern")) { struct strbuf re = STRBUF_INIT; - free(group->pattern); + if (group->pattern) { + regfree(group->pattern); + free(group->pattern); + } if (*value != '^') strbuf_addch(&re, '^'); strbuf_addstr(&re, value); @@ -169,8 +172,8 @@ static int pseudo_merge_config(const char *var, const char *value, } } else if (!strcmp(key, "samplerate")) { group->sample_rate = git_config_double(var, value, ctx->kvi); - if (!(0 <= group->sample_rate && group->sample_rate <= 1)) { - warning(_("%s must be between 0 and 1, using default"), var); + if (!(0 < group->sample_rate && group->sample_rate <= 1)) { + warning(_("%s must be between 0 (exclusive) and 1, using default"), var); group->sample_rate = DEFAULT_PSEUDO_MERGE_SAMPLE_RATE; } } else if (!strcmp(key, "threshold")) { @@ -236,6 +239,8 @@ static int find_pseudo_merge_group_for_ref(const struct reference *ref, void *_d c = lookup_commit(the_repository, maybe_peeled); if (!c) return 0; + if (repo_parse_commit(the_repository, c)) + return 0; if (!packlist_find(writer->to_pack, maybe_peeled)) return 0; @@ -559,9 +564,9 @@ static struct pseudo_merge *pseudo_merge_at(const struct pseudo_merge_map *pm, if (got == want) return use_pseudo_merge(pm, &pm->v[mi]); else if (got < want) - hi = mi; - else lo = mi + 1; + else + hi = mi; } warning(_("could not find pseudo-merge for commit %s at offset %"PRIuMAX), @@ -600,7 +605,7 @@ static int nth_pseudo_merge_ext(const struct pseudo_merge_map *pm, return error(_("out-of-bounds read: (%"PRIuMAX" >= %"PRIuMAX")"), (uintmax_t)ofs, (uintmax_t)pm->map_size); - read_pseudo_merge_commit_at(merge, pm->map + ofs); + merge->pseudo_merge_ofs = ofs; return 0; } @@ -671,7 +676,7 @@ int apply_pseudo_merges_for_commit(const struct pseudo_merge_map *pm, off_t ofs = merge_commit.pseudo_merge_ofs & ~((uint64_t)1<<63); uint32_t i; - if (pseudo_merge_ext_at(pm, &ext, ofs) < -1) { + if (pseudo_merge_ext_at(pm, &ext, ofs) < 0) { warning(_("could not read extended pseudo-merge table " "for commit %s"), oid_to_hex(&commit->object.oid));
diff --git a/quote.c b/quote.c index b9f6bdc..235fac8 100644 --- a/quote.c +++ b/quote.c
@@ -171,9 +171,7 @@ char *sq_dequote(char *arg) return sq_dequote_step(arg, NULL); } -static int sq_dequote_to_argv_internal(char *arg, - const char ***argv, int *nr, int *alloc, - struct strvec *array) +int sq_dequote_to_strvec(char *arg, struct strvec *array) { char *next = arg; @@ -191,27 +189,12 @@ static int sq_dequote_to_argv_internal(char *arg, c = *++next; } while (isspace(c)); } - if (argv) { - ALLOC_GROW(*argv, *nr + 1, *alloc); - (*argv)[(*nr)++] = dequoted; - } - if (array) - strvec_push(array, dequoted); + strvec_push(array, dequoted); } while (next); return 0; } -int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc) -{ - return sq_dequote_to_argv_internal(arg, argv, nr, alloc, NULL); -} - -int sq_dequote_to_strvec(char *arg, struct strvec *array) -{ - return sq_dequote_to_argv_internal(arg, NULL, NULL, NULL, array); -} - /* 1 means: quote as octal * 0 means: quote as octal if (quote_path_fully) * -1 means: never quote
diff --git a/quote.h b/quote.h index 0300c29..989f238 100644 --- a/quote.h +++ b/quote.h
@@ -2,6 +2,7 @@ #define QUOTE_H struct strbuf; +struct strvec; extern int quote_path_fully; @@ -67,17 +68,10 @@ char *sq_dequote_step(char *src, char **next); /* * Same as the above, but can be used to unwrap many arguments in the - * same string separated by space. Like sq_quote, it works in place, - * modifying arg and appending pointers into it to argv. + * same string separated by space. The strvec will duplicate and take + * ownership of the strings, but note that "arg" is still modified in-place + * during parsing. */ -int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc); - -/* - * Same as above, but store the unquoted strings in a strvec. We will - * still modify arg in place, but unlike sq_dequote_to_argv, the strvec - * will duplicate and take ownership of the strings. - */ -struct strvec; int sq_dequote_to_strvec(char *arg, struct strvec *); int unquote_c_style(struct strbuf *, const char *quoted, const char **endp);
diff --git a/read-cache.c b/read-cache.c index 38a04b8..2182910 100644 --- a/read-cache.c +++ b/read-cache.c
@@ -20,6 +20,7 @@ #include "dir.h" #include "object-file.h" #include "odb.h" +#include "odb/transaction.h" #include "oid-array.h" #include "tree.h" #include "commit.h"
diff --git a/refs.c b/refs.c index bfcb9c7..d3caa9a 100644 --- a/refs.c +++ b/refs.c
@@ -126,7 +126,8 @@ struct ref_namespace_info ref_namespace[] = { * points to the content of another. Unlike the other * ref namespaces, this one can be changed by the * GIT_REPLACE_REF_BASE environment variable. This - * .namespace value will be overwritten in setup_git_env(). + * .namespace value will be overwritten during repository + * setup. */ .ref = "refs/replace/", .decoration = DECORATION_GRAFTED, @@ -989,7 +990,7 @@ enum ref_worktree_type parse_worktree_ref(const char *maybe_worktree_ref, return REF_WORKTREE_SHARED; } -long get_files_ref_lock_timeout_ms(void) +long get_files_ref_lock_timeout_ms(struct repository *repo) { static int configured = 0; @@ -997,7 +998,7 @@ long get_files_ref_lock_timeout_ms(void) static int timeout_ms = 100; if (!configured) { - repo_config_get_int(the_repository, "core.filesreflocktimeout", &timeout_ms); + repo_config_get_int(repo, "core.filesreflocktimeout", &timeout_ms); configured = 1; } @@ -1307,6 +1308,7 @@ struct ref_update *ref_transaction_add_update( const char *refname, unsigned int flags, const struct object_id *new_oid, const struct object_id *old_oid, + const struct object_id *peeled, const char *new_target, const char *old_target, const char *committer_info, const char *msg) @@ -1339,6 +1341,8 @@ struct ref_update *ref_transaction_add_update( update->committer_info = xstrdup_or_null(committer_info); update->msg = normalize_reflog_message(msg); } + if (flags & REF_HAVE_PEELED) + oidcpy(&update->peeled, peeled); /* * This list is generally used by the backends to avoid duplicates. @@ -1383,25 +1387,27 @@ static int transaction_refname_valid(const char *refname, return 1; } -int ref_transaction_update(struct ref_transaction *transaction, - const char *refname, - const struct object_id *new_oid, - const struct object_id *old_oid, - const char *new_target, - const char *old_target, - unsigned int flags, const char *msg, - struct strbuf *err) +enum ref_transaction_error ref_transaction_update(struct ref_transaction *transaction, + const char *refname, + const struct object_id *new_oid, + const struct object_id *old_oid, + const char *new_target, + const char *old_target, + unsigned int flags, const char *msg, + struct strbuf *err) { + struct object_id peeled; + assert(err); if ((flags & REF_FORCE_CREATE_REFLOG) && (flags & REF_SKIP_CREATE_REFLOG)) { strbuf_addstr(err, _("refusing to force and skip creation of reflog")); - return -1; + return REF_TRANSACTION_ERROR_GENERIC; } if (!transaction_refname_valid(refname, new_oid, flags, err)) - return -1; + return REF_TRANSACTION_ERROR_GENERIC; if (flags & ~REF_TRANSACTION_UPDATE_ALLOWED_FLAGS) BUG("illegal flags 0x%x passed to ref_transaction_update()", flags); @@ -1416,8 +1422,32 @@ int ref_transaction_update(struct ref_transaction *transaction, flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0); flags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0); + if ((flags & REF_HAVE_NEW) && !new_target && !is_null_oid(new_oid) && + !(flags & REF_SKIP_OID_VERIFICATION) && !(flags & REF_LOG_ONLY)) { + struct object *o = parse_object(transaction->ref_store->repo, new_oid); + + if (!o) { + strbuf_addf(err, + _("trying to write ref '%s' with nonexistent object %s"), + refname, oid_to_hex(new_oid)); + return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE; + } + + if (o->type != OBJ_COMMIT && is_branch(refname)) { + strbuf_addf(err, _("trying to write non-commit object %s to branch '%s'"), + oid_to_hex(new_oid), refname); + return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE; + } + + if (o->type == OBJ_TAG) { + if (!peel_object(transaction->ref_store->repo, new_oid, &peeled, + PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE)) + flags |= REF_HAVE_PEELED; + } + } + ref_transaction_add_update(transaction, refname, flags, - new_oid, old_oid, new_target, + new_oid, old_oid, &peeled, new_target, old_target, NULL, msg); return 0; @@ -1444,7 +1474,7 @@ int ref_transaction_update_reflog(struct ref_transaction *transaction, return -1; update = ref_transaction_add_update(transaction, refname, flags, - new_oid, old_oid, NULL, NULL, + new_oid, old_oid, NULL, NULL, NULL, committer_info, msg); update->index = index; @@ -1472,7 +1502,7 @@ int ref_transaction_create(struct ref_transaction *transaction, return 1; } return ref_transaction_update(transaction, refname, new_oid, - null_oid(the_hash_algo), new_target, NULL, flags, + null_oid(transaction->ref_store->repo->hash_algo), new_target, NULL, flags, msg, err); } @@ -1491,7 +1521,7 @@ int ref_transaction_delete(struct ref_transaction *transaction, if (old_target && !(flags & REF_NO_DEREF)) BUG("delete cannot operate on symrefs with deref mode"); return ref_transaction_update(transaction, refname, - null_oid(the_hash_algo), old_oid, + null_oid(transaction->ref_store->repo->hash_algo), old_oid, NULL, old_target, flags, msg, err); } @@ -2295,6 +2325,10 @@ static struct ref_store *ref_store_init(struct repository *repo, { const struct ref_storage_be *be; struct ref_store *refs; + struct ref_store_init_options opts = { + .access_flags = flags, + .log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo), + }; be = find_ref_storage_backend(format); if (!be) @@ -2304,7 +2338,8 @@ static struct ref_store *ref_store_init(struct repository *repo, * TODO Send in a 'struct worktree' instead of a 'gitdir', and * allow the backend to handle how it wants to deal with worktrees. */ - refs = be->init(repo, repo->ref_storage_payload, gitdir, flags); + refs = be->init(repo, repo->ref_storage_payload, gitdir, &opts); + return refs; } @@ -2379,7 +2414,7 @@ struct ref_store *repo_get_submodule_ref_store(struct repository *repo, subrepo = xmalloc(sizeof(*subrepo)); if (repo_submodule_init(subrepo, repo, submodule, - null_oid(the_hash_algo))) { + null_oid(repo->hash_algo))) { free(subrepo); goto done; } @@ -2498,7 +2533,7 @@ int refs_update_symref_extended(struct ref_store *refs, const char *ref, if (referent && refs_read_symbolic_ref(refs, ref, referent) == NOT_A_SYMREF) { struct object_id oid; if (!refs_read_ref(refs, ref, &oid)) { - strbuf_addstr(referent, oid_to_hex(&oid)); + strbuf_add_oid_hex(referent, &oid); ret = NOT_A_SYMREF; } } @@ -2571,14 +2606,14 @@ static int transaction_hook_feed_stdin(int hook_stdin_fd, void *pp_cb, void *pp_ strbuf_reset(buf); if (!(update->flags & REF_HAVE_OLD)) - strbuf_addf(buf, "%s ", oid_to_hex(null_oid(the_hash_algo))); + strbuf_addf(buf, "%s ", oid_to_hex(null_oid(transaction->ref_store->repo->hash_algo))); else if (update->old_target) strbuf_addf(buf, "ref:%s ", update->old_target); else strbuf_addf(buf, "%s ", oid_to_hex(&update->old_oid)); if (!(update->flags & REF_HAVE_NEW)) - strbuf_addf(buf, "%s ", oid_to_hex(null_oid(the_hash_algo))); + strbuf_addf(buf, "%s ", oid_to_hex(null_oid(transaction->ref_store->repo->hash_algo))); else if (update->new_target) strbuf_addf(buf, "ref:%s ", update->new_target); else @@ -3146,6 +3181,7 @@ struct migration_data { static int migrate_one_ref(const struct reference *ref, void *cb_data) { struct migration_data *data = cb_data; + const struct git_hash_algo *hash_algo = data->transaction->ref_store->repo->hash_algo; struct strbuf symref_target = STRBUF_INIT; int ret; @@ -3154,7 +3190,7 @@ static int migrate_one_ref(const struct reference *ref, void *cb_data) if (ret < 0) goto done; - ret = ref_transaction_update(data->transaction, ref->name, NULL, null_oid(the_hash_algo), + ret = ref_transaction_update(data->transaction, ref->name, NULL, null_oid(hash_algo), symref_target.buf, NULL, REF_SKIP_CREATE_REFLOG | REF_NO_DEREF, NULL, data->errbuf); if (ret < 0) @@ -3452,7 +3488,7 @@ int repo_migrate_ref_storage_format(struct repository *repo, * repository format so that clients will use the new ref store. * We also need to swap out the repository's main ref store. */ - initialize_repository_version(hash_algo_by_ptr(repo->hash_algo), format, 1); + initialize_repository_version(the_repository, hash_algo_by_ptr(repo->hash_algo), format, 1); /* * Unset the old ref store and release it. `get_main_ref_store()` will
diff --git a/refs.h b/refs.h index d65de6a..71d5c18 100644 --- a/refs.h +++ b/refs.h
@@ -905,14 +905,14 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, * See the above comment "Reference transaction updates" for more * information. */ -int ref_transaction_update(struct ref_transaction *transaction, - const char *refname, - const struct object_id *new_oid, - const struct object_id *old_oid, - const char *new_target, - const char *old_target, - unsigned int flags, const char *msg, - struct strbuf *err); +enum ref_transaction_error ref_transaction_update(struct ref_transaction *transaction, + const char *refname, + const struct object_id *new_oid, + const struct object_id *old_oid, + const char *new_target, + const char *old_target, + unsigned int flags, const char *msg, + struct strbuf *err); /* * Similar to `ref_transaction_update`, but this function is only for adding
diff --git a/refs/files-backend.c b/refs/files-backend.c index b3b0c25..a4c7858 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c
@@ -19,7 +19,6 @@ #include "../iterator.h" #include "../dir-iterator.h" #include "../lockfile.h" -#include "../object.h" #include "../path.h" #include "../dir.h" #include "../chdir-notify.h" @@ -108,7 +107,7 @@ static void clear_loose_ref_cache(struct files_ref_store *refs) static struct ref_store *files_ref_store_init(struct repository *repo, const char *payload, const char *gitdir, - unsigned int flags) + const struct ref_store_init_options *opts) { struct files_ref_store *refs = xcalloc(1, sizeof(*refs)); struct ref_store *ref_store = (struct ref_store *)refs; @@ -120,11 +119,13 @@ static struct ref_store *files_ref_store_init(struct repository *repo, &ref_common_dir); base_ref_store_init(ref_store, repo, refdir.buf, &refs_be_files); - refs->store_flags = flags; + refs->gitcommondir = strbuf_detach(&ref_common_dir, NULL); refs->packed_ref_store = - packed_ref_store_init(repo, NULL, refs->gitcommondir, flags); - refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo); + packed_ref_store_init(repo, NULL, refs->gitcommondir, opts); + refs->store_flags = opts->access_flags; + refs->log_all_ref_updates = opts->log_all_ref_updates; + repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs); chdir_notify_reparent("files-backend $GIT_DIR", &refs->base.gitdir); @@ -792,7 +793,7 @@ static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs, if (hold_lock_file_for_update_timeout( &lock->lk, ref_file.buf, LOCK_NO_DEREF, - get_files_ref_lock_timeout_ms()) < 0) { + get_files_ref_lock_timeout_ms(transaction->ref_store->repo)) < 0) { int myerr = errno; errno = 0; if (myerr == ENOENT && --attempts_remaining > 0) { @@ -1190,13 +1191,17 @@ static int remove_empty_directories(struct strbuf *path) return remove_dir_recursively(path, REMOVE_DIR_EMPTY_ONLY); } +struct create_reflock_cb { + struct lock_file *lk; + struct repository *repo; +}; + static int create_reflock(const char *path, void *cb) { - struct lock_file *lk = cb; - + struct create_reflock_cb *data = cb; return hold_lock_file_for_update_timeout( - lk, path, LOCK_NO_DEREF, - get_files_ref_lock_timeout_ms()) < 0 ? -1 : 0; + data->lk, path, LOCK_NO_DEREF, + get_files_ref_lock_timeout_ms(data->repo)) < 0 ? -1 : 0; } /* @@ -1208,6 +1213,7 @@ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs, { struct strbuf ref_file = STRBUF_INIT; struct ref_lock *lock; + struct create_reflock_cb cb_data; files_assert_main_repository(refs, "lock_ref_oid_basic"); assert(err); @@ -1229,8 +1235,10 @@ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs, lock->ref_name = xstrdup(refname); lock->count = 1; + cb_data.lk = &lock->lk; + cb_data.repo = refs->base.repo; - if (raceproof_create_file(ref_file.buf, create_reflock, &lock->lk)) { + if (raceproof_create_file(ref_file.buf, create_reflock, &cb_data)) { unable_to_lock_message(ref_file.buf, errno, err); goto error_return; } @@ -1324,7 +1332,8 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r) ref_transaction_add_update( transaction, r->name, REF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING, - null_oid(the_hash_algo), &r->oid, NULL, NULL, NULL, NULL); + null_oid(the_hash_algo), &r->oid, NULL, NULL, NULL, + NULL, NULL); if (ref_transaction_commit(transaction, &err)) goto cleanup; @@ -1587,7 +1596,6 @@ static int rename_tmp_log(struct files_ref_store *refs, const char *newrefname) static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs, struct ref_lock *lock, const struct object_id *oid, - int skip_oid_verification, struct strbuf *err); static int commit_ref_update(struct files_ref_store *refs, struct ref_lock *lock, @@ -1735,7 +1743,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store, } oidcpy(&lock->old_oid, &orig_oid); - if (write_ref_to_lockfile(refs, lock, &orig_oid, 0, &err) || + if (write_ref_to_lockfile(refs, lock, &orig_oid, &err) || commit_ref_update(refs, lock, &orig_oid, logmsg, 0, &err)) { error("unable to write current sha1 into %s: %s", newrefname, err.buf); strbuf_release(&err); @@ -1753,7 +1761,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store, goto rollbacklog; } - if (write_ref_to_lockfile(refs, lock, &orig_oid, 0, &err) || + if (write_ref_to_lockfile(refs, lock, &orig_oid, &err) || commit_ref_update(refs, lock, &orig_oid, NULL, REF_SKIP_CREATE_REFLOG, &err)) { error("unable to write current sha1 into %s: %s", oldrefname, err.buf); strbuf_release(&err); @@ -1997,32 +2005,11 @@ static int files_log_ref_write(struct files_ref_store *refs, static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs, struct ref_lock *lock, const struct object_id *oid, - int skip_oid_verification, struct strbuf *err) { static char term = '\n'; - struct object *o; int fd; - if (!skip_oid_verification) { - o = parse_object(refs->base.repo, oid); - if (!o) { - strbuf_addf( - err, - "trying to write ref '%s' with nonexistent object %s", - lock->ref_name, oid_to_hex(oid)); - unlock_ref(lock); - return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE; - } - if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) { - strbuf_addf( - err, - "trying to write non-commit object %s to branch '%s'", - oid_to_hex(oid), lock->ref_name); - unlock_ref(lock); - return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE; - } - } fd = get_lock_file_fd(&lock->lk); if (write_in_full(fd, oid_to_hex(oid), refs->base.repo->hash_algo->hexsz) < 0 || write_in_full(fd, &term, 1) < 0 || @@ -2489,7 +2476,7 @@ static enum ref_transaction_error split_head_update(struct ref_update *update, new_update = ref_transaction_add_update( transaction, "HEAD", update->flags | REF_LOG_ONLY | REF_NO_DEREF | REF_LOG_VIA_SPLIT, - &update->new_oid, &update->old_oid, + &update->new_oid, &update->old_oid, &update->peeled, NULL, NULL, update->committer_info, update->msg); new_update->parent_update = update; @@ -2551,8 +2538,8 @@ static enum ref_transaction_error split_symref_update(struct ref_update *update, transaction, referent, new_flags, update->new_target ? NULL : &update->new_oid, update->old_target ? NULL : &update->old_oid, - update->new_target, update->old_target, NULL, - update->msg); + &update->peeled, update->new_target, update->old_target, + NULL, update->msg); new_update->parent_update = update; @@ -2826,7 +2813,6 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re } else { ret = write_ref_to_lockfile( refs, lock, &update->new_oid, - update->flags & REF_SKIP_OID_VERIFICATION, err); if (ret) { char *write_err = strbuf_detach(err, NULL); @@ -3016,7 +3002,7 @@ static int files_transaction_prepare(struct ref_store *ref_store, ref_transaction_add_update( packed_transaction, update->refname, REF_HAVE_NEW | REF_NO_DEREF, - &update->new_oid, NULL, + &update->new_oid, NULL, NULL, NULL, NULL, NULL, NULL); } } @@ -3222,19 +3208,22 @@ static int files_transaction_finish_initial(struct files_ref_store *refs, if (update->flags & REF_LOG_ONLY) ref_transaction_add_update(loose_transaction, update->refname, update->flags, &update->new_oid, - &update->old_oid, NULL, NULL, + &update->old_oid, &update->peeled, + NULL, NULL, update->committer_info, update->msg); else ref_transaction_add_update(loose_transaction, update->refname, update->flags & ~REF_HAVE_OLD, update->new_target ? NULL : &update->new_oid, NULL, - update->new_target, NULL, update->committer_info, + &update->peeled, update->new_target, + NULL, update->committer_info, NULL); } else { ref_transaction_add_update(packed_transaction, update->refname, update->flags & ~REF_HAVE_OLD, &update->new_oid, &update->old_oid, - NULL, NULL, update->committer_info, NULL); + &update->peeled, NULL, NULL, + update->committer_info, NULL); } } @@ -3864,22 +3853,12 @@ static int files_fsck_refs_content(struct ref_store *ref_store, static int files_fsck_refs_name(struct ref_store *ref_store UNUSED, struct fsck_options *o, const char *refname, - const char *path, + const char *path UNUSED, int mode UNUSED) { struct strbuf sb = STRBUF_INIT; - const char *filename; int ret = 0; - filename = basename((char *) path); - - /* - * Ignore the files ending with ".lock" as they may be lock files - * However, do not allow bare ".lock" files. - */ - if (filename[0] != '.' && ends_with(filename, ".lock")) - goto cleanup; - if (is_root_ref(refname)) goto cleanup; @@ -3939,6 +3918,7 @@ static int files_fsck_refs_dir(struct ref_store *ref_store, struct strbuf refname = STRBUF_INIT; struct strbuf sb = STRBUF_INIT; struct dir_iterator *iter; + const char *filename; int iter_status; int ret = 0; @@ -3962,6 +3942,15 @@ static int files_fsck_refs_dir(struct ref_store *ref_store, strbuf_addf(&refname, "worktrees/%s/", wt->id); strbuf_addf(&refname, "refs/%s", iter->relative_path); + filename = basename((char *) iter->path.buf); + + /* + * Ignore the files ending with ".lock" as they may be lock files. + * However, do not skip invalid refnames with '.lock' suffix. + */ + if (filename[0] != '.' && ends_with(filename, ".lock")) + continue; + if (files_fsck_ref(ref_store, o, refname.buf, iter->path.buf, iter->st.st_mode) < 0) ret = -1;
diff --git a/refs/packed-backend.c b/refs/packed-backend.c index 23ed629..0acde48 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c
@@ -218,14 +218,14 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot) struct ref_store *packed_ref_store_init(struct repository *repo, const char *payload UNUSED, const char *gitdir, - unsigned int store_flags) + const struct ref_store_init_options *opts) { struct packed_ref_store *refs = xcalloc(1, sizeof(*refs)); struct ref_store *ref_store = (struct ref_store *)refs; struct strbuf sb = STRBUF_INIT; base_ref_store_init(ref_store, repo, gitdir, &refs_be_packed); - refs->store_flags = store_flags; + refs->store_flags = opts->access_flags; strbuf_addf(&sb, "%s/packed-refs", gitdir); refs->path = strbuf_detach(&sb, NULL); @@ -1531,13 +1531,11 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re */ i++; } else { - struct object_id peeled; - int peel_error = peel_object(refs->base.repo, &update->new_oid, - &peeled, PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE); + bool peeled = update->flags & REF_HAVE_PEELED; if (write_packed_entry(out, update->refname, &update->new_oid, - peel_error ? NULL : &peeled)) + peeled ? &update->peeled : NULL)) goto write_error; i++;
diff --git a/refs/packed-backend.h b/refs/packed-backend.h index 2c2377a..1db48e8 100644 --- a/refs/packed-backend.h +++ b/refs/packed-backend.h
@@ -3,6 +3,7 @@ struct repository; struct ref_transaction; +struct ref_store_init_options; /* * Support for storing references in a `packed-refs` file. @@ -16,7 +17,7 @@ struct ref_transaction; struct ref_store *packed_ref_store_init(struct repository *repo, const char *payload, const char *gitdir, - unsigned int store_flags); + const struct ref_store_init_options *options); /* * Lock the packed-refs file for writing. Flags is passed to
diff --git a/refs/refs-internal.h b/refs/refs-internal.h index d79e35f..a08d589 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h
@@ -40,10 +40,17 @@ struct ref_transaction; #define REF_LOG_ONLY (1 << 7) /* + * The reference contains a peeled object ID. This is used when the + * new_oid is pointing to a tag object and the reference backend + * wants to also store the peeled value for optimized retrieval. + */ +#define REF_HAVE_PEELED (1 << 15) + +/* * Return the length of time to retry acquiring a loose reference lock * before giving up, in milliseconds: */ -long get_files_ref_lock_timeout_ms(void); +long get_files_ref_lock_timeout_ms(struct repository *repo); /* * Return true iff refname is minimally safe. "Safe" here means that @@ -93,6 +100,12 @@ struct ref_update { struct object_id old_oid; /* + * If the new_oid points to a tag object, set this to the peeled + * object ID for optimized retrieval without needed to hit the odb. + */ + struct object_id peeled; + + /* * If set, point the reference to this value. This can also be * used to convert regular references to become symbolic refs. * Cannot be set together with `new_oid`. @@ -169,6 +182,7 @@ struct ref_update *ref_transaction_add_update( const char *refname, unsigned int flags, const struct object_id *new_oid, const struct object_id *old_oid, + const struct object_id *peeled, const char *new_target, const char *old_target, const char *committer_info, const char *msg); @@ -386,6 +400,21 @@ struct ref_store; REF_STORE_MAIN) /* + * Options for initializing the ref backend. All backend-agnostic information + * which backends required will be held here. + */ +struct ref_store_init_options { + /* The kind of operations that the ref_store is allowed to perform. */ + unsigned int access_flags; + + /* + * Denotes under what conditions reflogs should be created when updating + * references. + */ + enum log_refs_config log_all_ref_updates; +}; + +/* * Initialize the ref_store for the specified gitdir. These functions * should call base_ref_store_init() to initialize the shared part of * the ref_store and to record the ref_store for later lookup. @@ -393,7 +422,7 @@ struct ref_store; typedef struct ref_store *ref_store_init_fn(struct repository *repo, const char *payload, const char *gitdir, - unsigned int flags); + const struct ref_store_init_options *opts); /* * Release all memory and resources associated with the ref store. */ @@ -421,10 +450,6 @@ typedef int ref_transaction_abort_fn(struct ref_store *refs, struct ref_transaction *transaction, struct strbuf *err); -typedef int ref_transaction_commit_fn(struct ref_store *refs, - struct ref_transaction *transaction, - struct strbuf *err); - typedef int optimize_fn(struct ref_store *ref_store, struct refs_optimize_opts *opts);
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index daea30a..4ae2292 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c
@@ -12,7 +12,6 @@ #include "../hex.h" #include "../ident.h" #include "../iterator.h" -#include "../object.h" #include "../parse.h" #include "../path.h" #include "../refs.h" @@ -369,7 +368,7 @@ static int reftable_be_config(const char *var, const char *value, static struct ref_store *reftable_be_init(struct repository *repo, const char *payload, const char *gitdir, - unsigned int store_flags) + const struct ref_store_init_options *opts) { struct reftable_ref_store *refs = xcalloc(1, sizeof(*refs)); struct strbuf ref_common_dir = STRBUF_INIT; @@ -386,8 +385,8 @@ static struct ref_store *reftable_be_init(struct repository *repo, base_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable); strmap_init(&refs->worktree_backends); - refs->store_flags = store_flags; - refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo); + refs->log_all_ref_updates = opts->log_all_ref_updates; + refs->store_flags = opts->access_flags; switch (repo->hash_algo->format_id) { case GIT_SHA1_FORMAT_ID: @@ -399,12 +398,12 @@ static struct ref_store *reftable_be_init(struct repository *repo, default: BUG("unknown hash algorithm %d", repo->hash_algo->format_id); } - refs->write_options.default_permissions = calc_shared_perm(the_repository, 0666 & ~mask); + refs->write_options.default_permissions = calc_shared_perm(repo, 0666 & ~mask); refs->write_options.disable_auto_compact = !git_env_bool("GIT_TEST_REFTABLE_AUTOCOMPACTION", 1); refs->write_options.lock_timeout_ms = 100; - repo_config(the_repository, reftable_be_config, &refs->write_options); + repo_config(repo, reftable_be_config, &refs->write_options); /* * It is somewhat unfortunate that we have to mirror the default block @@ -486,7 +485,7 @@ static int reftable_be_create_on_disk(struct ref_store *ref_store, struct strbuf sb = STRBUF_INIT; strbuf_addf(&sb, "%s/reftable", refs->base.gitdir); - safe_create_dir(the_repository, sb.buf, 1); + safe_create_dir(ref_store->repo, sb.buf, 1); strbuf_reset(&sb); strbuf_release(&sb); @@ -1081,25 +1080,6 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor return 0; } - /* Verify that the new object ID is valid. */ - if ((u->flags & REF_HAVE_NEW) && !is_null_oid(&u->new_oid) && - !(u->flags & REF_SKIP_OID_VERIFICATION) && - !(u->flags & REF_LOG_ONLY)) { - struct object *o = parse_object(refs->base.repo, &u->new_oid); - if (!o) { - strbuf_addf(err, - _("trying to write ref '%s' with nonexistent object %s"), - u->refname, oid_to_hex(&u->new_oid)); - return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE; - } - - if (o->type != OBJ_COMMIT && is_branch(u->refname)) { - strbuf_addf(err, _("trying to write non-commit object %s to branch '%s'"), - oid_to_hex(&u->new_oid), u->refname); - return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE; - } - } - /* * When we update the reference that HEAD points to we enqueue * a second log-only update for HEAD so that its reflog is @@ -1126,8 +1106,8 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor ref_transaction_add_update( transaction, "HEAD", u->flags | REF_LOG_ONLY | REF_NO_DEREF, - &u->new_oid, &u->old_oid, NULL, NULL, NULL, - u->msg); + &u->new_oid, &u->old_oid, &u->peeled, NULL, NULL, + NULL, u->msg); } ret = reftable_backend_read_ref(be, rewritten_ref, @@ -1213,7 +1193,7 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor transaction, referent->buf, new_flags, u->new_target ? NULL : &u->new_oid, u->old_target ? NULL : &u->old_oid, - u->new_target, u->old_target, + &u->peeled, u->new_target, u->old_target, u->committer_info, u->msg); new_update->parent_update = u; @@ -1603,17 +1583,13 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data goto done; } else if (u->flags & REF_HAVE_NEW) { struct reftable_ref_record ref = {0}; - struct object_id peeled; - int peel_error; ref.refname = (char *)u->refname; ref.update_index = ts; - peel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled, - PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE); - if (!peel_error) { + if (u->flags & REF_HAVE_PEELED) { ref.value_type = REFTABLE_REF_VAL2; - memcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ); + memcpy(ref.value.val2.target_value, u->peeled.hash, GIT_MAX_RAWSZ); memcpy(ref.value.val2.value, u->new_oid.hash, GIT_MAX_RAWSZ); } else if (!is_null_oid(&u->new_oid)) { ref.value_type = REFTABLE_REF_VAL1;
diff --git a/reftable/system.h b/reftable/system.h index c0e2cbe..628232a 100644 --- a/reftable/system.h +++ b/reftable/system.h
@@ -84,7 +84,7 @@ struct reftable_flock { * to acquire the lock. If `timeout_ms` is 0 we don't wait, if it is negative * we block indefinitely. * - * Retrun 0 on success, a reftable error code on error. Specifically, + * Return 0 on success, a reftable error code on error. Specifically, * `REFTABLE_LOCK_ERROR` should be returned in case the target path is already * locked. */
diff --git a/remote-curl.c b/remote-curl.c index aba60d5..9e614c5 100644 --- a/remote-curl.c +++ b/remote-curl.c
@@ -946,7 +946,7 @@ static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_rece do { err = probe_rpc(rpc, &results); if (err == HTTP_REAUTH) - credential_fill(the_repository, &http_auth, 0); + http_reauth_prepare(0); } while (err == HTTP_REAUTH); if (err != HTTP_OK) return -1; @@ -1068,7 +1068,7 @@ static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_rece rpc->any_written = 0; err = run_slot(slot, NULL); if (err == HTTP_REAUTH && !large_request) { - credential_fill(the_repository, &http_auth, 0); + http_reauth_prepare(0); curl_slist_free_all(headers); goto retry; } @@ -1557,7 +1557,7 @@ int cmd_main(int argc, const char **argv) int nongit; int ret = 1; - setup_git_directory_gently(&nongit); + setup_git_directory_gently(the_repository, &nongit); if (argc < 2) { error(_("remote-curl: usage: git remote-curl <remote> [<url>]")); goto cleanup; @@ -1605,7 +1605,7 @@ int cmd_main(int argc, const char **argv) break; if (starts_with(buf.buf, "fetch ")) { if (nongit) { - setup_git_directory_gently(&nongit); + setup_git_directory_gently(the_repository, &nongit); if (nongit) die(_("remote-curl: fetch attempted without a local repo")); }
diff --git a/remote.c b/remote.c index a664cd1..00723b3 100644 --- a/remote.c +++ b/remote.c
@@ -8,6 +8,7 @@ #include "gettext.h" #include "hex.h" #include "remote.h" +#include "url.h" #include "urlmatch.h" #include "refs.h" #include "refspec.h" @@ -152,6 +153,8 @@ static struct remote *make_remote(struct remote_state *remote_state, refspec_init_push(&ret->push); refspec_init_fetch(&ret->fetch); string_list_init_dup(&ret->server_options); + string_list_init_dup(&ret->negotiation_restrict); + string_list_init_dup(&ret->negotiation_include); ALLOC_GROW(remote_state->remotes, remote_state->remotes_nr + 1, remote_state->remotes_alloc); @@ -179,6 +182,8 @@ static void remote_clear(struct remote *remote) FREE_AND_NULL(remote->http_proxy); FREE_AND_NULL(remote->http_proxy_authmethod); string_list_clear(&remote->server_options, 0); + string_list_clear(&remote->negotiation_restrict, 0); + string_list_clear(&remote->negotiation_include, 0); } static void add_merge(struct branch *branch, const char *name) @@ -562,6 +567,12 @@ static int handle_config(const char *key, const char *value, } else if (!strcmp(subkey, "serveroption")) { return parse_transport_option(key, value, &remote->server_options); + } else if (!strcmp(subkey, "negotiationrestrict")) { + return parse_transport_option(key, value, + &remote->negotiation_restrict); + } else if (!strcmp(subkey, "negotiationinclude")) { + return parse_transport_option(key, value, + &remote->negotiation_include); } else if (!strcmp(subkey, "followremotehead")) { const char *no_warn_branch; if (!strcmp(value, "never")) @@ -2114,6 +2125,43 @@ int get_fetch_map(const struct ref *remote_refs, return 0; } +int get_remote_group(const char *key, const char *value, + const struct config_context *ctx UNUSED, + void *priv) +{ + struct remote_group_data *g = priv; + + if (skip_prefix(key, "remotes.", &key) && !strcmp(key, g->name)) { + /* split list by white space */ + while (*value) { + size_t wordlen = strcspn(value, " \t\n"); + + if (wordlen >= 1) + string_list_append_nodup(g->list, + xstrndup(value, wordlen)); + value += wordlen + (value[wordlen] != '\0'); + } + } + + return 0; +} + +int add_remote_or_group(const char *name, struct string_list *list) +{ + int prev_nr = list->nr; + struct remote_group_data g; + g.name = name; g.list = list; + + repo_config(the_repository, get_remote_group, &g); + if (list->nr == prev_nr) { + struct remote *remote = remote_get(name); + if (!remote_is_configured(remote, 0)) + return 0; + string_list_append(list, remote->name); + } + return 1; +} + int resolve_remote_symref(struct ref *ref, struct ref *list) { if (!ref->symref)
diff --git a/remote.h b/remote.h index fc05294..54b17e4 100644 --- a/remote.h +++ b/remote.h
@@ -117,6 +117,8 @@ struct remote { char *http_proxy_authmethod; struct string_list server_options; + struct string_list negotiation_restrict; + struct string_list negotiation_include; enum follow_remote_head_settings follow_remote_head; const char *no_warn_branch; @@ -347,6 +349,18 @@ int branch_has_merge_config(struct branch *branch); int branch_merge_matches(struct branch *, int n, const char *); +/* list of the remote in a group as configured */ +struct remote_group_data { + const char *name; + struct string_list *list; +}; + +int get_remote_group(const char *key, const char *value, + const struct config_context *ctx, + void *priv); + +int add_remote_or_group(const char *name, struct string_list *list); + /** * Return the fully-qualified refname of the tracking branch for `branch`. * I.e., what "branch@{upstream}" would give you. Returns NULL if no @@ -418,8 +432,8 @@ struct push_cas_option { unsigned use_tracking:1; char *refname; } *entry; - int nr; - int alloc; + size_t nr; + size_t alloc; }; int parseopt_push_cas_option(const struct option *, const char *arg, int unset);
diff --git a/repack-geometry.c b/repack-geometry.c index 7cebd0c..2064683 100644 --- a/repack-geometry.c +++ b/repack-geometry.c
@@ -4,6 +4,7 @@ #include "repack.h" #include "repository.h" #include "hex.h" +#include "midx.h" #include "packfile.h" static uint32_t pack_geometry_weight(struct packed_git *p) @@ -31,8 +32,28 @@ void pack_geometry_init(struct pack_geometry *geometry, { struct packed_git *p; struct strbuf buf = STRBUF_INIT; + struct multi_pack_index *m = get_multi_pack_index(existing->source); repo_for_each_pack(existing->repo, p) { + if (geometry->midx_layer_threshold_set && m && + p->multi_pack_index) { + /* + * When writing MIDX layers incrementally, + * ignore packs unless they are in the most + * recent MIDX layer *and* there are at least + * 'midx_layer_threshold' packs in that layer. + * + * Otherwise 'p' is either in an older layer, or + * the youngest layer does not have enough packs + * to consider its packs as candidates for + * repacking. In either of those cases we want + * to ignore the pack. + */ + if (m->num_packs < geometry->midx_layer_threshold || + !midx_layer_contains_pack(m, pack_basename(p))) + continue; + } + if (args->local && !p->pack_local) /* * When asked to only repack local packfiles we skip @@ -173,6 +194,20 @@ void pack_geometry_split(struct pack_geometry *geometry) geometry->promisor_split = compute_pack_geometry_split(geometry->promisor_pack, geometry->promisor_pack_nr, geometry->split_factor); + for (uint32_t i = 0; i < geometry->split; i++) { + struct packed_git *p = geometry->pack[i]; + /* + * During incremental MIDX/bitmap repacking, any packs + * included in the rollup are either (a) not MIDX'd, or + * (b) contained in the tip layer iff it has at least + * the threshold number of packs. + * + * In the latter case, we can safely conclude that the + * tip of the MIDX chain will be rewritten. + */ + if (p->multi_pack_index) + geometry->midx_tip_rewritten = true; + } } struct packed_git *pack_geometry_preferred_pack(struct pack_geometry *geometry) @@ -214,7 +249,8 @@ static void remove_redundant_packs(struct packed_git **pack, uint32_t pack_nr, struct string_list *names, struct existing_packs *existing, - const char *packdir) + const char *packdir, + bool wrote_incremental_midx) { const struct git_hash_algo *algop = existing->repo->hash_algo; struct strbuf buf = STRBUF_INIT; @@ -234,7 +270,8 @@ static void remove_redundant_packs(struct packed_git **pack, (string_list_has_string(&existing->kept_packs, buf.buf))) continue; - repack_remove_redundant_pack(existing->repo, packdir, buf.buf); + repack_remove_redundant_pack(existing->repo, packdir, buf.buf, + wrote_incremental_midx); } strbuf_release(&buf); @@ -243,12 +280,13 @@ static void remove_redundant_packs(struct packed_git **pack, void pack_geometry_remove_redundant(struct pack_geometry *geometry, struct string_list *names, struct existing_packs *existing, - const char *packdir) + const char *packdir, + bool wrote_incremental_midx) { remove_redundant_packs(geometry->pack, geometry->split, - names, existing, packdir); + names, existing, packdir, wrote_incremental_midx); remove_redundant_packs(geometry->promisor_pack, geometry->promisor_split, - names, existing, packdir); + names, existing, packdir, wrote_incremental_midx); } void pack_geometry_release(struct pack_geometry *geometry)
diff --git a/repack-midx.c b/repack-midx.c index 0682b80..b6b1de7 100644 --- a/repack-midx.c +++ b/repack-midx.c
@@ -2,12 +2,16 @@ #include "repack.h" #include "hash.h" #include "hex.h" +#include "lockfile.h" +#include "midx.h" #include "odb.h" #include "oidset.h" #include "pack-bitmap.h" +#include "path.h" #include "refs.h" #include "run-command.h" #include "tempfile.h" +#include "trace2.h" struct midx_snapshot_ref_data { struct repository *repo; @@ -275,32 +279,63 @@ static void remove_redundant_bitmaps(struct string_list *include, strbuf_release(&path); } -int write_midx_included_packs(struct repack_write_midx_opts *opts) +static void repack_prepare_midx_command(struct child_process *cmd, + struct repack_write_midx_opts *opts, + const char *subcommand) +{ + cmd->git_cmd = 1; + + strvec_pushl(&cmd->args, "multi-pack-index", subcommand, NULL); + + if (opts->show_progress) + strvec_push(&cmd->args, "--progress"); + else + strvec_push(&cmd->args, "--no-progress"); + + if (opts->write_bitmaps) + strvec_push(&cmd->args, "--bitmap"); +} + +static int repack_fill_midx_stdin_packs(struct child_process *cmd, + struct string_list *include, + struct string_list *out) +{ + struct strbuf in_buf = STRBUF_INIT; + struct strbuf out_buf = STRBUF_INIT; + struct string_list_item *item; + int ret; + + strvec_push(&cmd->args, "--stdin-packs"); + + for_each_string_list_item(item, include) + strbuf_addf(&in_buf, "%s\n", item->string); + + ret = pipe_command(cmd, in_buf.buf, in_buf.len, + out ? &out_buf : NULL, 0, NULL, 0); + + if (out) + string_list_split_f(out, out_buf.buf, "\n", -1, + STRING_LIST_SPLIT_NONEMPTY); + + strbuf_release(&in_buf); + strbuf_release(&out_buf); + + return ret; +} + +static int write_midx_included_packs(struct repack_write_midx_opts *opts) { struct child_process cmd = CHILD_PROCESS_INIT; struct string_list include = STRING_LIST_INIT_DUP; struct string_list_item *item; struct packed_git *preferred = pack_geometry_preferred_pack(opts->geometry); - FILE *in; int ret = 0; midx_included_packs(&include, opts); if (!include.nr) goto done; - cmd.in = -1; - cmd.git_cmd = 1; - - strvec_push(&cmd.args, "multi-pack-index"); - strvec_pushl(&cmd.args, "write", "--stdin-packs", NULL); - - if (opts->show_progress) - strvec_push(&cmd.args, "--progress"); - else - strvec_push(&cmd.args, "--no-progress"); - - if (opts->write_bitmaps) - strvec_push(&cmd.args, "--bitmap"); + repack_prepare_midx_command(&cmd, opts, "write"); if (preferred) strvec_pushf(&cmd.args, "--preferred-pack=%s", @@ -342,16 +377,7 @@ int write_midx_included_packs(struct repack_write_midx_opts *opts) strvec_pushf(&cmd.args, "--refs-snapshot=%s", opts->refs_snapshot); - ret = start_command(&cmd); - if (ret) - goto done; - - in = xfdopen(cmd.in, "w"); - for_each_string_list_item(item, &include) - fprintf(in, "%s\n", item->string); - fclose(in); - - ret = finish_command(&cmd); + ret = repack_fill_midx_stdin_packs(&cmd, &include, NULL); done: if (!ret && opts->write_bitmaps) remove_redundant_bitmaps(&include, opts->packdir); @@ -360,3 +386,637 @@ int write_midx_included_packs(struct repack_write_midx_opts *opts) return ret; } + +struct midx_compaction_step { + union { + struct multi_pack_index *copy; + struct string_list write; + struct { + struct multi_pack_index *from; + struct multi_pack_index *to; + } compact; + } u; + + uint32_t objects_nr; + char *csum; + + enum { + MIDX_COMPACTION_STEP_UNKNOWN, + MIDX_COMPACTION_STEP_COPY, + MIDX_COMPACTION_STEP_WRITE, + MIDX_COMPACTION_STEP_COMPACT, + } type; +}; + +static const char *midx_compaction_step_base(const struct midx_compaction_step *step) +{ + switch (step->type) { + case MIDX_COMPACTION_STEP_UNKNOWN: + BUG("cannot use UNKNOWN step as a base"); + case MIDX_COMPACTION_STEP_COPY: + return midx_get_checksum_hex(step->u.copy); + case MIDX_COMPACTION_STEP_WRITE: + BUG("cannot use WRITE step as a base"); + case MIDX_COMPACTION_STEP_COMPACT: + return midx_get_checksum_hex(step->u.compact.to); + default: + BUG("unhandled midx compaction step type %d", step->type); + } +} + +static int midx_compaction_step_exec_copy(struct midx_compaction_step *step) +{ + step->csum = xstrdup(midx_get_checksum_hex(step->u.copy)); + return 0; +} + +static int midx_compaction_step_exec_write(struct midx_compaction_step *step, + struct repack_write_midx_opts *opts, + const char *base) +{ + struct child_process cmd = CHILD_PROCESS_INIT; + struct string_list hash = STRING_LIST_INIT_DUP; + struct string_list_item *item; + const char *preferred_pack = NULL; + int ret = 0; + + if (!step->u.write.nr) { + ret = error(_("no packs to write MIDX during compaction")); + goto out; + } + + for_each_string_list_item(item, &step->u.write) { + if (item->util) + preferred_pack = item->string; + } + + repack_prepare_midx_command(&cmd, opts, "write"); + strvec_pushl(&cmd.args, "--incremental", "--no-write-chain-file", NULL); + strvec_pushf(&cmd.args, "--base=%s", base ? base : "none"); + + if (preferred_pack) { + struct strbuf buf = STRBUF_INIT; + + strbuf_addstr(&buf, preferred_pack); + strbuf_strip_suffix(&buf, ".idx"); + strbuf_addstr(&buf, ".pack"); + + strvec_pushf(&cmd.args, "--preferred-pack=%s", buf.buf); + + strbuf_release(&buf); + } + + ret = repack_fill_midx_stdin_packs(&cmd, &step->u.write, &hash); + if (hash.nr != 1) { + ret = error(_("expected exactly one line during MIDX write, " + "got: %"PRIuMAX), + (uintmax_t)hash.nr); + goto out; + } + + step->csum = xstrdup(hash.items[0].string); + +out: + string_list_clear(&hash, 0); + + return ret; +} + +static int midx_compaction_step_exec_compact(struct midx_compaction_step *step, + struct repack_write_midx_opts *opts) +{ + struct child_process cmd = CHILD_PROCESS_INIT; + struct strbuf buf = STRBUF_INIT; + FILE *out = NULL; + int ret; + + repack_prepare_midx_command(&cmd, opts, "compact"); + strvec_pushl(&cmd.args, "--incremental", "--no-write-chain-file", + midx_get_checksum_hex(step->u.compact.from), + midx_get_checksum_hex(step->u.compact.to), NULL); + + cmd.out = -1; + + ret = start_command(&cmd); + if (ret) + goto out; + + out = xfdopen(cmd.out, "r"); + while (strbuf_getline_lf(&buf, out) != EOF) { + if (step->csum) { + ret = error(_("unexpected MIDX output: '%s'"), buf.buf); + fclose(out); + out = NULL; + finish_command(&cmd); + goto out; + } + step->csum = strbuf_detach(&buf, NULL); + } + + ret = finish_command(&cmd); + +out: + if (out) + fclose(out); + strbuf_release(&buf); + + return ret; +} + +static int midx_compaction_step_exec(struct midx_compaction_step *step, + struct repack_write_midx_opts *opts, + const char *base) +{ + switch (step->type) { + case MIDX_COMPACTION_STEP_UNKNOWN: + BUG("cannot execute UNKNOWN midx compaction step"); + case MIDX_COMPACTION_STEP_COPY: + return midx_compaction_step_exec_copy(step); + case MIDX_COMPACTION_STEP_WRITE: + return midx_compaction_step_exec_write(step, opts, base); + case MIDX_COMPACTION_STEP_COMPACT: + return midx_compaction_step_exec_compact(step, opts); + default: + BUG("unhandled midx compaction step type %d", step->type); + } +} + +static void midx_compaction_step_release(struct midx_compaction_step *step) +{ + if (step->type == MIDX_COMPACTION_STEP_WRITE) + string_list_clear(&step->u.write, 0); + free(step->csum); +} + +/* + * Build an append-only MIDX plan: a single WRITE step for the freshly + * written packs, plus COPY steps for every existing layer. No + * compaction or merging is performed. + */ +static void repack_make_midx_append_plan(struct repack_write_midx_opts *opts, + struct midx_compaction_step **steps_p, + size_t *steps_nr_p) +{ + struct multi_pack_index *m; + struct midx_compaction_step *steps = NULL; + struct midx_compaction_step *step; + size_t steps_nr = 0, steps_alloc = 0; + + odb_reprepare(opts->existing->repo->objects); + m = get_multi_pack_index(opts->existing->source); + + if (opts->names->nr) { + struct strbuf buf = STRBUF_INIT; + uint32_t i; + + ALLOC_GROW(steps, st_add(steps_nr, 1), steps_alloc); + + step = &steps[steps_nr++]; + memset(step, 0, sizeof(*step)); + + step->type = MIDX_COMPACTION_STEP_WRITE; + string_list_init_dup(&step->u.write); + + for (i = 0; i < opts->names->nr; i++) { + strbuf_reset(&buf); + strbuf_addf(&buf, "pack-%s.idx", + opts->names->items[i].string); + string_list_append(&step->u.write, buf.buf); + } + + strbuf_release(&buf); + } + + for (; m; m = m->base_midx) { + ALLOC_GROW(steps, st_add(steps_nr, 1), steps_alloc); + + step = &steps[steps_nr++]; + memset(step, 0, sizeof(*step)); + + step->type = MIDX_COMPACTION_STEP_COPY; + step->u.copy = m; + step->objects_nr = m->num_objects; + } + + *steps_p = steps; + *steps_nr_p = steps_nr; +} + +static int repack_make_midx_compaction_plan(struct repack_write_midx_opts *opts, + struct midx_compaction_step **steps_p, + size_t *steps_nr_p) +{ + struct multi_pack_index *m; + struct midx_compaction_step *steps = NULL; + struct midx_compaction_step step = { 0 }; + struct strbuf buf = STRBUF_INIT; + size_t steps_nr = 0, steps_alloc = 0; + uint32_t i; + int ret = 0; + + trace2_region_enter("repack", "make_midx_compaction_plan", + opts->existing->repo); + + odb_reprepare(opts->existing->repo->objects); + m = get_multi_pack_index(opts->existing->source); + + for (i = 0; m && i < m->num_packs + m->num_packs_in_base; i++) { + if (prepare_midx_pack(m, i)) { + ret = error(_("could not load pack %"PRIu32" from MIDX"), + i); + goto out; + } + } + + trace2_region_enter("repack", "steps:write", opts->existing->repo); + + /* + * The first MIDX in the resulting chain is always going to be + * new. + * + * At a minimum, it will include all of the newly written packs. + * If there is an existing MIDX whose tip layer contains packs + * that were repacked, it will also include any of its packs + * which were *not* rolled up as part of the geometric repack + * (if any), and the previous tip will be replaced. + * + * It may grow to include the packs from zero or more MIDXs from + * the old chain, beginning either at the old tip (if the MIDX + * was *not* rewritten) or the old tip's base MIDX layer + * (otherwise). + */ + step.type = MIDX_COMPACTION_STEP_WRITE; + string_list_init_dup(&step.u.write); + + for (i = 0; i < opts->names->nr; i++) { + strbuf_reset(&buf); + strbuf_addf(&buf, "pack-%s.idx", opts->names->items[i].string); + string_list_append(&step.u.write, buf.buf); + + trace2_data_string("repack", opts->existing->repo, + "include:fresh", + step.u.write.items[step.u.write.nr - 1].string); + } + for (i = 0; i < opts->geometry->split; i++) { + struct packed_git *p = opts->geometry->pack[i]; + if (unsigned_add_overflows(step.objects_nr, p->num_objects)) { + ret = error(_("too many objects in MIDX compaction step")); + goto out; + } + + step.objects_nr += p->num_objects; + } + trace2_data_intmax("repack", opts->existing->repo, + "include:fresh:objects_nr", + (uintmax_t)step.objects_nr); + + /* + * Now handle any existing packs which were *not* rewritten. + * + * The list of packs in opts->geometry only contains MIDX'd + * packs from the newest layer when that layer has more than + * 'repack.midxNewLayerThreshold' number of packs. + * + * If the MIDX tip was rewritten (that is, one or more of those + * packs appear below the split line), then add all packs above + * the split line to the new layer, as the old one is no longer + * usable. + * + * If the MIDX tip was not rewritten (that is, all MIDX'd packs + * from the youngest layer appear below the split line, or were + * not included in the geometric repack at all because there + * were too few of them), ignore them since we'll retain the + * existing layer as-is. + */ + for (i = opts->geometry->split; i < opts->geometry->pack_nr; i++) { + struct packed_git *p = opts->geometry->pack[i]; + struct string_list_item *item; + + strbuf_reset(&buf); + strbuf_addstr(&buf, pack_basename(p)); + strbuf_strip_suffix(&buf, ".pack"); + strbuf_addstr(&buf, ".idx"); + + if (p->multi_pack_index && + !opts->geometry->midx_tip_rewritten) { + trace2_data_string("repack", opts->existing->repo, + "exclude:unmodified", buf.buf); + continue; + } + + trace2_data_string("repack", opts->existing->repo, + "include:unmodified", buf.buf); + trace2_data_string("repack", opts->existing->repo, + "include:unmodified:midx", + p->multi_pack_index ? "true" : "false"); + + item = string_list_append(&step.u.write, buf.buf); + if (p->multi_pack_index || i == opts->geometry->pack_nr - 1) + item->util = (void *)1; /* mark as preferred */ + + if (unsigned_add_overflows(step.objects_nr, p->num_objects)) { + ret = error(_("too many objects in MIDX compaction step")); + goto out; + } + + step.objects_nr += p->num_objects; + } + trace2_data_intmax("repack", opts->existing->repo, + "include:unmodified:objects_nr", + (uintmax_t)step.objects_nr); + + /* + * If the MIDX tip was rewritten, then we no longer consider it + * a candidate for compaction, since it will not exist in the + * MIDX chain being built. + */ + if (opts->geometry->midx_tip_rewritten) + m = m->base_midx; + + trace2_data_string("repack", opts->existing->repo, "midx:rewrote-tip", + opts->geometry->midx_tip_rewritten ? "true" : "false"); + + trace2_region_enter("repack", "compact", opts->existing->repo); + + /* + * Compact additional MIDX layers into this proposed one until + * the merging condition is violated. + */ + while (m) { + uint32_t preferred_pack_idx; + + trace2_data_string("repack", opts->existing->repo, + "candidate", midx_get_checksum_hex(m)); + + if (step.objects_nr < m->num_objects / opts->midx_split_factor) { + /* + * Stop compacting MIDX layer as soon as the + * merged size is less than half the size of the + * next layer in the chain. + */ + trace2_data_string("repack", opts->existing->repo, + "compact", "violated"); + trace2_data_intmax("repack", opts->existing->repo, + "objects_nr", + (uintmax_t)step.objects_nr); + trace2_data_intmax("repack", opts->existing->repo, + "next_objects_nr", + (uintmax_t)m->num_objects); + trace2_data_intmax("repack", opts->existing->repo, + "split_factor", + (uintmax_t)opts->midx_split_factor); + + break; + } + + if (midx_preferred_pack(m, &preferred_pack_idx) < 0) { + ret = error(_("could not find preferred pack for MIDX " + "%s"), midx_get_checksum_hex(m)); + goto out; + } + + for (i = 0; i < m->num_packs; i++) { + struct string_list_item *item; + uint32_t pack_int_id = i + m->num_packs_in_base; + struct packed_git *p = nth_midxed_pack(m, pack_int_id); + + strbuf_reset(&buf); + strbuf_addstr(&buf, pack_basename(p)); + strbuf_strip_suffix(&buf, ".pack"); + strbuf_addstr(&buf, ".idx"); + + trace2_data_string("repack", opts->existing->repo, + "midx:pack", buf.buf); + + item = string_list_append(&step.u.write, buf.buf); + if (pack_int_id == preferred_pack_idx) + item->util = (void *)1; /* mark as preferred */ + } + + if (unsigned_add_overflows(step.objects_nr, m->num_objects)) { + ret = error(_("too many objects in MIDX compaction step")); + goto out; + } + step.objects_nr += m->num_objects; + + m = m->base_midx; + } + + if (step.u.write.nr > 0) { + /* + * As long as there is at least one new pack to write + * (and thus the MIDX is non-empty), add it to the plan. + */ + ALLOC_GROW(steps, steps_nr + 1, steps_alloc); + steps[steps_nr++] = step; + } + + trace2_data_intmax("repack", opts->existing->repo, + "step:objects_nr", (uintmax_t)step.objects_nr); + trace2_data_intmax("repack", opts->existing->repo, + "step:packs_nr", (uintmax_t)step.u.write.nr); + + trace2_region_leave("repack", "compact", opts->existing->repo); + trace2_region_leave("repack", "steps:write", opts->existing->repo); + + trace2_region_enter("repack", "steps:rest", opts->existing->repo); + + /* + * Then start over, repeat, and either compact or keep as-is + * each MIDX layer until we have exhausted the chain. + * + * Finally, evaluate the remainder of the chain (if any) and + * either compact a sequence of adjacent layers, or keep + * individual layers as-is according to the same merging + * condition as above. + */ + while (m) { + struct multi_pack_index *next = m; + + ALLOC_GROW(steps, steps_nr + 1, steps_alloc); + + memset(&step, 0, sizeof(step)); + step.type = MIDX_COMPACTION_STEP_UNKNOWN; + + trace2_region_enter("repack", "step", opts->existing->repo); + + trace2_data_string("repack", opts->existing->repo, + "from", midx_get_checksum_hex(m)); + + while (next) { + uint32_t proposed_objects_nr; + if (unsigned_add_overflows(step.objects_nr, next->num_objects)) { + ret = error(_("too many objects in MIDX compaction step")); + trace2_region_leave("repack", "step", opts->existing->repo); + goto out; + } + + proposed_objects_nr = step.objects_nr + next->num_objects; + + trace2_data_string("repack", opts->existing->repo, + "proposed", + midx_get_checksum_hex(next)); + trace2_data_intmax("repack", opts->existing->repo, + "proposed:objects_nr", + (uintmax_t)next->num_objects); + + if (!next->base_midx) { + /* + * If we are at the end of the MIDX + * chain, there is nothing to compact, + * so mark it and stop. + */ + step.objects_nr = proposed_objects_nr; + break; + } + + if (proposed_objects_nr < next->base_midx->num_objects / opts->midx_split_factor) { + /* + * If there is a MIDX following this + * one, but our accumulated size is less + * than half of its size, compacting + * them would violate the merging + * condition, so stop here. + */ + + trace2_data_string("repack", opts->existing->repo, + "compact:violated:at", + midx_get_checksum_hex(next->base_midx)); + trace2_data_intmax("repack", opts->existing->repo, + "compact:violated:at:objects_nr", + (uintmax_t)next->base_midx->num_objects); + break; + } + + /* + * Otherwise, it is OK to compact the next layer + * into this one. Do so, and then continue + * through the remainder of the chain. + */ + step.objects_nr = proposed_objects_nr; + trace2_data_intmax("repack", opts->existing->repo, + "step:objects_nr", + (uintmax_t)step.objects_nr); + next = next->base_midx; + } + + if (m == next) { + step.type = MIDX_COMPACTION_STEP_COPY; + step.u.copy = m; + + trace2_data_string("repack", opts->existing->repo, + "type", "copy"); + } else { + step.type = MIDX_COMPACTION_STEP_COMPACT; + step.u.compact.from = next; + step.u.compact.to = m; + + trace2_data_string("repack", opts->existing->repo, + "to", midx_get_checksum_hex(m)); + trace2_data_string("repack", opts->existing->repo, + "type", "compact"); + } + + m = next->base_midx; + steps[steps_nr++] = step; + trace2_region_leave("repack", "step", opts->existing->repo); + } + + trace2_region_leave("repack", "steps:rest", opts->existing->repo); + +out: + *steps_p = steps; + *steps_nr_p = steps_nr; + + strbuf_release(&buf); + + trace2_region_leave("repack", "make_midx_compaction_plan", + opts->existing->repo); + + return ret; +} + +static int write_midx_incremental(struct repack_write_midx_opts *opts) +{ + struct midx_compaction_step *steps = NULL; + struct strbuf lock_name = STRBUF_INIT; + struct lock_file lf; + struct strvec keep_hashes = STRVEC_INIT; + size_t steps_nr = 0; + size_t i; + int ret = 0; + + get_midx_chain_filename(opts->existing->source, &lock_name); + if (safe_create_leading_directories(opts->existing->repo, + lock_name.buf)) + die_errno(_("unable to create leading directories of %s"), + lock_name.buf); + hold_lock_file_for_update(&lf, lock_name.buf, LOCK_DIE_ON_ERROR); + + if (!fdopen_lock_file(&lf, "w")) { + ret = error_errno(_("unable to open multi-pack-index chain file")); + goto done; + } + + if (opts->geometry->split_factor) { + if (repack_make_midx_compaction_plan(opts, &steps, &steps_nr) < 0) { + ret = error(_("unable to generate compaction plan")); + goto done; + } + } else { + repack_make_midx_append_plan(opts, &steps, &steps_nr); + } + + for (i = 0; i < steps_nr; i++) { + struct midx_compaction_step *step = &steps[i]; + char *base = NULL; + + if (i + 1 < steps_nr) + base = xstrdup(midx_compaction_step_base(&steps[i + 1])); + + if (midx_compaction_step_exec(step, opts, base) < 0) { + ret = error(_("unable to execute compaction step %"PRIuMAX), + (uintmax_t)i); + free(base); + goto done; + } + + free(base); + } + + i = steps_nr; + while (i--) { + struct midx_compaction_step *step = &steps[i]; + if (!step->csum) + BUG("missing result for compaction step %"PRIuMAX, + (uintmax_t)i); + fprintf(get_lock_file_fp(&lf), "%s\n", step->csum); + strvec_push(&keep_hashes, step->csum); + } + + commit_lock_file(&lf); + + clear_incremental_midx_files(opts->existing->repo, &keep_hashes); + +done: + strvec_clear(&keep_hashes); + strbuf_release(&lock_name); + for (i = 0; i < steps_nr; i++) + midx_compaction_step_release(&steps[i]); + free(steps); + return ret; +} + +int repack_write_midx(struct repack_write_midx_opts *opts) +{ + switch (opts->mode) { + case REPACK_WRITE_MIDX_NONE: + BUG("write_midx mode is NONE?"); + case REPACK_WRITE_MIDX_DEFAULT: + return write_midx_included_packs(opts); + case REPACK_WRITE_MIDX_INCREMENTAL: + return write_midx_incremental(opts); + default: + BUG("unhandled write_midx mode: %d", opts->mode); + } +}
diff --git a/repack.c b/repack.c index 5968410..571dabb 100644 --- a/repack.c +++ b/repack.c
@@ -55,14 +55,18 @@ void pack_objects_args_release(struct pack_objects_args *args) } void repack_remove_redundant_pack(struct repository *repo, const char *dir_name, - const char *base_name) + const char *base_name, + bool wrote_incremental_midx) { struct strbuf buf = STRBUF_INIT; struct odb_source *source = repo->objects->sources; struct multi_pack_index *m = get_multi_pack_index(source); strbuf_addf(&buf, "%s.pack", base_name); - if (m && source->local && midx_contains_pack(m, buf.buf)) + if (m && source->local && midx_contains_pack(m, buf.buf)) { clear_midx_file(repo); + if (!wrote_incremental_midx) + clear_incremental_midx_files(repo, NULL); + } strbuf_insertf(&buf, 0, "%s/", dir_name); unlink_pack_path(buf.buf, 1); strbuf_release(&buf); @@ -154,6 +158,8 @@ void existing_packs_collect(struct existing_packs *existing, string_list_append(&existing->non_kept_packs, buf.buf); } + existing->source = existing->repo->objects->sources; + string_list_sort(&existing->kept_packs); string_list_sort(&existing->non_kept_packs); string_list_sort(&existing->cruft_packs); @@ -248,25 +254,63 @@ void existing_packs_mark_for_deletion(struct existing_packs *existing, &existing->cruft_packs); } +/* + * Mark every pack that is referenced by the existing MIDX chain as + * retained, so that a subsequent call to + * existing_packs_mark_for_deletion() will not mark them for deletion. + * + * This is used when writing an incremental MIDX layer on top of an + * existing chain: retained layers continue to reference the same + * packs on disk, so those packs must not be unlinked even if the + * freshly-written pack supersedes them. + */ +void existing_packs_retain_midx_packs(struct existing_packs *existing) +{ + struct string_list_item *item; + struct strbuf buf = STRBUF_INIT; + + for_each_string_list_item(item, &existing->midx_packs) { + struct string_list_item *found; + + strbuf_reset(&buf); + strbuf_addstr(&buf, item->string); + strbuf_strip_suffix(&buf, ".pack"); + strbuf_strip_suffix(&buf, ".idx"); + + found = string_list_lookup(&existing->non_kept_packs, buf.buf); + if (found) + existing_packs_mark_retained(found); + + found = string_list_lookup(&existing->cruft_packs, buf.buf); + if (found) + existing_packs_mark_retained(found); + } + + strbuf_release(&buf); +} + static void remove_redundant_packs_1(struct repository *repo, struct string_list *packs, - const char *packdir) + const char *packdir, + bool wrote_incremental_midx) { struct string_list_item *item; for_each_string_list_item(item, packs) { if (!existing_pack_is_marked_for_deletion(item)) continue; - repack_remove_redundant_pack(repo, packdir, item->string); + repack_remove_redundant_pack(repo, packdir, item->string, + wrote_incremental_midx); } } void existing_packs_remove_redundant(struct existing_packs *existing, - const char *packdir) + const char *packdir, + bool wrote_incremental_midx) { remove_redundant_packs_1(existing->repo, &existing->non_kept_packs, - packdir); + packdir, wrote_incremental_midx); remove_redundant_packs_1(existing->repo, &existing->cruft_packs, - packdir); + packdir, wrote_incremental_midx); } void existing_packs_release(struct existing_packs *existing)
diff --git a/repack.h b/repack.h index bc9f2e1..f9fbc89 100644 --- a/repack.h +++ b/repack.h
@@ -34,7 +34,8 @@ void prepare_pack_objects(struct child_process *cmd, void pack_objects_args_release(struct pack_objects_args *args); void repack_remove_redundant_pack(struct repository *repo, const char *dir_name, - const char *base_name); + const char *base_name, + bool wrote_incremental_midx); struct write_pack_opts { struct pack_objects_args *po_args; @@ -56,6 +57,7 @@ struct packed_git; struct existing_packs { struct repository *repo; + struct odb_source *source; struct string_list kept_packs; struct string_list non_kept_packs; struct string_list cruft_packs; @@ -82,8 +84,10 @@ void existing_packs_retain_cruft(struct existing_packs *existing, struct packed_git *cruft); void existing_packs_mark_for_deletion(struct existing_packs *existing, struct string_list *names); +void existing_packs_retain_midx_packs(struct existing_packs *existing); void existing_packs_remove_redundant(struct existing_packs *existing, - const char *packdir); + const char *packdir, + bool wrote_incremental_midx); void existing_packs_release(struct existing_packs *existing); struct generated_pack; @@ -107,6 +111,10 @@ struct pack_geometry { uint32_t promisor_pack_nr, promisor_pack_alloc; uint32_t promisor_split; + uint32_t midx_layer_threshold; + bool midx_layer_threshold_set; + bool midx_tip_rewritten; + int split_factor; }; @@ -124,11 +132,18 @@ struct packed_git *pack_geometry_preferred_pack(struct pack_geometry *geometry); void pack_geometry_remove_redundant(struct pack_geometry *geometry, struct string_list *names, struct existing_packs *existing, - const char *packdir); + const char *packdir, + bool wrote_incremental_midx); void pack_geometry_release(struct pack_geometry *geometry); struct tempfile; +enum repack_write_midx_mode { + REPACK_WRITE_MIDX_NONE, + REPACK_WRITE_MIDX_DEFAULT, + REPACK_WRITE_MIDX_INCREMENTAL, +}; + struct repack_write_midx_opts { struct existing_packs *existing; struct pack_geometry *geometry; @@ -138,10 +153,13 @@ struct repack_write_midx_opts { int show_progress; int write_bitmaps; int midx_must_contain_cruft; + int midx_split_factor; + int midx_new_layer_threshold; + enum repack_write_midx_mode mode; }; void midx_snapshot_refs(struct repository *repo, struct tempfile *f); -int write_midx_included_packs(struct repack_write_midx_opts *opts); +int repack_write_midx(struct repack_write_midx_opts *opts); int write_filtered_pack(const struct write_pack_opts *opts, struct existing_packs *existing,
diff --git a/replay.c b/replay.c index f96f1f6..da531d5 100644 --- a/replay.c +++ b/replay.c
@@ -120,7 +120,7 @@ static struct commit *create_commit(struct repository *repo, out: repo_unuse_commit_buffer(repo, based_on, message); free_commit_extra_headers(extra); - free_commit_list(parents); + commit_list_free(parents); strbuf_release(&msg); free(author); return (struct commit *)obj; @@ -269,7 +269,8 @@ static struct commit *pick_regular_commit(struct repository *repo, struct commit *onto, struct merge_options *merge_opt, struct merge_result *result, - enum replay_mode mode) + enum replay_mode mode, + enum replay_empty_commit_action empty) { struct commit *base, *replayed_base; struct tree *pickme_tree, *base_tree, *replayed_base_tree; @@ -321,12 +322,25 @@ static struct commit *pick_regular_commit(struct repository *repo, } merge_opt->ancestor = NULL; merge_opt->branch2 = NULL; + if (!result->clean) return NULL; - /* Drop commits that become empty */ + + /* Handle commits that become empty */ if (oideq(&replayed_base_tree->object.oid, &result->tree->object.oid) && - !oideq(&pickme_tree->object.oid, &base_tree->object.oid)) - return replayed_base; + !oideq(&pickme_tree->object.oid, &base_tree->object.oid)) { + switch (empty) { + case REPLAY_EMPTY_COMMIT_DROP: + return replayed_base; + case REPLAY_EMPTY_COMMIT_KEEP: + break; + case REPLAY_EMPTY_COMMIT_ABORT: + result->clean = error(_("commit %s became empty after replay"), + oid_to_hex(&pickme->object.oid)); + return NULL; + } + } + return create_commit(repo, result->tree, pickme, replayed_base, mode); } @@ -417,7 +431,7 @@ int replay_revisions(struct rev_info *revs, last_commit = pick_regular_commit(revs->repo, commit, replayed_commits, mode == REPLAY_MODE_REVERT ? last_commit : onto, - &merge_opt, &result, mode); + &merge_opt, &result, mode, opts->empty); if (!last_commit) break; @@ -458,6 +472,11 @@ int replay_revisions(struct rev_info *revs, } } + if (result.clean < 0) { + ret = -1; + goto out; + } + if (!result.clean) { ret = 1; goto out;
diff --git a/replay.h b/replay.h index 0ab74b9..faf95c7 100644 --- a/replay.h +++ b/replay.h
@@ -7,6 +7,19 @@ struct repository; struct rev_info; /* + * Controls what happens when a replayed commit becomes empty (i.e. its tree + * is identical to its parent's tree after the replay). + */ +enum replay_empty_commit_action { + /* Silently discard the empty commit. */ + REPLAY_EMPTY_COMMIT_DROP, + /* Keep the empty commit as-is. */ + REPLAY_EMPTY_COMMIT_KEEP, + /* Abort with an error. */ + REPLAY_EMPTY_COMMIT_ABORT, +}; + +/* * A set of options that can be passed to `replay_revisions()`. */ struct replay_revisions_options { @@ -19,7 +32,7 @@ struct replay_revisions_options { /* * Starting point at which to create the new commits; must be a - * committish. References pointing at decendants of `onto` will be + * committish. References pointing at descendants of `onto` will be * updated to point to the new commits. */ const char *onto; @@ -43,6 +56,12 @@ struct replay_revisions_options { * Requires `onto` to be set. */ int contained; + + /* + * Controls what to do when a replayed commit becomes empty. + * Defaults to REPLAY_EMPTY_COMMIT_DROP. + */ + enum replay_empty_commit_action empty; }; /* This struct is used as an out-parameter by `replay_revisions()`. */
diff --git a/repository.c b/repository.c index 9e5537f..187dd47 100644 --- a/repository.c +++ b/repository.c
@@ -181,12 +181,6 @@ void repo_set_gitdir(struct repository *repo, free(old_gitdir); repo_set_commondir(repo, o->commondir); - - if (!repo->objects) - repo->objects = odb_new(repo, o->object_dir, o->alternate_db); - else if (!o->skip_initializing_odb) - BUG("cannot reinitialize an already-initialized object directory"); - repo->disable_ref_updates = o->disable_ref_updates; expand_base_dir(&repo->graft_file, o->graft_file, @@ -262,8 +256,8 @@ void repo_set_worktree(struct repository *repo, const char *path) trace2_def_repo(repo); } -static int read_and_verify_repository_format(struct repository_format *format, - const char *commondir) +static int read_repository_format_from_commondir(struct repository_format *format, + const char *commondir) { int ret = 0; struct strbuf sb = STRBUF_INIT; @@ -272,11 +266,6 @@ static int read_and_verify_repository_format(struct repository_format *format, read_repository_format(format, sb.buf); strbuf_reset(&sb); - if (verify_repository_format(format, &sb) < 0) { - warning("%s", sb.buf); - ret = -1; - } - strbuf_release(&sb); return ret; } @@ -290,6 +279,8 @@ int repo_init(struct repository *repo, const char *worktree) { struct repository_format format = REPOSITORY_FORMAT_INIT; + struct strbuf err = STRBUF_INIT; + memset(repo, 0, sizeof(*repo)); initialize_repository(repo); @@ -297,32 +288,24 @@ int repo_init(struct repository *repo, if (repo_init_gitdir(repo, gitdir)) goto error; - if (read_and_verify_repository_format(&format, repo->commondir)) + if (read_repository_format_from_commondir(&format, repo->commondir)) goto error; - repo_set_hash_algo(repo, format.hash_algo); - repo_set_compat_hash_algo(repo, format.compat_hash_algo); - repo_set_ref_storage_format(repo, format.ref_storage_format, - format.ref_storage_payload); - repo->repository_format_worktree_config = format.worktree_config; - repo->repository_format_relative_worktrees = format.relative_worktrees; - repo->repository_format_precious_objects = format.precious_objects; - repo->repository_format_submodule_path_cfg = format.submodule_path_cfg; - - /* take ownership of format.partial_clone */ - repo->repository_format_partial_clone = format.partial_clone; - format.partial_clone = NULL; + if (apply_repository_format(repo, &format, 0, &err) < 0) { + warning("%s", err.buf); + goto error; + } if (worktree) repo_set_worktree(repo, worktree); - if (repo->compat_hash_algo) - repo_read_loose_object_map(repo); - clear_repository_format(&format); + strbuf_release(&err); return 0; error: + clear_repository_format(&format); + strbuf_release(&err); repo_clear(repo); return -1; } @@ -425,6 +408,8 @@ void repo_clear(struct repository *repo) hook_cache_clear(repo->hook_config_cache); FREE_AND_NULL(repo->hook_config_cache); } + strmap_clear(&repo->event_jobs, 0); /* values are uintptr_t, not heap ptrs */ + string_list_clear(&repo->disabled_events, 0); if (repo->promisor_remote_config) { promisor_remote_clear(repo->promisor_remote_config);
diff --git a/repository.h b/repository.h index 078059a..36e2db2 100644 --- a/repository.h +++ b/repository.h
@@ -2,6 +2,7 @@ #define REPOSITORY_H #include "strmap.h" +#include "string-list.h" #include "repo-settings.h" #include "environment.h" @@ -113,6 +114,8 @@ struct repository { * A NULL value indicates that there is no working directory. */ char *worktree; + bool worktree_initialized; + bool worktree_config_is_bogus; /* * Path from the root of the top-level superproject down to this @@ -172,6 +175,15 @@ struct repository { */ struct strmap *hook_config_cache; + /* Cached value of hook.jobs config (0 if unset, defaults to serial). */ + unsigned int hook_jobs; + + /* Cached map of event-name -> jobs count (as uintptr_t) from hook.<event>.jobs. */ + struct strmap event_jobs; + + /* Cached list of event names with hook.<event>.enabled = false. */ + struct string_list disabled_events; + /* Configurations related to promisor remotes. */ char *repository_format_partial_clone; struct promisor_remote_config *promisor_remote_config; @@ -209,12 +221,9 @@ const char *repo_get_work_tree(struct repository *repo); */ struct set_gitdir_args { const char *commondir; - const char *object_dir; const char *graft_file; const char *index_file; - const char *alternate_db; bool disable_ref_updates; - bool skip_initializing_odb; }; void repo_set_gitdir(struct repository *repo, const char *root, @@ -269,6 +278,6 @@ void repo_update_index_if_able(struct repository *, struct lock_file *); * Return 1 if upgrade repository format to target_version succeeded, * 0 if no upgrade is necessary, and -1 when upgrade is not possible. */ -int upgrade_repository_format(int target_version); +int upgrade_repository_format(struct repository *repo, int target_version); #endif /* REPOSITORY_H */
diff --git a/rerere.c b/rerere.c index 0296700..28a740b 100644 --- a/rerere.c +++ b/rerere.c
@@ -548,7 +548,7 @@ static int check_one_conflict(struct index_state *istate, int i, int *type) /* * Scan the index and find paths that have conflicts that rerere can - * handle, i.e. the ones that has both stages #2 and #3. + * handle, i.e. the ones that have both stages #2 and #3. * * NEEDSWORK: we do not record or replay a previous "resolve by * deletion" for a delete-modify conflict, as that is inherently risky
diff --git a/revision.c b/revision.c index 599b3a6..e91d7e1 100644 --- a/revision.c +++ b/revision.c
@@ -473,10 +473,10 @@ static struct commit *handle_commit(struct rev_info *revs, die("%s is unknown object", name); } -static int everybody_uninteresting(struct commit_list *orig, +static int everybody_uninteresting(struct prio_queue *orig, struct commit **interesting_cache) { - struct commit_list *list = orig; + size_t i; if (*interesting_cache) { struct commit *commit = *interesting_cache; @@ -484,9 +484,8 @@ static int everybody_uninteresting(struct commit_list *orig, return 0; } - while (list) { - struct commit *commit = list->item; - list = list->next; + for (i = 0; i < orig->nr; i++) { + struct commit *commit = orig->array[i].data; if (commit->object.flags & UNINTERESTING) continue; @@ -1117,7 +1116,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit) } static int process_parents(struct rev_info *revs, struct commit *commit, - struct commit_list **list, struct prio_queue *queue) + struct prio_queue *queue) { struct commit_list *parent = commit->parents; unsigned pass_flags; @@ -1159,8 +1158,6 @@ static int process_parents(struct rev_info *revs, struct commit *commit, if (p->object.flags & SEEN) continue; p->object.flags |= (SEEN | NOT_USER_GIVEN); - if (list) - commit_list_insert_by_date(p, list); if (queue) prio_queue_put(queue, p); if (revs->exclude_first_parent_only) @@ -1208,8 +1205,6 @@ static int process_parents(struct rev_info *revs, struct commit *commit, p->object.flags |= pass_flags | CHILD_VISITED; if (!(p->object.flags & SEEN)) { p->object.flags |= (SEEN | NOT_USER_GIVEN); - if (list) - commit_list_insert_by_date(p, list); if (queue) prio_queue_put(queue, p); } @@ -1300,20 +1295,17 @@ static void cherry_pick_list(struct commit_list *list, struct rev_info *revs) /* How many extra uninteresting commits we want to see.. */ #define SLOP 5 -static int still_interesting(struct commit_list *src, timestamp_t date, int slop, +static int still_interesting(struct prio_queue *src, timestamp_t date, int slop, struct commit **interesting_cache) { /* - * No source list at all? We're definitely done.. + * Since src is sorted by date, it is enough to peek at the + * first entry to compare dates. No entry at all means done. */ - if (!src) + struct commit *commit = prio_queue_peek(src); + if (!commit) return 0; - - /* - * Does the destination list contain entries with a date - * before the source list? Definitely _not_ done. - */ - if (date <= src->item->date) + if (date <= commit->date) return SLOP; /* @@ -1451,6 +1443,7 @@ static int limit_list(struct rev_info *revs) struct commit_list *newlist = NULL; struct commit_list **p = &newlist; struct commit *interesting_cache = NULL; + struct prio_queue queue = { .compare = compare_commits_by_commit_date }; if (revs->ancestry_path_implicit_bottoms) { collect_bottom_commits(original_list, @@ -1461,6 +1454,11 @@ static int limit_list(struct rev_info *revs) while (original_list) { struct commit *commit = pop_commit(&original_list); + prio_queue_put(&queue, commit); + } + + while (queue.nr) { + struct commit *commit = prio_queue_get(&queue); struct object *obj = &commit->object; if (commit == interesting_cache) @@ -1468,11 +1466,13 @@ static int limit_list(struct rev_info *revs) if (revs->max_age != -1 && (commit->date < revs->max_age)) obj->flags |= UNINTERESTING; - if (process_parents(revs, commit, &original_list, NULL) < 0) + if (process_parents(revs, commit, &queue) < 0) { + clear_prio_queue(&queue); return -1; + } if (obj->flags & UNINTERESTING) { mark_parents_uninteresting(revs, commit); - slop = still_interesting(original_list, date, slop, &interesting_cache); + slop = still_interesting(&queue, date, slop, &interesting_cache); if (slop) continue; break; @@ -1509,7 +1509,7 @@ static int limit_list(struct rev_info *revs) } } - commit_list_free(original_list); + clear_prio_queue(&queue); revs->commits = newlist; return 0; } @@ -2072,7 +2072,7 @@ static int handle_dotdot_1(const char *a_name, const char *b_name, return -1; if (!cant_be_filename) { - verify_non_filename(revs->prefix, full_name); + verify_non_filename(the_repository, revs->prefix, full_name); } a_obj = parse_object(revs->repo, &a_oid); @@ -2225,7 +2225,7 @@ static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int fl goto out; } if (!cant_be_filename) - verify_non_filename(revs->prefix, arg); + verify_non_filename(the_repository, revs->prefix, arg); object = get_reference(revs, arg, &oid, flags ^ local_flags); if (!object) { ret = (revs->ignore_missing || revs->do_not_die_on_missing_objects) ? 0 : -1; @@ -2339,10 +2339,28 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg } if ((argcount = parse_long_opt("max-count", argv, &optarg))) { + if (revs->max_count_type == 1) + die_for_incompatible_opt2(1, "--max-count", 1, + "--max-count-oldest"); revs->max_count = parse_count(optarg); revs->no_walk = 0; + revs->max_count_type = 0; return argcount; + } else if ((argcount = parse_long_opt("max-count-oldest", argv, &optarg))) { + if (revs->max_count_type == 0 && revs->max_count != -1) + die_for_incompatible_opt2(1, "--max-count", 1, + "--max-count-oldest"); + if (revs->skip_count > 0) + die_for_incompatible_opt2(1, "--skip", 1, + "--max-count-oldest"); + revs->max_count = parse_count(optarg); + revs->no_walk = 0; + revs->max_count_type = 1; + revs->max_count_stage = 0; } else if ((argcount = parse_long_opt("skip", argv, &optarg))) { + if (revs->max_count_type == 1) + die_for_incompatible_opt2(1, "--skip", 1, + "--max-count-oldest"); revs->skip_count = parse_count(optarg); return argcount; } else if ((*arg == '-') && isdigit(arg[1])) { @@ -2606,6 +2624,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg } else if (!strcmp(arg, "--no-graph")) { graph_clear(revs->graph); revs->graph = NULL; + } else if (skip_prefix(arg, "--graph-lane-limit=", &optarg)) { + revs->graph_max_lanes = parse_count(optarg); } else if (!strcmp(arg, "--encode-email-headers")) { revs->encode_email_headers = 1; } else if (!strcmp(arg, "--no-encode-email-headers")) { @@ -2922,9 +2942,10 @@ static void read_revisions_from_stdin(struct rev_info *revs, int seen_end_of_options = 0; int save_warning; int flags = 0; + struct repo_config_values *cfg = repo_config_values(the_repository); - save_warning = warn_on_object_refname_ambiguity; - warn_on_object_refname_ambiguity = 0; + save_warning = cfg->warn_on_object_refname_ambiguity; + cfg->warn_on_object_refname_ambiguity = 0; strbuf_init(&sb, 1000); while (strbuf_getline(&sb, stdin) != EOF) { @@ -2958,7 +2979,7 @@ static void read_revisions_from_stdin(struct rev_info *revs, read_pathspec_from_stdin(&sb, prune); strbuf_release(&sb); - warn_on_object_refname_ambiguity = save_warning; + cfg->warn_on_object_refname_ambiguity = save_warning; } static void NORETURN diagnose_missing_default(const char *def) @@ -3067,7 +3088,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s * but the latter we have checked in the main loop. */ for (j = i; j < argc; j++) - verify_filename(revs->prefix, argv[j], j == i); + verify_filename(the_repository, revs->prefix, argv[j], j == i); strvec_pushv(&prune_data, argv + i); break; @@ -3112,6 +3133,14 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s object_context_release(&oc); } + if (revs->line_level_traverse) { + if (want_ancestry(revs)) + revs->limited = 1; + revs->topo_order = 1; + if (!revs->diffopt.output_format) + revs->diffopt.output_format = DIFF_FORMAT_PATCH; + } + /* Did the user ask for any diff output? Run the diff! */ if (revs->diffopt.output_format & ~DIFF_FORMAT_NO_OUTPUT) revs->diff = 1; @@ -3125,14 +3154,6 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s if (revs->diffopt.objfind) revs->simplify_history = 0; - if (revs->line_level_traverse) { - if (want_ancestry(revs)) - revs->limited = 1; - revs->topo_order = 1; - if (!revs->diffopt.output_format) - revs->diffopt.output_format = DIFF_FORMAT_PATCH; - } - if (revs->topo_order && !generation_numbers_enabled(the_repository)) revs->limited = 1; @@ -3175,12 +3196,20 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s if (revs->no_walk && revs->graph) die(_("options '%s' and '%s' cannot be used together"), "--no-walk", "--graph"); + + if (revs->graph_max_lanes > 0 && !revs->graph) + die(_("the option '%s' requires '%s'"), "--graph-lane-limit", "--graph"); + if (!revs->reflog_info && revs->grep_filter.use_reflog_filter) die(_("the option '%s' requires '%s'"), "--grep-reflog", "--walk-reflogs"); if (revs->line_level_traverse && - (revs->diffopt.output_format & ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT))) - die(_("-L does not yet support diff formats besides -p and -s")); + (revs->full_diff || + (revs->diffopt.output_format & + ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT | + DIFF_FORMAT_RAW | DIFF_FORMAT_NAME | + DIFF_FORMAT_NAME_STATUS | DIFF_FORMAT_SUMMARY)))) + die(_("-L does not yet support the requested diff format")); if (revs->expand_tabs_in_log < 0) revs->expand_tabs_in_log = revs->expand_tabs_in_log_default; @@ -3253,6 +3282,7 @@ static void free_void_commit_list(void *list) void release_revisions(struct rev_info *revs) { commit_list_free(revs->commits); + clear_prio_queue(&revs->commit_queue); commit_list_free(revs->ancestry_path_bottoms); release_display_notes(&revs->notes_opt); object_array_clear(&revs->pending); @@ -3722,7 +3752,7 @@ static void explore_walk_step(struct rev_info *revs) if (revs->max_age != -1 && (c->date < revs->max_age)) c->object.flags |= UNINTERESTING; - if (process_parents(revs, c, NULL, NULL) < 0) + if (process_parents(revs, c, NULL) < 0) return; if (c->object.flags & UNINTERESTING) @@ -3898,7 +3928,7 @@ static void expand_topo_walk(struct rev_info *revs, struct commit *commit) { struct commit_list *p; struct topo_walk_info *info = revs->topo_walk_info; - if (process_parents(revs, commit, NULL, NULL) < 0) { + if (process_parents(revs, commit, NULL) < 0) { if (!revs->ignore_missing_links) die("Failed to traverse parents of commit %s", oid_to_hex(&commit->object.oid)); @@ -3934,6 +3964,13 @@ static void expand_topo_walk(struct rev_info *revs, struct commit *commit) } } +void rev_info_commit_list_to_queue(struct rev_info *revs) +{ + while (revs->commits) + prio_queue_put(&revs->commit_queue, pop_commit(&revs->commits)); +} + + int prepare_revision_walk(struct rev_info *revs) { int i; @@ -4002,7 +4039,7 @@ static enum rewrite_result rewrite_one_1(struct rev_info *revs, for (;;) { struct commit *p = *pp; if (!revs->limited) - if (process_parents(revs, p, NULL, queue) < 0) + if (process_parents(revs, p, queue) < 0) return rewrite_one_error; if (p->object.flags & UNINTERESTING) return rewrite_one_ok; @@ -4016,27 +4053,18 @@ static enum rewrite_result rewrite_one_1(struct rev_info *revs, } } -static void merge_queue_into_list(struct prio_queue *q, struct commit_list **list) +static void merge_queue_into_prio_queue(struct prio_queue *from, + struct prio_queue *to) { - while (q->nr) { - struct commit *item = prio_queue_peek(q); - struct commit_list *p = *list; - - if (p && p->item->date >= item->date) - list = &p->next; - else { - p = commit_list_insert(item, list); - list = &p->next; /* skip newly added item */ - prio_queue_get(q); /* pop item */ - } - } + while (from->nr) + prio_queue_put(to, prio_queue_get(from)); } static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp) { struct prio_queue queue = { compare_commits_by_commit_date }; enum rewrite_result ret = rewrite_one_1(revs, pp, &queue); - merge_queue_into_list(&queue, &revs->commits); + merge_queue_into_prio_queue(&queue, &revs->commit_queue); clear_prio_queue(&queue); return ret; } @@ -4323,22 +4351,57 @@ static void track_linear(struct rev_info *revs, struct commit *commit) revs->previous_parents = commit_list_copy(commit->parents); } +enum rev_walk_mode { + REV_WALK_REFLOG, + REV_WALK_TOPO, + REV_WALK_LIMITED, + REV_WALK_NO_WALK, + REV_WALK_STREAMING, +}; + +static enum rev_walk_mode get_walk_mode(struct rev_info *revs) +{ + if (revs->reflog_info) + return REV_WALK_REFLOG; + if (revs->topo_walk_info) + return REV_WALK_TOPO; + if (revs->limited) + return REV_WALK_LIMITED; + if (revs->no_walk) + return REV_WALK_NO_WALK; + return REV_WALK_STREAMING; +} + static struct commit *get_revision_1(struct rev_info *revs) { + enum rev_walk_mode mode = get_walk_mode(revs); + + if (mode == REV_WALK_STREAMING && revs->commits) + rev_info_commit_list_to_queue(revs); + while (1) { struct commit *commit; - if (revs->reflog_info) + switch (mode) { + case REV_WALK_REFLOG: commit = next_reflog_entry(revs->reflog_info); - else if (revs->topo_walk_info) + break; + case REV_WALK_TOPO: commit = next_topo_commit(revs); - else + break; + case REV_WALK_LIMITED: + case REV_WALK_NO_WALK: commit = pop_commit(&revs->commits); + break; + case REV_WALK_STREAMING: + commit = prio_queue_get(&revs->commit_queue); + break; + } if (!commit) return NULL; - if (revs->reflog_info) + if (mode == REV_WALK_REFLOG) commit->object.flags &= ~(ADDED | SEEN | SHOWN); /* @@ -4346,20 +4409,29 @@ static struct commit *get_revision_1(struct rev_info *revs) * the parents here. We also need to do the date-based limiting * that we'd otherwise have done in limit_list(). */ - if (!revs->limited) { - if (revs->max_age != -1 && - comparison_date(revs, commit) < revs->max_age) - continue; + if (mode != REV_WALK_LIMITED && + revs->max_age != -1 && + comparison_date(revs, commit) < revs->max_age) + continue; - if (revs->reflog_info) - try_to_simplify_commit(revs, commit); - else if (revs->topo_walk_info) - expand_topo_walk(revs, commit); - else if (process_parents(revs, commit, &revs->commits, NULL) < 0) { + switch (mode) { + case REV_WALK_REFLOG: + try_to_simplify_commit(revs, commit); + break; + case REV_WALK_TOPO: + expand_topo_walk(revs, commit); + break; + case REV_WALK_STREAMING: + if (process_parents(revs, commit, + &revs->commit_queue) < 0) { if (!revs->ignore_missing_links) die("Failed to traverse parents of commit %s", - oid_to_hex(&commit->object.oid)); + oid_to_hex(&commit->object.oid)); } + break; + case REV_WALK_NO_WALK: + case REV_WALK_LIMITED: + break; } switch (simplify_commit(revs, commit)) { @@ -4521,15 +4593,91 @@ static struct commit *get_revision_internal(struct rev_info *revs) return c; } +static void retrieve_oldest_commits(struct rev_info *revs, + struct commit_list **queue) +{ + struct commit *c; + int max_count = revs->max_count; + int queuei_count = 0; + int queueo_count = 0; + struct commit_list *queueo = NULL; + struct commit_list *queuei = NULL; + struct commit_list *reversed_queue = NULL; + struct commit_list *p; + + revs->max_count = -1; + while ((c = get_revision_internal(revs))) { + /* + * We need to reset SHOWN status otherwise --graph breaks. + * It is fine to do, get_revision_internal() doesn't consider + * children commits as they have been already processed and the + * traversal happens only child to parent. + * + * We do this because the --graph machinery relies on the status + * of the parents to decide how the printing will happen. + * + * We can't simply replace this instruction with a + * graph_update() as it doesn't do the actualy printing, we'd + * have to remove any commit that goes over the + * --max-count-oldest limit from revs->graph. + */ + c->object.flags &= ~(SHOWN | CHILD_SHOWN); + commit_list_insert(c, &queuei); + if (!(c->object.flags & BOUNDARY)) + queuei_count++; + while (queuei_count + queueo_count > max_count) { + if (!queueo_count) { + while ((c = pop_commit(&queuei))) { + commit_list_insert(c, &queueo); + queueo_count++; + } + queuei_count = 0; + } + c = pop_commit(&queueo); + queueo_count--; + /* We need to do this otherwise we'll discard the + * commits that go over the --max-count-oldest limit but + * not their respective boundaries. This matters only if + * we're discarding the commit right before the boundary. + */ + for (p = c->parents; p; p = p->next) + p->item->object.flags &= ~CHILD_SHOWN; + } + } + + while ((c = pop_commit(&queueo))) + commit_list_insert(c, &reversed_queue); + while ((c = pop_commit(&queuei))) + commit_list_insert(c, &queueo); + while ((c = pop_commit(&queueo))) + commit_list_insert(c, &reversed_queue); + + while ((c = pop_commit(&reversed_queue))) + commit_list_insert(c, queue); +} + struct commit *get_revision(struct rev_info *revs) { struct commit *c; struct commit_list *reversed; + struct commit_list *queue = NULL; + struct commit_list *p; + + if (revs->max_count_type == 1 && !revs->max_count_stage) { + retrieve_oldest_commits(revs, &queue); + commit_list_free(revs->commits); + revs->commits = queue; + revs->max_count_stage = 1; + } if (revs->reverse) { reversed = NULL; - while ((c = get_revision_internal(revs))) - commit_list_insert(c, &reversed); + if (revs->max_count_type == 1) + while ((c = pop_commit(&revs->commits))) + commit_list_insert(c, &reversed); + else + while ((c = get_revision_internal(revs))) + commit_list_insert(c, &reversed); commit_list_free(revs->commits); revs->commits = reversed; revs->reverse = 0; @@ -4543,7 +4691,18 @@ struct commit *get_revision(struct rev_info *revs) return c; } - c = get_revision_internal(revs); + if (revs->max_count_stage) { + c = pop_commit(&revs->commits); + if (c) { + c->object.flags |= SHOWN; + if (!(c->object.flags & BOUNDARY)) + for (p = c->parents; p; p = p->next) + p->item->object.flags |= CHILD_SHOWN; + } + } else { + c = get_revision_internal(revs); + } + if (c && revs->graph) graph_update(revs->graph, c); if (!c) {
diff --git a/revision.h b/revision.h index 584f133..00c392b 100644 --- a/revision.h +++ b/revision.h
@@ -12,6 +12,7 @@ #include "decorate.h" #include "ident.h" #include "list-objects-filter-options.h" +#include "prio-queue.h" #include "strvec.h" /** @@ -122,8 +123,14 @@ struct oidset; struct topo_walk_info; struct rev_info { - /* Starting list */ + /* + * Work queue of commits, stored as either a linked list or a + * priority queue, but never both at the same time. + * rev_info_commit_list_to_queue() converts list to queue. + */ struct commit_list *commits; + struct prio_queue commit_queue; + struct object_array pending; struct repository *repo; @@ -305,10 +312,13 @@ struct rev_info { /* Display history graph */ struct git_graph *graph; + int graph_max_lanes; /* special limits */ int skip_count; int max_count; + unsigned int max_count_type:1; + unsigned int max_count_stage:1; timestamp_t max_age; timestamp_t max_age_as_filter; timestamp_t min_age; @@ -400,6 +410,7 @@ struct rev_info { * uninitialized. */ #define REV_INFO_INIT { \ + .commit_queue = { .compare = compare_commits_by_commit_date }, \ .abbrev = DEFAULT_ABBREV, \ .simplify_history = 1, \ .pruning.flags.recursive = 1, \ @@ -478,6 +489,8 @@ void reset_revision_walk(void); */ int prepare_revision_walk(struct rev_info *revs); +/* Drain the commits linked list into the priority queue. */ +void rev_info_commit_list_to_queue(struct rev_info *revs); /** * Takes a pointer to a `rev_info` structure and iterates over it, returning a * `struct commit *` each time you call it. The end of the revision list is
diff --git a/run-command.c b/run-command.c index c146a56..e70a8a3 100644 --- a/run-command.c +++ b/run-command.c
@@ -545,6 +545,7 @@ static void atfork_parent(struct atfork_state *as) "restoring signal mask"); #endif } + #endif /* GIT_WINDOWS_NATIVE */ static inline void set_cloexec(int fd) @@ -831,6 +832,17 @@ int start_command(struct child_process *cmd) child_close(cmd->out); } + if (cmd->close_fd_above_stderr) { + long max_fd = sysconf(_SC_OPEN_MAX); + int fd; + if (max_fd < 0 || max_fd > 4096) + max_fd = 4096; + for (fd = 3; fd < max_fd; fd++) { + if (fd != child_notifier) + close(fd); + } + } + if (cmd->dir && chdir(cmd->dir)) child_die(CHILD_ERR_CHDIR); @@ -1944,10 +1956,14 @@ void run_processes_parallel(const struct run_process_parallel_opts *opts) int prepare_auto_maintenance(struct repository *r, int quiet, struct child_process *maint) { - int enabled, auto_detach; + int enabled = 1, auto_detach; - if (!repo_config_get_bool(r, "maintenance.auto", &enabled) && - !enabled) + if (repo_config_get_bool(r, "maintenance.auto", &enabled)) { + int gc_threshold; + if (!repo_config_get_int(r, "gc.auto", &gc_threshold)) + enabled = gc_threshold > 0; + } + if (!enabled) return 0; /*
diff --git a/run-command.h b/run-command.h index 8ca496d..c2fad4f 100644 --- a/run-command.h +++ b/run-command.h
@@ -143,6 +143,15 @@ struct child_process { unsigned stdout_to_stderr:1; unsigned clean_on_exit:1; unsigned wait_after_clean:1; + + /** + * Close file descriptors 3 and above in the child after forking + * but before exec. This prevents the child from inheriting + * pipe endpoints or other descriptors from the parent + * environment (e.g., the test harness). + */ + unsigned close_fd_above_stderr:1; + void (*clean_on_exit_handler)(struct child_process *process); };
diff --git a/scalar.c b/scalar.c index 4efb6ac..a80d8ee 100644 --- a/scalar.c +++ b/scalar.c
@@ -58,7 +58,7 @@ static void setup_enlistment_directory(int argc, const char **argv, } strbuf_setlen(&path, len); - setup_git_directory(); + setup_git_directory(the_repository); if (!the_repository->worktree) die(_("Scalar enlistments require a worktree")); @@ -514,7 +514,7 @@ static int cmd_clone(int argc, const char **argv) goto cleanup; } - setup_git_directory(); + setup_git_directory(the_repository); /* common-main already logs `argv` */ trace2_def_repo(the_repository);
diff --git a/send-pack.c b/send-pack.c index b4361d5..3bb5afc 100644 --- a/send-pack.c +++ b/send-pack.c
@@ -434,28 +434,48 @@ static void reject_invalid_nonce(const char *nonce, int len) static void get_commons_through_negotiation(struct repository *r, const char *url, + const struct string_list *negotiation_include, + const struct string_list *negotiation_restrict, const struct ref *remote_refs, struct oid_array *commons) { struct child_process child = CHILD_PROCESS_INIT; const struct ref *ref; int len = r->hash_algo->hexsz + 1; /* hash + NL */ - int nr_negotiation_tip = 0; + int nr_negotiation = 0; child.git_cmd = 1; child.no_stdin = 1; child.out = -1; strvec_pushl(&child.args, "fetch", "--negotiate-only", NULL); - for (ref = remote_refs; ref; ref = ref->next) { - if (!is_null_oid(&ref->new_oid)) { - strvec_pushf(&child.args, "--negotiation-tip=%s", - oid_to_hex(&ref->new_oid)); - nr_negotiation_tip++; + + if (negotiation_restrict && negotiation_restrict->nr) { + struct string_list_item *item; + for_each_string_list_item(item, negotiation_restrict) + strvec_pushf(&child.args, "--negotiation-restrict=%s", + item->string); + nr_negotiation = negotiation_restrict->nr; + } else { + for (ref = remote_refs; ref; ref = ref->next) { + if (!is_null_oid(&ref->new_oid)) { + strvec_pushf(&child.args, "--negotiation-restrict=%s", + oid_to_hex(&ref->new_oid)); + nr_negotiation++; + } } } + + if (negotiation_include && negotiation_include->nr) { + struct string_list_item *item; + for_each_string_list_item(item, negotiation_include) + strvec_pushf(&child.args, "--negotiation-include=%s", + item->string); + nr_negotiation += negotiation_include->nr; + } + strvec_push(&child.args, url); - if (!nr_negotiation_tip) { + if (!nr_negotiation) { child_process_clear(&child); return; } @@ -529,7 +549,10 @@ int send_pack(struct repository *r, repo_config_get_bool(r, "push.negotiate", &push_negotiate); if (push_negotiate) { trace2_region_enter("send_pack", "push_negotiate", r); - get_commons_through_negotiation(r, args->url, remote_refs, &commons); + get_commons_through_negotiation(r, args->url, + args->negotiation_include, + args->negotiation_restrict, + remote_refs, &commons); trace2_region_leave("send_pack", "push_negotiate", r); }
diff --git a/send-pack.h b/send-pack.h index c5ded2d..13850c9 100644 --- a/send-pack.h +++ b/send-pack.h
@@ -18,6 +18,8 @@ struct repository; struct send_pack_args { const char *url; + const struct string_list *negotiation_include; + const struct string_list *negotiation_restrict; unsigned verbose:1, quiet:1, porcelain:1,
diff --git a/sequencer.c b/sequencer.c index b7d8dca..57855b0 100644 --- a/sequencer.c +++ b/sequencer.c
@@ -2223,7 +2223,7 @@ static void refer_to_commit(struct repository *r, struct strbuf *msgbuf, repo_format_commit_message(r, commit, "%h (%s, %ad)", msgbuf, &ctx); } else { - strbuf_addstr(msgbuf, oid_to_hex(&commit->object.oid)); + strbuf_add_oid_hex(msgbuf, &commit->object.oid); } } @@ -2395,7 +2395,7 @@ static int do_pick_commit(struct repository *r, if (!has_conforming_footer(&ctx->message, NULL, 0)) strbuf_addch(&ctx->message, '\n'); strbuf_addstr(&ctx->message, cherry_picked_prefix); - strbuf_addstr(&ctx->message, oid_to_hex(&commit->object.oid)); + strbuf_add_oid_hex(&ctx->message, &commit->object.oid); strbuf_addstr(&ctx->message, ")\n"); } if (!is_fixup(command)) @@ -4657,7 +4657,9 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset) static void create_autostash_internal(struct repository *r, const char *path, - const char *refname) + const char *refname, + const char *message, + bool silent) { struct strbuf buf = STRBUF_INIT; struct lock_file lock_file = LOCK_INIT; @@ -4679,7 +4681,8 @@ static void create_autostash_internal(struct repository *r, struct object_id oid; strvec_pushl(&stash.args, - "stash", "create", "autostash", NULL); + "stash", "create", + message ? message : "autostash", NULL); stash.git_cmd = 1; stash.no_stdin = 1; strbuf_reset(&buf); @@ -4702,7 +4705,8 @@ static void create_autostash_internal(struct repository *r, &oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR); } - printf(_("Created autostash: %s\n"), buf.buf); + if (!silent) + printf(_("Created autostash: %s\n"), buf.buf); if (reset_head(r, &ropts) < 0) die(_("could not reset --hard")); discard_index(r->index); @@ -4714,15 +4718,19 @@ static void create_autostash_internal(struct repository *r, void create_autostash(struct repository *r, const char *path) { - create_autostash_internal(r, path, NULL); + create_autostash_internal(r, path, NULL, NULL, false); } -void create_autostash_ref(struct repository *r, const char *refname) +void create_autostash_ref(struct repository *r, const char *refname, + const char *message, bool silent) { - create_autostash_internal(r, NULL, refname); + create_autostash_internal(r, NULL, refname, message, silent); } -static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply) +static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply, + const char *label_ours, const char *label_theirs, + const char *label_base, + const char *stash_msg) { struct child_process child = CHILD_PROCESS_INIT; int ret = 0; @@ -4733,6 +4741,12 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply) child.no_stderr = 1; strvec_push(&child.args, "stash"); strvec_push(&child.args, "apply"); + if (label_ours) + strvec_pushf(&child.args, "--label-ours=%s", label_ours); + if (label_theirs) + strvec_pushf(&child.args, "--label-theirs=%s", label_theirs); + if (label_base) + strvec_pushf(&child.args, "--label-base=%s", label_base); strvec_push(&child.args, stash_oid); ret = run_command(&child); } @@ -4746,20 +4760,24 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply) strvec_push(&store.args, "stash"); strvec_push(&store.args, "store"); strvec_push(&store.args, "-m"); - strvec_push(&store.args, "autostash"); + strvec_push(&store.args, stash_msg ? stash_msg : "autostash"); strvec_push(&store.args, "-q"); strvec_push(&store.args, stash_oid); if (run_command(&store)) ret = error(_("cannot store %s"), stash_oid); + else if (attempt_apply) + fprintf(stderr, + _("Your local changes are stashed, however applying them\n" + "resulted in conflicts. You can either resolve the conflicts\n" + "and then discard the stash with \"git stash drop\", or, if you\n" + "do not want to resolve them now, run \"git reset --hard\" and\n" + "apply the local changes later by running \"git stash pop\".\n")); else fprintf(stderr, - _("%s\n" + _("Autostash exists; creating a new stash entry.\n" "Your changes are safe in the stash.\n" "You can run \"git stash pop\" or" - " \"git stash drop\" at any time.\n"), - attempt_apply ? - _("Applying autostash resulted in conflicts.") : - _("Autostash exists; creating a new stash entry.")); + " \"git stash drop\" at any time.\n")); } return ret; @@ -4777,7 +4795,8 @@ static int apply_save_autostash(const char *path, int attempt_apply) } strbuf_trim(&stash_oid); - ret = apply_save_autostash_oid(stash_oid.buf, attempt_apply); + ret = apply_save_autostash_oid(stash_oid.buf, attempt_apply, + NULL, NULL, NULL, NULL); unlink(path); strbuf_release(&stash_oid); @@ -4796,11 +4815,14 @@ int apply_autostash(const char *path) int apply_autostash_oid(const char *stash_oid) { - return apply_save_autostash_oid(stash_oid, 1); + return apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL, NULL); } static int apply_save_autostash_ref(struct repository *r, const char *refname, - int attempt_apply) + int attempt_apply, + const char *label_ours, const char *label_theirs, + const char *label_base, + const char *stash_msg) { struct object_id stash_oid; char stash_oid_hex[GIT_MAX_HEXSZ + 1]; @@ -4816,7 +4838,9 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname, return error(_("autostash reference is a symref")); oid_to_hex_r(stash_oid_hex, &stash_oid); - ret = apply_save_autostash_oid(stash_oid_hex, attempt_apply); + ret = apply_save_autostash_oid(stash_oid_hex, attempt_apply, + label_ours, label_theirs, label_base, + stash_msg); refs_delete_ref(get_main_ref_store(r), "", refname, &stash_oid, REF_NO_DEREF); @@ -4826,12 +4850,17 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname, int save_autostash_ref(struct repository *r, const char *refname) { - return apply_save_autostash_ref(r, refname, 0); + return apply_save_autostash_ref(r, refname, 0, + NULL, NULL, NULL, NULL); } -int apply_autostash_ref(struct repository *r, const char *refname) +int apply_autostash_ref(struct repository *r, const char *refname, + const char *label_ours, const char *label_theirs, + const char *label_base, const char *stash_msg) { - return apply_save_autostash_ref(r, refname, 1); + return apply_save_autostash_ref(r, refname, 1, + label_ours, label_theirs, label_base, + stash_msg); } static int checkout_onto(struct repository *r, struct replay_opts *opts, @@ -6409,7 +6438,6 @@ struct todo_add_branch_context { size_t items_nr; size_t items_alloc; struct strbuf *buf; - struct commit *commit; struct string_list refs_to_oids; }; @@ -6431,8 +6459,14 @@ static int add_decorations_to_list(const struct commit *commit, /* * If the branch is the current HEAD, then it will be * updated by the default rebase behavior. + * Exclude it from the list of refs to update, + * as well as any non-branch decorations. + * Non-branch decorations may be present if the pretty format + * includes "%d", which would have loaded all refs + * into the global decoration table. */ - if (head_ref && !strcmp(head_ref, decoration->name)) { + if ((head_ref && !strcmp(head_ref, decoration->name)) || + (decoration->type != DECORATION_REF_LOCAL)) { decoration = decoration->next; continue; } @@ -6498,7 +6532,6 @@ static int todo_list_add_update_ref_commands(struct todo_list *todo_list) ctx.items[ctx.items_nr++] = todo_list->items[i++]; if (item->commit) { - ctx.commit = item->commit; add_decorations_to_list(item->commit, &ctx); } }
diff --git a/sequencer.h b/sequencer.h index a6fa670..3164bd4 100644 --- a/sequencer.h +++ b/sequencer.h
@@ -229,12 +229,15 @@ void commit_post_rewrite(struct repository *r, const struct object_id *new_head); void create_autostash(struct repository *r, const char *path); -void create_autostash_ref(struct repository *r, const char *refname); +void create_autostash_ref(struct repository *r, const char *refname, + const char *message, bool silent); int save_autostash(const char *path); int save_autostash_ref(struct repository *r, const char *refname); int apply_autostash(const char *path); int apply_autostash_oid(const char *stash_oid); -int apply_autostash_ref(struct repository *r, const char *refname); +int apply_autostash_ref(struct repository *r, const char *refname, + const char *label_ours, const char *label_theirs, + const char *label_base, const char *stash_msg); #define SUMMARY_INITIAL_COMMIT (1 << 0) #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)
diff --git a/setup.c b/setup.c index 7ec4427..b465265 100644 --- a/setup.c +++ b/setup.c
@@ -26,9 +26,6 @@ #include "trace2.h" #include "worktree.h" -static int inside_git_dir = -1; -static int inside_work_tree = -1; -static int work_tree_config_is_bogus; enum allowed_bare_repo { ALLOWED_BARE_REPO_EXPLICIT = 0, ALLOWED_BARE_REPO_ALL, @@ -50,13 +47,13 @@ const char *tmp_original_cwd; * /dir/repolink/file (repolink points to /dir/repo) -> file * /dir/repo (exactly equal to work tree) -> (empty string) */ -static int abspath_part_inside_repo(char *path) +static int abspath_part_inside_repo(struct repository *repo, char *path) { size_t len; size_t wtlen; char *path0; int off; - const char *work_tree = precompose_string_if_needed(repo_get_work_tree(the_repository)); + const char *work_tree = precompose_string_if_needed(repo_get_work_tree(repo)); struct strbuf realpath = STRBUF_INIT; if (!work_tree) @@ -119,7 +116,8 @@ static int abspath_part_inside_repo(char *path) * ../../sub1/sub2/foo -> sub1/sub2/foo (but no remaining prefix) * `pwd`/../bar -> sub1/bar (no remaining prefix) */ -char *prefix_path_gently(const char *prefix, int len, +char *prefix_path_gently(struct repository *repo, + const char *prefix, int len, int *remaining_prefix, const char *path) { const char *orig = path; @@ -132,7 +130,7 @@ char *prefix_path_gently(const char *prefix, int len, free(sanitized); return NULL; } - if (abspath_part_inside_repo(sanitized)) { + if (abspath_part_inside_repo(repo, sanitized)) { free(sanitized); return NULL; } @@ -148,23 +146,23 @@ char *prefix_path_gently(const char *prefix, int len, return sanitized; } -char *prefix_path(const char *prefix, int len, const char *path) +char *prefix_path(struct repository *repo, const char *prefix, int len, const char *path) { - char *r = prefix_path_gently(prefix, len, NULL, path); + char *r = prefix_path_gently(repo, prefix, len, NULL, path); if (!r) { - const char *hint_path = repo_get_work_tree(the_repository); + const char *hint_path = repo_get_work_tree(repo); if (!hint_path) - hint_path = repo_get_git_dir(the_repository); + hint_path = repo_get_git_dir(repo); die(_("'%s' is outside repository at '%s'"), path, absolute_path(hint_path)); } return r; } -int path_inside_repo(const char *prefix, const char *path) +int path_inside_repo(struct repository *repo, const char *prefix, const char *path) { int len = prefix ? strlen(prefix) : 0; - char *r = prefix_path_gently(prefix, len, NULL, path); + char *r = prefix_path_gently(repo, prefix, len, NULL, path); if (r) { free(r); return 1; @@ -281,7 +279,8 @@ static int looks_like_pathspec(const char *arg) * diagnose_misspelt_rev == 0 for the next ones (because we already * saw a filename, there's not ambiguity anymore). */ -void verify_filename(const char *prefix, +void verify_filename(struct repository *repo, + const char *prefix, const char *arg, int diagnose_misspelt_rev) { @@ -289,7 +288,7 @@ void verify_filename(const char *prefix, die(_("option '%s' must come before non-option arguments"), arg); if (looks_like_pathspec(arg) || check_filename(prefix, arg)) return; - die_verify_filename(the_repository, prefix, arg, diagnose_misspelt_rev); + die_verify_filename(repo, prefix, arg, diagnose_misspelt_rev); } /* @@ -297,9 +296,9 @@ void verify_filename(const char *prefix, * and we parsed the arg as a refname. It should not be interpretable * as a filename. */ -void verify_non_filename(const char *prefix, const char *arg) +void verify_non_filename(struct repository *repo, const char *prefix, const char *arg) { - if (!is_inside_work_tree() || is_inside_git_dir()) + if (!is_inside_work_tree(repo) || is_inside_git_dir(repo)) return; if (*arg == '-') return; /* flag */ @@ -470,32 +469,38 @@ int is_nonbare_repository_dir(struct strbuf *path) return ret; } -int is_inside_git_dir(void) +int is_inside_git_dir(struct repository *repo) { - if (inside_git_dir < 0) - inside_git_dir = is_inside_dir(repo_get_git_dir(the_repository)); - return inside_git_dir; + struct strbuf buf = STRBUF_INIT; + int ret = is_inside_dir(strbuf_realpath(&buf, repo_get_git_dir(repo), 1)); + strbuf_release(&buf); + return ret; } -int is_inside_work_tree(void) +int is_inside_work_tree(struct repository *repo) { - if (inside_work_tree < 0) - inside_work_tree = is_inside_dir(repo_get_work_tree(the_repository)); - return inside_work_tree; + struct strbuf buf = STRBUF_INIT; + const char *worktree; + int ret; + + worktree = repo_get_work_tree(repo); + if (!worktree) + return 0; + + ret = is_inside_dir(strbuf_realpath(&buf, worktree, 1)); + + strbuf_release(&buf); + return ret; } -void setup_work_tree(void) +void setup_work_tree(struct repository *repo) { const char *work_tree; - static int initialized = 0; - if (initialized) - return; - - if (work_tree_config_is_bogus) + if (repo->worktree_config_is_bogus) die(_("unable to set up work tree using invalid config")); - work_tree = repo_get_work_tree(the_repository); + work_tree = repo_get_work_tree(repo); if (!work_tree || chdir_notify(work_tree)) die(_("this operation must be run in a work tree")); @@ -505,11 +510,9 @@ void setup_work_tree(void) */ if (getenv(GIT_WORK_TREE_ENVIRONMENT)) setenv(GIT_WORK_TREE_ENVIRONMENT, ".", 1); - - initialized = 1; } -static void setup_original_cwd(void) +static void setup_original_cwd(struct repository *repo) { struct strbuf tmp = STRBUF_INIT; const char *worktree = NULL; @@ -535,9 +538,9 @@ static void setup_original_cwd(void) /* Normalize the directory */ if (!strbuf_realpath(&tmp, tmp_original_cwd, 0)) { - trace2_data_string("setup", the_repository, + trace2_data_string("setup", repo, "realpath-path", tmp_original_cwd); - trace2_data_string("setup", the_repository, + trace2_data_string("setup", repo, "realpath-failure", strerror(errno)); free((char*)tmp_original_cwd); tmp_original_cwd = NULL; @@ -552,7 +555,7 @@ static void setup_original_cwd(void) * Get our worktree; we only protect the current working directory * if it's in the worktree. */ - worktree = repo_get_work_tree(the_repository); + worktree = repo_get_work_tree(repo); if (!worktree) goto no_prevention_needed; @@ -747,7 +750,9 @@ static int check_repo_format(const char *var, const char *value, return read_worktree_config(var, value, ctx, vdata); } -static int check_repository_format_gently(const char *gitdir, struct repository_format *candidate, int *nongit_ok) +static int check_repository_format_gently(const char *gitdir, + struct repository_format *candidate, + int *nongit_ok) { struct strbuf sb = STRBUF_INIT; struct strbuf err = STRBUF_INIT; @@ -759,7 +764,7 @@ static int check_repository_format_gently(const char *gitdir, struct repository_ strbuf_release(&sb); /* - * For historical use of check_repository_format() in git-init, + * For historical use of check_and_apply_repository_format() in git-init, * we treat a missing config as a silent "ok", even when nongit_ok * is unset. */ @@ -776,8 +781,6 @@ static int check_repository_format_gently(const char *gitdir, struct repository_ die("%s", err.buf); } - the_repository->repository_format_precious_objects = candidate->precious_objects; - string_list_clear(&candidate->unknown_extensions, 0); string_list_clear(&candidate->v1_only_extensions, 0); @@ -795,20 +798,17 @@ static int check_repository_format_gently(const char *gitdir, struct repository_ if (!has_common) { if (candidate->is_bare != -1) { is_bare_repository_cfg = candidate->is_bare; - if (is_bare_repository_cfg == 1) - inside_work_tree = -1; } if (candidate->work_tree) { free(git_work_tree_cfg); git_work_tree_cfg = xstrdup(candidate->work_tree); - inside_work_tree = -1; } } return 0; } -int upgrade_repository_format(int target_version) +int upgrade_repository_format(struct repository *repo, int target_version) { struct strbuf sb = STRBUF_INIT; struct strbuf err = STRBUF_INIT; @@ -816,7 +816,7 @@ int upgrade_repository_format(int target_version) struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; int ret; - repo_common_path_append(the_repository, &sb, "config"); + repo_common_path_append(repo, &sb, "config"); read_repository_format(&repo_fmt, sb.buf); strbuf_release(&sb); @@ -838,7 +838,7 @@ int upgrade_repository_format(int target_version) } strbuf_addf(&repo_version, "%d", target_version); - repo_config_set(the_repository, "core.repositoryformatversion", repo_version.buf); + repo_config_set(repo, "core.repositoryformatversion", repo_version.buf); ret = 1; @@ -1034,8 +1034,8 @@ const char *read_gitfile_gently(const char *path, int *return_error_code) return error_code ? NULL : path; } -static void setup_git_env_internal(const char *git_dir, - bool skip_initializing_odb) +static void setup_git_env_internal(struct repository *repo, + const char *git_dir) { char *git_replace_ref_base; const char *shallow_file; @@ -1044,15 +1044,12 @@ static void setup_git_env_internal(const char *git_dir, struct strvec to_free = STRVEC_INIT; args.commondir = getenv_safe(&to_free, GIT_COMMON_DIR_ENVIRONMENT); - args.object_dir = getenv_safe(&to_free, DB_ENVIRONMENT); args.graft_file = getenv_safe(&to_free, GRAFT_ENVIRONMENT); args.index_file = getenv_safe(&to_free, INDEX_ENVIRONMENT); - args.alternate_db = getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT); if (getenv(GIT_QUARANTINE_ENVIRONMENT)) args.disable_ref_updates = true; - args.skip_initializing_odb = skip_initializing_odb; - repo_set_gitdir(the_repository, git_dir, &args); + repo_set_gitdir(repo, git_dir, &args); strvec_clear(&to_free); if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT)) @@ -1064,38 +1061,34 @@ static void setup_git_env_internal(const char *git_dir, shallow_file = getenv(GIT_SHALLOW_FILE_ENVIRONMENT); if (shallow_file) - set_alternate_shallow_file(the_repository, shallow_file, 0); + set_alternate_shallow_file(repo, shallow_file, 0); if (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0)) fetch_if_missing = 0; } -void setup_git_env(const char *git_dir) -{ - setup_git_env_internal(git_dir, false); -} - -static void set_git_dir_1(const char *path, bool skip_initializing_odb) +static void set_git_dir_1(struct repository *repo, const char *path) { xsetenv(GIT_DIR_ENVIRONMENT, path, 1); - setup_git_env_internal(path, skip_initializing_odb); + setup_git_env_internal(repo, path); } static void update_relative_gitdir(const char *name UNUSED, const char *old_cwd, const char *new_cwd, - void *data UNUSED) + void *data) { + struct repository *repo = data; char *path = reparent_relative_path(old_cwd, new_cwd, - repo_get_git_dir(the_repository)); + repo_get_git_dir(repo)); trace_printf_key(&trace_setup_key, "setup: move $GIT_DIR to '%s'", path); - set_git_dir_1(path, true); + set_git_dir_1(repo, path); free(path); } -static void set_git_dir(const char *path, int make_realpath) +static void set_git_dir(struct repository *repo, const char *path, int make_realpath) { struct strbuf realpath = STRBUF_INIT; @@ -1104,14 +1097,15 @@ static void set_git_dir(const char *path, int make_realpath) path = realpath.buf; } - set_git_dir_1(path, false); + set_git_dir_1(repo, path); if (!is_absolute_path(path)) - chdir_notify_register(NULL, update_relative_gitdir, NULL); + chdir_notify_register(NULL, update_relative_gitdir, repo); strbuf_release(&realpath); } -static const char *setup_explicit_git_dir(const char *gitdirenv, +static const char *setup_explicit_git_dir(struct repository *repo, + const char *gitdirenv, struct strbuf *cwd, struct repository_format *repo_fmt, int *nongit_ok) @@ -1146,22 +1140,22 @@ static const char *setup_explicit_git_dir(const char *gitdirenv, /* #3, #7, #11, #15, #19, #23, #27, #31 (see t1510) */ if (work_tree_env) - set_git_work_tree(work_tree_env); + set_git_work_tree(repo, work_tree_env); else if (is_bare_repository_cfg > 0) { if (git_work_tree_cfg) { /* #22.2, #30 */ warning("core.bare and core.worktree do not make sense"); - work_tree_config_is_bogus = 1; + repo->worktree_config_is_bogus = true; } /* #18, #26 */ - set_git_dir(gitdirenv, 0); + set_git_dir(repo, gitdirenv, 0); free(gitfile); return NULL; } else if (git_work_tree_cfg) { /* #6, #14 */ if (is_absolute_path(git_work_tree_cfg)) - set_git_work_tree(git_work_tree_cfg); + set_git_work_tree(repo, git_work_tree_cfg); else { char *core_worktree; if (chdir(gitdirenv)) @@ -1171,32 +1165,32 @@ static const char *setup_explicit_git_dir(const char *gitdirenv, core_worktree = xgetcwd(); if (chdir(cwd->buf)) die_errno(_("cannot come back to cwd")); - set_git_work_tree(core_worktree); + set_git_work_tree(repo, core_worktree); free(core_worktree); } } else if (!git_env_bool(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, 1)) { /* #16d */ - set_git_dir(gitdirenv, 0); + set_git_dir(repo, gitdirenv, 0); free(gitfile); return NULL; } else /* #2, #10 */ - set_git_work_tree("."); + set_git_work_tree(repo, "."); /* set_git_work_tree() must have been called by now */ - worktree = repo_get_work_tree(the_repository); + worktree = repo_get_work_tree(repo); /* both repo_get_work_tree() and cwd are already normalized */ if (!strcmp(cwd->buf, worktree)) { /* cwd == worktree */ - set_git_dir(gitdirenv, 0); + set_git_dir(repo, gitdirenv, 0); free(gitfile); return NULL; } offset = dir_inside_of(cwd->buf, worktree); if (offset >= 0) { /* cwd inside worktree? */ - set_git_dir(gitdirenv, 1); + set_git_dir(repo, gitdirenv, 1); if (chdir(worktree)) die_errno(_("cannot chdir to '%s'"), worktree); strbuf_addch(cwd, '/'); @@ -1205,12 +1199,13 @@ static const char *setup_explicit_git_dir(const char *gitdirenv, } /* cwd outside worktree */ - set_git_dir(gitdirenv, 0); + set_git_dir(repo, gitdirenv, 0); free(gitfile); return NULL; } -static const char *setup_discovered_git_dir(const char *gitdir, +static const char *setup_discovered_git_dir(struct repository *repo, + const char *gitdir, struct strbuf *cwd, int offset, struct repository_format *repo_fmt, int *nongit_ok) @@ -1227,25 +1222,23 @@ static const char *setup_discovered_git_dir(const char *gitdir, gitdir = to_free = real_pathdup(gitdir, 1); if (chdir(cwd->buf)) die_errno(_("cannot come back to cwd")); - ret = setup_explicit_git_dir(gitdir, cwd, repo_fmt, nongit_ok); + ret = setup_explicit_git_dir(repo, gitdir, cwd, repo_fmt, nongit_ok); free(to_free); return ret; } /* #16.2, #17.2, #20.2, #21.2, #24, #25, #28, #29 (see t1510) */ if (is_bare_repository_cfg > 0) { - set_git_dir(gitdir, (offset != cwd->len)); + set_git_dir(repo, gitdir, (offset != cwd->len)); if (chdir(cwd->buf)) die_errno(_("cannot come back to cwd")); return NULL; } /* #0, #1, #5, #8, #9, #12, #13 */ - set_git_work_tree("."); + set_git_work_tree(repo, "."); if (strcmp(gitdir, DEFAULT_GIT_DIR_ENVIRONMENT)) - set_git_dir(gitdir, 0); - inside_git_dir = 0; - inside_work_tree = 1; + set_git_dir(repo, gitdir, 0); if (offset >= cwd->len) return NULL; @@ -1258,7 +1251,8 @@ static const char *setup_discovered_git_dir(const char *gitdir, } /* #16.1, #17.1, #20.1, #21.1, #22.1 (see t1510) */ -static const char *setup_bare_git_dir(struct strbuf *cwd, int offset, +static const char *setup_bare_git_dir(struct repository *repo, + struct strbuf *cwd, int offset, struct repository_format *repo_fmt, int *nongit_ok) { @@ -1276,20 +1270,18 @@ static const char *setup_bare_git_dir(struct strbuf *cwd, int offset, gitdir = offset == cwd->len ? "." : xmemdupz(cwd->buf, offset); if (chdir(cwd->buf)) die_errno(_("cannot come back to cwd")); - return setup_explicit_git_dir(gitdir, cwd, repo_fmt, nongit_ok); + return setup_explicit_git_dir(repo, gitdir, cwd, repo_fmt, nongit_ok); } - inside_git_dir = 1; - inside_work_tree = 0; if (offset != cwd->len) { if (chdir(cwd->buf)) die_errno(_("cannot come back to cwd")); root_len = offset_1st_component(cwd->buf); strbuf_setlen(cwd, offset > root_len ? offset : root_len); - set_git_dir(cwd->buf, 0); + set_git_dir(repo, cwd->buf, 0); } else - set_git_dir(".", 0); + set_git_dir(repo, ".", 0); return NULL; } @@ -1485,7 +1477,11 @@ static int allowed_bare_repo_cb(const char *key, const char *value, static enum allowed_bare_repo get_allowed_bare_repo(void) { +#ifdef WITH_BREAKING_CHANGES + enum allowed_bare_repo result = ALLOWED_BARE_REPO_EXPLICIT; +#else enum allowed_bare_repo result = ALLOWED_BARE_REPO_ALL; +#endif git_protected_config(allowed_bare_repo_cb, &result); return result; } @@ -1754,7 +1750,71 @@ enum discovery_result discover_git_directory_reason(struct strbuf *commondir, return result; } -const char *enter_repo(const char *path, unsigned flags) +int apply_repository_format(struct repository *repo, + const struct repository_format *format, + enum apply_repository_format_flags flags, + struct strbuf *err) +{ + char *object_directory = NULL, *alternate_object_directories = NULL; + + if (verify_repository_format(format, err) < 0) + return -1; + + if (flags & APPLY_REPOSITORY_FORMAT_HONOR_ENV) { + object_directory = xstrdup_or_null(getenv(DB_ENVIRONMENT)); + alternate_object_directories = xstrdup_or_null(getenv(ALTERNATE_DB_ENVIRONMENT)); + } + + repo_set_hash_algo(repo, format->hash_algo); + repo->objects = odb_new(repo, object_directory, + alternate_object_directories); + repo_set_compat_hash_algo(repo, format->compat_hash_algo); + repo_set_ref_storage_format(repo, + format->ref_storage_format, + format->ref_storage_payload); + repo->repository_format_worktree_config = + format->worktree_config; + repo->repository_format_submodule_path_cfg = + format->submodule_path_cfg; + repo->repository_format_relative_worktrees = + format->relative_worktrees; + repo->repository_format_partial_clone = + xstrdup_or_null(format->partial_clone); + repo->repository_format_precious_objects = + format->precious_objects; + + free(alternate_object_directories); + free(object_directory); + return 0; +} + +/* + * Check the repository format version in the path found in repo_get_git_dir(repo), + * and die if it is a version we don't understand. Generally one would + * set_git_dir() before calling this, and use it only for "are we in a valid + * repo?". + * + * If successful and fmt is not NULL, fill fmt with data. + */ +static void check_and_apply_repository_format(struct repository *repo, + struct repository_format *fmt, + enum apply_repository_format_flags flags) +{ + struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; + struct strbuf err = STRBUF_INIT; + + if (!fmt) + fmt = &repo_fmt; + + check_repository_format_gently(repo_get_git_dir(repo), fmt, NULL); + if (apply_repository_format(repo, fmt, flags, &err) < 0) + die("%s", err.buf); + startup_info->have_repository = 1; + + clear_repository_format(&repo_fmt); +} + +const char *enter_repo(struct repository *repo, const char *path, unsigned flags) { static struct strbuf validated_path = STRBUF_INIT; static struct strbuf used_path = STRBUF_INIT; @@ -1827,40 +1887,39 @@ const char *enter_repo(const char *path, unsigned flags) } if (is_git_directory(".")) { - set_git_dir(".", 0); - check_repository_format(NULL); + set_git_dir(repo, ".", 0); + check_and_apply_repository_format(repo, NULL, + APPLY_REPOSITORY_FORMAT_HONOR_ENV); return path; } return NULL; } -static int git_work_tree_initialized; - /* * Note. This works only before you used a work tree. This was added * primarily to support git-clone to work in a new repository it just * created, and is not meant to flip between different work trees. */ -void set_git_work_tree(const char *new_work_tree) +void set_git_work_tree(struct repository *repo, const char *new_work_tree) { - if (git_work_tree_initialized) { + if (repo->worktree_initialized) { struct strbuf realpath = STRBUF_INIT; strbuf_realpath(&realpath, new_work_tree, 1); new_work_tree = realpath.buf; - if (strcmp(new_work_tree, the_repository->worktree)) + if (strcmp(new_work_tree, repo->worktree)) die("internal error: work tree has already been set\n" "Current worktree: %s\nNew worktree: %s", - the_repository->worktree, new_work_tree); + repo->worktree, new_work_tree); strbuf_release(&realpath); return; } - git_work_tree_initialized = 1; - repo_set_worktree(the_repository, new_work_tree); + repo->worktree_initialized = true; + repo_set_worktree(repo, new_work_tree); } -const char *setup_git_directory_gently(int *nongit_ok) +const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) { static struct strbuf cwd = STRBUF_INIT; struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT, report = STRBUF_INIT; @@ -1875,7 +1934,7 @@ const char *setup_git_directory_gently(int *nongit_ok) * configuration (including the per-repo config file that we * ignored previously). */ - repo_config_clear(the_repository); + repo_config_clear(repo); /* * Let's assume that we are in a git repository. @@ -1891,18 +1950,18 @@ const char *setup_git_directory_gently(int *nongit_ok) switch (setup_git_directory_gently_1(&dir, &gitdir, &report, 1)) { case GIT_DIR_EXPLICIT: - prefix = setup_explicit_git_dir(gitdir.buf, &cwd, &repo_fmt, nongit_ok); + prefix = setup_explicit_git_dir(repo, gitdir.buf, &cwd, &repo_fmt, nongit_ok); break; case GIT_DIR_DISCOVERED: if (dir.len < cwd.len && chdir(dir.buf)) die(_("cannot change to '%s'"), dir.buf); - prefix = setup_discovered_git_dir(gitdir.buf, &cwd, dir.len, + prefix = setup_discovered_git_dir(repo, gitdir.buf, &cwd, dir.len, &repo_fmt, nongit_ok); break; case GIT_DIR_BARE: if (dir.len < cwd.len && chdir(dir.buf)) die(_("cannot change to '%s'"), dir.buf); - prefix = setup_bare_git_dir(&cwd, dir.len, &repo_fmt, nongit_ok); + prefix = setup_bare_git_dir(repo, &cwd, dir.len, &repo_fmt, nongit_ok); break; case GIT_DIR_HIT_CEILING: if (!nongit_ok) @@ -1982,31 +2041,22 @@ const char *setup_git_directory_gently(int *nongit_ok) startup_info->have_repository || /* GIT_DIR_EXPLICIT */ getenv(GIT_DIR_ENVIRONMENT)) { - if (!the_repository->gitdir) { + if (!repo->gitdir) { const char *gitdir = getenv(GIT_DIR_ENVIRONMENT); if (!gitdir) gitdir = DEFAULT_GIT_DIR_ENVIRONMENT; - setup_git_env(gitdir); + setup_git_env_internal(repo, gitdir); } + if (startup_info->have_repository) { - repo_set_hash_algo(the_repository, repo_fmt.hash_algo); - repo_set_compat_hash_algo(the_repository, - repo_fmt.compat_hash_algo); - repo_set_ref_storage_format(the_repository, - repo_fmt.ref_storage_format, - repo_fmt.ref_storage_payload); - the_repository->repository_format_worktree_config = - repo_fmt.worktree_config; - the_repository->repository_format_relative_worktrees = - repo_fmt.relative_worktrees; - the_repository->repository_format_submodule_path_cfg = - repo_fmt.submodule_path_cfg; - /* take ownership of repo_fmt.partial_clone */ - the_repository->repository_format_partial_clone = - repo_fmt.partial_clone; - repo_fmt.partial_clone = NULL; - the_repository->repository_format_precious_objects = - repo_fmt.precious_objects; + struct strbuf err = STRBUF_INIT; + + if (apply_repository_format(repo, &repo_fmt, + APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0) + die("%s", err.buf); + + clear_repository_format(&repo_fmt); + strbuf_release(&err); } } /* @@ -2038,13 +2088,13 @@ const char *setup_git_directory_gently(int *nongit_ok) format = ref_storage_format_by_name(backend); if (format == REF_STORAGE_FORMAT_UNKNOWN) die(_("unknown ref storage format: '%s'"), backend); - repo_set_ref_storage_format(the_repository, format, payload); + repo_set_ref_storage_format(repo, format, payload); free(backend); free(payload); } - setup_original_cwd(); + setup_original_cwd(repo); strbuf_release(&dir); strbuf_release(&gitdir); @@ -2105,38 +2155,15 @@ int git_config_perm(const char *var, const char *value) return -(i & 0666); } -void check_repository_format(struct repository_format *fmt) -{ - struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; - if (!fmt) - fmt = &repo_fmt; - check_repository_format_gently(repo_get_git_dir(the_repository), fmt, NULL); - startup_info->have_repository = 1; - repo_set_hash_algo(the_repository, fmt->hash_algo); - repo_set_compat_hash_algo(the_repository, fmt->compat_hash_algo); - repo_set_ref_storage_format(the_repository, - fmt->ref_storage_format, - fmt->ref_storage_payload); - the_repository->repository_format_worktree_config = - fmt->worktree_config; - the_repository->repository_format_submodule_path_cfg = - fmt->submodule_path_cfg; - the_repository->repository_format_relative_worktrees = - fmt->relative_worktrees; - the_repository->repository_format_partial_clone = - xstrdup_or_null(fmt->partial_clone); - clear_repository_format(&repo_fmt); -} - /* * Returns the "prefix", a path to the current working directory * relative to the work tree root, or NULL, if the current working * directory is not a strict subdirectory of the work tree root. The * prefix always ends with a '/' character. */ -const char *setup_git_directory(void) +const char *setup_git_directory(struct repository *repo) { - return setup_git_directory_gently(NULL); + return setup_git_directory_gently(repo, NULL); } const char *resolve_gitdir_gently(const char *suspect, int *return_error_code) @@ -2162,12 +2189,26 @@ int daemonize(void) errno = ENOSYS; return -1; #else - switch (fork()) { + pid_t parent_pid = getpid(); + pid_t child_pid = fork(); + + switch (child_pid) { case 0: + /* + * We're in the child process, so we take ownership of + * all tempfiles. + */ + reassign_tempfile_ownership(parent_pid, getpid()); break; case -1: die_errno(_("fork failed")); default: + /* + * We're in the parent process, so we drop ownership of + * all tempfiles to prevent us from removing them upon + * exit. + */ + reassign_tempfile_ownership(parent_pid, child_pid); exit(0); } if (setsid() == -1) @@ -2239,7 +2280,9 @@ const char *get_template_dir(const char *option_template) #define GIT_DEFAULT_HASH_ENVIRONMENT "GIT_DEFAULT_HASH" -static void copy_templates_1(struct strbuf *path, struct strbuf *template_path, +static void copy_templates_1(struct repository *repo, + struct strbuf *path, + struct strbuf *template_path, DIR *dir) { size_t path_baselen = path->len; @@ -2253,7 +2296,7 @@ static void copy_templates_1(struct strbuf *path, struct strbuf *template_path, * with the way the namespace under .git/ is organized, should * be really carefully chosen. */ - safe_create_dir(the_repository, path->buf, 1); + safe_create_dir(repo, path->buf, 1); while ((de = readdir(dir)) != NULL) { struct stat st_git, st_template; int exists = 0; @@ -2281,7 +2324,7 @@ static void copy_templates_1(struct strbuf *path, struct strbuf *template_path, die_errno(_("cannot opendir '%s'"), template_path->buf); strbuf_addch(path, '/'); strbuf_addch(template_path, '/'); - copy_templates_1(path, template_path, subdir); + copy_templates_1(repo, path, template_path, subdir); closedir(subdir); } else if (exists) @@ -2306,7 +2349,7 @@ static void copy_templates_1(struct strbuf *path, struct strbuf *template_path, } } -static void copy_templates(const char *option_template) +static void copy_templates(struct repository *repo, const char *option_template) { const char *template_dir = get_template_dir(option_template); struct strbuf path = STRBUF_INIT; @@ -2347,9 +2390,9 @@ static void copy_templates(const char *option_template) goto close_free_return; } - strbuf_addstr(&path, repo_get_common_dir(the_repository)); + strbuf_addstr(&path, repo_get_common_dir(repo)); strbuf_complete(&path, '/'); - copy_templates_1(&path, &template_path, dir); + copy_templates_1(repo, &path, &template_path, dir); close_free_return: closedir(dir); free_return: @@ -2373,7 +2416,8 @@ static int needs_work_tree_config(const char *git_dir, const char *work_tree) return 1; } -void initialize_repository_version(int hash_algo, +void initialize_repository_version(struct repository *repo, + int hash_algo, enum ref_storage_format ref_storage_format, int reinit) { @@ -2390,35 +2434,35 @@ void initialize_repository_version(int hash_algo, */ if (hash_algo != GIT_HASH_SHA1_LEGACY || ref_storage_format != REF_STORAGE_FORMAT_FILES || - the_repository->ref_storage_payload) + repo->ref_storage_payload) target_version = GIT_REPO_VERSION_READ; if (hash_algo != GIT_HASH_SHA1_LEGACY && hash_algo != GIT_HASH_UNKNOWN) - repo_config_set(the_repository, "extensions.objectformat", + repo_config_set(repo, "extensions.objectformat", hash_algos[hash_algo].name); else if (reinit) - repo_config_set_gently(the_repository, "extensions.objectformat", NULL); + repo_config_set_gently(repo, "extensions.objectformat", NULL); - if (the_repository->ref_storage_payload) { + if (repo->ref_storage_payload) { struct strbuf ref_uri = STRBUF_INIT; strbuf_addf(&ref_uri, "%s://%s", ref_storage_format_to_name(ref_storage_format), - the_repository->ref_storage_payload); - repo_config_set(the_repository, "extensions.refstorage", ref_uri.buf); + repo->ref_storage_payload); + repo_config_set(repo, "extensions.refstorage", ref_uri.buf); strbuf_release(&ref_uri); } else if (ref_storage_format != REF_STORAGE_FORMAT_FILES) { - repo_config_set(the_repository, "extensions.refstorage", + repo_config_set(repo, "extensions.refstorage", ref_storage_format_to_name(ref_storage_format)); } else if (reinit) { - repo_config_set_gently(the_repository, "extensions.refstorage", NULL); + repo_config_set_gently(repo, "extensions.refstorage", NULL); } if (reinit) { struct strbuf config = STRBUF_INIT; struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; - repo_common_path_append(the_repository, &config, "config"); + repo_common_path_append(repo, &config, "config"); read_repository_format(&repo_fmt, config.buf); if (repo_fmt.v1_only_extensions.nr) @@ -2428,40 +2472,41 @@ void initialize_repository_version(int hash_algo, clear_repository_format(&repo_fmt); } - repo_config_get_bool(the_repository, "init.defaultSubmodulePathConfig", + repo_config_get_bool(repo, "init.defaultSubmodulePathConfig", &default_submodule_path_config); if (default_submodule_path_config) { /* extensions.submodulepathconfig requires at least version 1 */ if (target_version == 0) target_version = 1; - repo_config_set(the_repository, "extensions.submodulepathconfig", "true"); + repo_config_set(repo, "extensions.submodulepathconfig", "true"); } strbuf_addf(&repo_version, "%d", target_version); - repo_config_set(the_repository, "core.repositoryformatversion", repo_version.buf); + repo_config_set(repo, "core.repositoryformatversion", repo_version.buf); strbuf_release(&repo_version); } -static int is_reinit(void) +static int is_reinit(struct repository *repo) { struct strbuf buf = STRBUF_INIT; char junk[2]; int ret; - repo_git_path_replace(the_repository, &buf, "HEAD"); + repo_git_path_replace(repo, &buf, "HEAD"); ret = !access(buf.buf, R_OK) || readlink(buf.buf, junk, sizeof(junk) - 1) != -1; strbuf_release(&buf); return ret; } -void create_reference_database(const char *initial_branch, int quiet) +void create_reference_database(struct repository *repo, + const char *initial_branch, int quiet) { struct strbuf err = STRBUF_INIT; char *to_free = NULL; - int reinit = is_reinit(); + int reinit = is_reinit(repo); - if (ref_store_create_on_disk(get_main_ref_store(the_repository), 0, &err)) + if (ref_store_create_on_disk(get_main_ref_store(repo), 0, &err)) die("failed to set up refs db: %s", err.buf); /* @@ -2473,14 +2518,14 @@ void create_reference_database(const char *initial_branch, int quiet) if (!initial_branch) initial_branch = to_free = - repo_default_branch_name(the_repository, quiet); + repo_default_branch_name(repo, quiet); ref = xstrfmt("refs/heads/%s", initial_branch); if (check_refname_format(ref, 0) < 0) die(_("invalid initial branch name: '%s'"), initial_branch); - if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", ref, NULL) < 0) + if (refs_update_symref(get_main_ref_store(repo), "HEAD", ref, NULL) < 0) exit(1); free(ref); } @@ -2493,7 +2538,8 @@ void create_reference_database(const char *initial_branch, int quiet) free(to_free); } -static int create_default_files(const char *template_path, +static int create_default_files(struct repository *repo, + const char *template_path, const char *original_git_dir, const struct repository_format *fmt, int init_shared_repository) @@ -2502,7 +2548,7 @@ static int create_default_files(const char *template_path, struct strbuf path = STRBUF_INIT; int reinit; int filemode; - const char *work_tree = repo_get_work_tree(the_repository); + const char *work_tree = repo_get_work_tree(repo); /* * First copy the templates -- we might have the default @@ -2513,19 +2559,19 @@ static int create_default_files(const char *template_path, * values (since we've just potentially changed what's available on * disk). */ - copy_templates(template_path); - repo_config_clear(the_repository); - repo_settings_reset_shared_repository(the_repository); - repo_config(the_repository, git_default_config, NULL); + copy_templates(repo, template_path); + repo_config_clear(repo); + repo_settings_reset_shared_repository(repo); + repo_config(repo, git_default_config, NULL); - reinit = is_reinit(); + reinit = is_reinit(repo); /* * We must make sure command-line options continue to override any * values we might have just re-read from the config. */ if (init_shared_repository != -1) - repo_settings_set_shared_repository(the_repository, + repo_settings_set_shared_repository(repo, init_shared_repository); is_bare_repository_cfg = !work_tree; @@ -2534,14 +2580,14 @@ static int create_default_files(const char *template_path, * We would have created the above under user's umask -- under * shared-repository settings, we would need to fix them up. */ - if (repo_settings_get_shared_repository(the_repository)) { - adjust_shared_perm(the_repository, repo_get_git_dir(the_repository)); + if (repo_settings_get_shared_repository(repo)) { + adjust_shared_perm(repo, repo_get_git_dir(repo)); } - initialize_repository_version(fmt->hash_algo, fmt->ref_storage_format, reinit); + initialize_repository_version(repo, fmt->hash_algo, fmt->ref_storage_format, reinit); /* Check filemode trustability */ - repo_git_path_replace(the_repository, &path, "config"); + repo_git_path_replace(repo, &path, "config"); filemode = TEST_FILEMODE; if (TEST_FILEMODE && !lstat(path.buf, &st1)) { struct stat st2; @@ -2552,22 +2598,22 @@ static int create_default_files(const char *template_path, if (filemode && !reinit && (st1.st_mode & S_IXUSR)) filemode = 0; } - repo_config_set(the_repository, "core.filemode", filemode ? "true" : "false"); + repo_config_set(repo, "core.filemode", filemode ? "true" : "false"); if (is_bare_repository()) - repo_config_set(the_repository, "core.bare", "true"); + repo_config_set(repo, "core.bare", "true"); else { - repo_config_set(the_repository, "core.bare", "false"); + repo_config_set(repo, "core.bare", "false"); /* allow template config file to override the default */ - if (repo_settings_get_log_all_ref_updates(the_repository) == LOG_REFS_UNSET) - repo_config_set(the_repository, "core.logallrefupdates", "true"); + if (repo_settings_get_log_all_ref_updates(repo) == LOG_REFS_UNSET) + repo_config_set(repo, "core.logallrefupdates", "true"); if (needs_work_tree_config(original_git_dir, work_tree)) - repo_config_set(the_repository, "core.worktree", work_tree); + repo_config_set(repo, "core.worktree", work_tree); } if (!reinit) { /* Check if symlink is supported in the work tree */ - repo_git_path_replace(the_repository, &path, "tXXXXXX"); + repo_git_path_replace(repo, &path, "tXXXXXX"); if (!close(xmkstemp(path.buf)) && !unlink(path.buf) && !symlink("testing", path.buf) && @@ -2575,12 +2621,12 @@ static int create_default_files(const char *template_path, S_ISLNK(st1.st_mode)) unlink(path.buf); /* good */ else - repo_config_set(the_repository, "core.symlinks", "false"); + repo_config_set(repo, "core.symlinks", "false"); /* Check if the filesystem is case-insensitive */ - repo_git_path_replace(the_repository, &path, "CoNfIg"); + repo_git_path_replace(repo, &path, "CoNfIg"); if (!access(path.buf, F_OK)) - repo_config_set(the_repository, "core.ignorecase", "true"); + repo_config_set(repo, "core.ignorecase", "true"); probe_utf8_pathname_composition(); } @@ -2588,23 +2634,23 @@ static int create_default_files(const char *template_path, return reinit; } -static void create_object_directory(void) +static void create_object_directory(struct repository *repo) { struct strbuf path = STRBUF_INIT; size_t baselen; - strbuf_addstr(&path, repo_get_object_directory(the_repository)); + strbuf_addstr(&path, repo_get_object_directory(repo)); baselen = path.len; - safe_create_dir(the_repository, path.buf, 1); + safe_create_dir(repo, path.buf, 1); strbuf_setlen(&path, baselen); strbuf_addstr(&path, "/pack"); - safe_create_dir(the_repository, path.buf, 1); + safe_create_dir(repo, path.buf, 1); strbuf_setlen(&path, baselen); strbuf_addstr(&path, "/info"); - safe_create_dir(the_repository, path.buf, 1); + safe_create_dir(repo, path.buf, 1); strbuf_release(&path); } @@ -2682,7 +2728,8 @@ static int read_default_format_config(const char *key, const char *value, return ret; } -static void repository_format_configure(struct repository_format *repo_fmt, +static void repository_format_configure(struct repository *repo, + struct repository_format *repo_fmt, int hash, enum ref_storage_format ref_format) { struct default_format_config cfg = { @@ -2719,7 +2766,7 @@ static void repository_format_configure(struct repository_format *repo_fmt, } else if (cfg.hash != GIT_HASH_UNKNOWN) { repo_fmt->hash_algo = cfg.hash; } - repo_set_hash_algo(the_repository, repo_fmt->hash_algo); + repo_set_hash_algo(repo, repo_fmt->hash_algo); env = getenv("GIT_DEFAULT_REF_FORMAT"); if (repo_fmt->version >= 0 && @@ -2758,11 +2805,12 @@ static void repository_format_configure(struct repository_format *repo_fmt, free(backend); } - repo_set_ref_storage_format(the_repository, repo_fmt->ref_storage_format, + repo_set_ref_storage_format(repo, repo_fmt->ref_storage_format, repo_fmt->ref_storage_payload); } -int init_db(const char *git_dir, const char *real_git_dir, +int init_db(struct repository *repo, + const char *git_dir, const char *real_git_dir, const char *template_dir, int hash, enum ref_storage_format ref_storage_format, const char *initial_branch, @@ -2782,13 +2830,13 @@ int init_db(const char *git_dir, const char *real_git_dir, if (!exist_ok && !stat(real_git_dir, &st)) die(_("%s already exists"), real_git_dir); - set_git_dir(real_git_dir, 1); - git_dir = repo_get_git_dir(the_repository); + set_git_dir(repo, real_git_dir, 1); + git_dir = repo_get_git_dir(repo); separate_git_dir(git_dir, original_git_dir); } else { - set_git_dir(git_dir, 1); - git_dir = repo_get_git_dir(the_repository); + set_git_dir(repo, git_dir, 1); + git_dir = repo_get_git_dir(repo); } startup_info->have_repository = 1; @@ -2798,27 +2846,28 @@ int init_db(const char *git_dir, const char *real_git_dir, * config file, so this will not fail. What we are catching * is an attempt to reinitialize new repository with an old tool. */ - check_repository_format(&repo_fmt); + check_and_apply_repository_format(repo, &repo_fmt, + APPLY_REPOSITORY_FORMAT_HONOR_ENV); - repository_format_configure(&repo_fmt, hash, ref_storage_format); + repository_format_configure(repo, &repo_fmt, hash, ref_storage_format); /* * Ensure `core.hidedotfiles` is processed. This must happen after we * have set up the repository format such that we can evaluate * includeIf conditions correctly in the case of re-initialization. */ - repo_config(the_repository, git_default_core_config, NULL); + repo_config(repo, git_default_core_config, NULL); - safe_create_dir(the_repository, git_dir, 0); + safe_create_dir(repo, git_dir, 0); - reinit = create_default_files(template_dir, original_git_dir, + reinit = create_default_files(repo, template_dir, original_git_dir, &repo_fmt, init_shared_repository); if (!(flags & INIT_DB_SKIP_REFDB)) - create_reference_database(initial_branch, flags & INIT_DB_QUIET); - create_object_directory(); + create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET); + create_object_directory(repo); - if (repo_settings_get_shared_repository(the_repository)) { + if (repo_settings_get_shared_repository(repo)) { char buf[10]; /* We do not spell "group" and such, so that * the configuration can be read by older version @@ -2826,29 +2875,29 @@ int init_db(const char *git_dir, const char *real_git_dir, * and compatibility values for PERM_GROUP and * PERM_EVERYBODY. */ - if (repo_settings_get_shared_repository(the_repository) < 0) + if (repo_settings_get_shared_repository(repo) < 0) /* force to the mode value */ - xsnprintf(buf, sizeof(buf), "0%o", -repo_settings_get_shared_repository(the_repository)); - else if (repo_settings_get_shared_repository(the_repository) == PERM_GROUP) + xsnprintf(buf, sizeof(buf), "0%o", -repo_settings_get_shared_repository(repo)); + else if (repo_settings_get_shared_repository(repo) == PERM_GROUP) xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_GROUP); - else if (repo_settings_get_shared_repository(the_repository) == PERM_EVERYBODY) + else if (repo_settings_get_shared_repository(repo) == PERM_EVERYBODY) xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_EVERYBODY); else BUG("invalid value for shared_repository"); - repo_config_set(the_repository, "core.sharedrepository", buf); - repo_config_set(the_repository, "receive.denyNonFastforwards", "true"); + repo_config_set(repo, "core.sharedrepository", buf); + repo_config_set(repo, "receive.denyNonFastforwards", "true"); } if (!(flags & INIT_DB_QUIET)) { int len = strlen(git_dir); if (reinit) - printf(repo_settings_get_shared_repository(the_repository) + printf(repo_settings_get_shared_repository(repo) ? _("Reinitialized existing shared Git repository in %s%s\n") : _("Reinitialized existing Git repository in %s%s\n"), git_dir, len && git_dir[len-1] != '/' ? "/" : ""); else - printf(repo_settings_get_shared_repository(the_repository) + printf(repo_settings_get_shared_repository(repo) ? _("Initialized empty shared Git repository in %s%s\n") : _("Initialized empty Git repository in %s%s\n"), git_dir, len && git_dir[len-1] != '/' ? "/" : "");
diff --git a/setup.h b/setup.h index 80bc6e5..705d1d6 100644 --- a/setup.h +++ b/setup.h
@@ -4,8 +4,8 @@ #include "refs.h" #include "string-list.h" -int is_inside_git_dir(void); -int is_inside_work_tree(void); +int is_inside_git_dir(struct repository *repo); +int is_inside_work_tree(struct repository *repo); int get_common_dir_noenv(struct strbuf *sb, const char *gitdir); int get_common_dir(struct strbuf *sb, const char *gitdir); @@ -56,7 +56,7 @@ const char *resolve_gitdir_gently(const char *suspect, int *return_error_code); void die_upon_dubious_ownership(const char *gitfile, const char *worktree, const char *gitdir); -void setup_work_tree(void); +void setup_work_tree(struct repository *repo); /* * discover_git_directory_reason() is similar to discover_git_directory(), @@ -96,7 +96,7 @@ static inline int discover_git_directory(struct strbuf *commondir, return 0; } -void set_git_work_tree(const char *tree); +void set_git_work_tree(struct repository *repo, const char *tree); /* Flags that can be passed to `enter_repo()`. */ enum { @@ -134,21 +134,37 @@ enum { * links. User relative paths are also returned as they are given, * except DWIM suffixing. */ -const char *enter_repo(const char *path, unsigned flags); +const char *enter_repo(struct repository *repo, const char *path, unsigned flags); -const char *setup_git_directory_gently(int *); -const char *setup_git_directory(void); -char *prefix_path(const char *prefix, int len, const char *path); -char *prefix_path_gently(const char *prefix, int len, int *remaining, const char *path); +const char *setup_git_directory_gently(struct repository *repo, int *); +const char *setup_git_directory(struct repository *repo); +char *prefix_path(struct repository *repo, const char *prefix, int len, const char *path); +char *prefix_path_gently(struct repository *repo, const char *prefix, int len, int *remaining, const char *path); int check_filename(const char *prefix, const char *name); -void verify_filename(const char *prefix, +void verify_filename(struct repository *repo, + const char *prefix, const char *name, int diagnose_misspelt_rev); -void verify_non_filename(const char *prefix, const char *name); -int path_inside_repo(const char *prefix, const char *path); +void verify_non_filename(struct repository *repo, const char *prefix, const char *name); +int path_inside_repo(struct repository *repo, const char *prefix, const char *path); void sanitize_stdfds(void); + +/* + * Daemonize the current process by forking and then exiting the parent + * process. Returns 0 when successful, in which case the parent process will + * have exited and it's the child process that continues to run the code. + * Otherwise, a negative error code is returned and the parent process will + * continue execution. + * + * Note that this function will also perform the following changes: + * + * - Standard file descriptors in the child process are closed. + * - The child process is made a session leader via setsid(3p). + * - All tempfiles owned by the parent process are reassigned to the + * daemonized child process. + */ int daemonize(void); /* @@ -220,15 +236,25 @@ void clear_repository_format(struct repository_format *format); int verify_repository_format(const struct repository_format *format, struct strbuf *err); +enum apply_repository_format_flags { + /* + * Honor environment variables when applying the repository format to + * the repository. For now, this only covers environment variables that + * relate to the object database. + */ + APPLY_REPOSITORY_FORMAT_HONOR_ENV = (1 << 0), +}; + /* - * Check the repository format version in the path found in repo_get_git_dir(the_repository), - * and die if it is a version we don't understand. Generally one would - * set_git_dir() before calling this, and use it only for "are we in a valid - * repo?". - * - * If successful and fmt is not NULL, fill fmt with data. + * Apply the given repository format to the repo. This initializes extensions + * and basic data structures required for normal operation. Returns 0 on + * success, a negative error code when the format is not valid as determined by + * `verify_repository_format()`. */ -void check_repository_format(struct repository_format *fmt); +int apply_repository_format(struct repository *repo, + const struct repository_format *format, + enum apply_repository_format_flags flags, + struct strbuf *err); const char *get_template_dir(const char *option_template); @@ -236,15 +262,17 @@ const char *get_template_dir(const char *option_template); #define INIT_DB_EXIST_OK (1 << 1) #define INIT_DB_SKIP_REFDB (1 << 2) -int init_db(const char *git_dir, const char *real_git_dir, +int init_db(struct repository *repo, + const char *git_dir, const char *real_git_dir, const char *template_dir, int hash_algo, enum ref_storage_format ref_storage_format, const char *initial_branch, int init_shared_repository, unsigned int flags); -void initialize_repository_version(int hash_algo, +void initialize_repository_version(struct repository *repo, + int hash_algo, enum ref_storage_format ref_storage_format, int reinit); -void create_reference_database(const char *initial_branch, int quiet); +void create_reference_database(struct repository *repo, const char *initial_branch, int quiet); /* * NOTE NOTE NOTE!!
diff --git a/shallow.c b/shallow.c index a8ad92e..07cae44 100644 --- a/shallow.c +++ b/shallow.c
@@ -245,7 +245,11 @@ struct commit_list *get_shallow_commits(struct object_array *heads, int depth, int shallow_flag, int not_shallow_flag) { if (shallows && deepen_relative) { - depth += get_shallows_depth(heads, shallows); + int cur_shallow_depth = get_shallows_depth(heads, shallows); + if (cur_shallow_depth) + depth += cur_shallow_depth; + else + return NULL; } return get_shallows_or_depth(heads, NULL, NULL, depth, shallow_flag, not_shallow_flag); @@ -395,7 +399,7 @@ static int write_shallow_commits_1(struct strbuf *out, int use_pack_protocol, if (!extra) return data.count; for (size_t i = 0; i < extra->nr; i++) { - strbuf_addstr(out, oid_to_hex(extra->oid + i)); + strbuf_add_oid_hex(out, extra->oid + i); strbuf_addch(out, '\n'); data.count++; }
diff --git a/sideband.c b/sideband.c index ea7c252..1523a53 100644 --- a/sideband.c +++ b/sideband.c
@@ -10,6 +10,7 @@ #include "help.h" #include "pkt-line.h" #include "write-or-die.h" +#include "urlmatch.h" struct keyword_entry { /* @@ -26,6 +27,85 @@ static struct keyword_entry keywords[] = { { "error", GIT_COLOR_BOLD_RED }, }; +static enum { + ALLOW_CONTROL_SEQUENCES_UNSET = -1, + ALLOW_NO_CONTROL_CHARACTERS = 0, + ALLOW_ANSI_COLOR_SEQUENCES = 1<<0, + ALLOW_ANSI_CURSOR_MOVEMENTS = 1<<1, + ALLOW_ANSI_ERASE = 1<<2, + ALLOW_ALL_CONTROL_CHARACTERS = 1<<3, + ALLOW_DEFAULT_ANSI_SEQUENCES = ALLOW_ANSI_COLOR_SEQUENCES +} allow_control_characters = ALLOW_CONTROL_SEQUENCES_UNSET; + +static inline int skip_prefix_in_csv(const char *value, const char *prefix, + const char **out) +{ + if (!skip_prefix(value, prefix, &value) || + (*value && *value != ',')) + return 0; + *out = value + !!*value; + return 1; +} + +int sideband_allow_control_characters_config(const char *var, const char *value) +{ + switch (git_parse_maybe_bool(value)) { + case 0: + allow_control_characters = ALLOW_NO_CONTROL_CHARACTERS; + return 0; + case 1: + allow_control_characters = ALLOW_ALL_CONTROL_CHARACTERS; + return 0; + default: + break; + } + + allow_control_characters = ALLOW_NO_CONTROL_CHARACTERS; + while (*value) { + if (skip_prefix_in_csv(value, "color", &value)) + allow_control_characters |= ALLOW_ANSI_COLOR_SEQUENCES; + else if (skip_prefix_in_csv(value, "cursor", &value)) + allow_control_characters |= ALLOW_ANSI_CURSOR_MOVEMENTS; + else if (skip_prefix_in_csv(value, "erase", &value)) + allow_control_characters |= ALLOW_ANSI_ERASE; + else if (skip_prefix_in_csv(value, "true", &value)) + allow_control_characters = ALLOW_ALL_CONTROL_CHARACTERS; + else if (skip_prefix_in_csv(value, "false", &value)) + allow_control_characters = ALLOW_NO_CONTROL_CHARACTERS; + else + warning(_("unrecognized value for '%s': '%s'"), var, value); + } + return 0; +} + +static int sideband_config_callback(const char *var, const char *value, + const struct config_context *ctx UNUSED, + void *data UNUSED) +{ + if (!strcmp(var, "sideband.allowcontrolcharacters")) + return sideband_allow_control_characters_config(var, value); + + return 0; +} + +void sideband_apply_url_config(const char *url) +{ + struct urlmatch_config config = URLMATCH_CONFIG_INIT; + char *normalized_url; + + if (!url) + BUG("must not call sideband_apply_url_config(NULL)"); + + config.section = "sideband"; + config.collect_fn = sideband_config_callback; + + normalized_url = url_normalize(url, &config.url); + repo_config(the_repository, urlmatch_config_entry, &config); + free(normalized_url); + string_list_clear(&config.vars, 1); + urlmatch_config_release(&config); +} + /* Returns a color setting (GIT_COLOR_NEVER, etc). */ static enum git_colorbool use_sideband_colors(void) { @@ -39,6 +119,14 @@ static enum git_colorbool use_sideband_colors(void) if (use_sideband_colors_cached != GIT_COLOR_UNKNOWN) return use_sideband_colors_cached; + if (allow_control_characters == ALLOW_CONTROL_SEQUENCES_UNSET) { + if (!repo_config_get_value(the_repository, "sideband.allowcontrolcharacters", &value)) + sideband_allow_control_characters_config("sideband.allowcontrolcharacters", value); + + if (allow_control_characters == ALLOW_CONTROL_SEQUENCES_UNSET) + allow_control_characters = ALLOW_DEFAULT_ANSI_SEQUENCES; + } + if (!repo_config_get_string_tmp(the_repository, key, &value)) use_sideband_colors_cached = git_config_colorbool(key, value); else if (!repo_config_get_string_tmp(the_repository, "color.ui", &value)) @@ -66,6 +154,93 @@ void list_config_color_sideband_slots(struct string_list *list, const char *pref list_config_item(list, prefix, keywords[i].keyword); } +static int handle_ansi_sequence(struct strbuf *dest, const char *src, int n) +{ + int i; + + /* + * Valid ANSI color sequences are of the form + * + * ESC [ [<n> [; <n>]*] m + * + * These are part of the Select Graphic Rendition sequences which + * contain more than just color sequences, for more details see + * https://en.wikipedia.org/wiki/ANSI_escape_code#SGR. + * + * The cursor movement sequences are: + * + * ESC [ n A - Cursor up n lines (CUU) + * ESC [ n B - Cursor down n lines (CUD) + * ESC [ n C - Cursor forward n columns (CUF) + * ESC [ n D - Cursor back n columns (CUB) + * ESC [ n E - Cursor next line, beginning (CNL) + * ESC [ n F - Cursor previous line, beginning (CPL) + * ESC [ n G - Cursor to column n (CHA) + * ESC [ n ; m H - Cursor position (row n, col m) (CUP) + * ESC [ n ; m f - Same as H (HVP) + * + * The sequences to erase characters are: + * + * + * ESC [ 0 J - Clear from cursor to end of screen (ED) + * ESC [ 1 J - Clear from cursor to beginning of screen (ED) + * ESC [ 2 J - Clear entire screen (ED) + * ESC [ 3 J - Clear entire screen + scrollback (ED) - xterm extension + * ESC [ 0 K - Clear from cursor to end of line (EL) + * ESC [ 1 K - Clear from cursor to beginning of line (EL) + * ESC [ 2 K - Clear entire line (EL) + * ESC [ n M - Delete n lines (DL) + * ESC [ n P - Delete n characters (DCH) + * ESC [ n X - Erase n characters (ECH) + * + * For a comprehensive list of common ANSI Escape sequences, see + * https://www.xfree86.org/current/ctlseqs.html + */ + + if (n < 3 || src[0] != '\x1b' || src[1] != '[') + return 0; + + for (i = 2; i < n; i++) { + if (((allow_control_characters & ALLOW_ANSI_COLOR_SEQUENCES) && + src[i] == 'm') || + ((allow_control_characters & ALLOW_ANSI_CURSOR_MOVEMENTS) && + strchr("ABCDEFGHf", src[i])) || + ((allow_control_characters & ALLOW_ANSI_ERASE) && + strchr("JKMPX", src[i]))) { + strbuf_add(dest, src, i + 1); + return i; + } + if (!isdigit(src[i]) && src[i] != ';') + break; + } + + return 0; +} + +static void strbuf_add_sanitized(struct strbuf *dest, const char *src, int n) +{ + int i; + + if ((allow_control_characters & ALLOW_ALL_CONTROL_CHARACTERS)) { + strbuf_add(dest, src, n); + return; + } + + strbuf_grow(dest, n); + for (; n && *src; src++, n--) { + if (!iscntrl(*src) || *src == '\t' || *src == '\n') { + strbuf_addch(dest, *src); + } else if (allow_control_characters != ALLOW_NO_CONTROL_CHARACTERS && + (i = handle_ansi_sequence(dest, src, n))) { + src += i; + n -= i; + } else { + strbuf_addch(dest, '^'); + strbuf_addch(dest, *src == 0x7f ? '?' : 0x40 + *src); + } + } +} + /* * Optionally highlight one keyword in remote output if it appears at the start * of the line. This should be called for a single line only, which is @@ -81,7 +256,7 @@ static void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n) int i; if (!want_color_stderr(use_sideband_colors())) { - strbuf_add(dest, src, n); + strbuf_add_sanitized(dest, src, n); return; } @@ -114,13 +289,13 @@ static void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n) } } - strbuf_add(dest, src, n); + strbuf_add_sanitized(dest, src, n); } #define DISPLAY_PREFIX "remote: " -#define ANSI_SUFFIX "\033[K" +#define ANSI_PREFIX "\033[K" #define DUMB_SUFFIX " " int demultiplex_sideband(const char *me, int status, @@ -129,15 +304,18 @@ int demultiplex_sideband(const char *me, int status, struct strbuf *scratch, enum sideband_type *sideband_type) { - static const char *suffix; + static const char *prefix, *suffix; const char *b, *brk; int band; if (!suffix) { - if (isatty(2) && !is_terminal_dumb()) - suffix = ANSI_SUFFIX; - else + if (isatty(2) && !is_terminal_dumb()) { + prefix = ANSI_PREFIX DISPLAY_PREFIX; + suffix = ""; + } else { + prefix = DISPLAY_PREFIX; suffix = DUMB_SUFFIX; + } } if (status == PACKET_READ_EOF) { @@ -171,8 +349,7 @@ int demultiplex_sideband(const char *me, int status, case 3: if (die_on_error) die(_("remote error: %s"), buf + 1); - strbuf_addf(scratch, "%s%s", scratch->len ? "\n" : "", - DISPLAY_PREFIX); + strbuf_addf(scratch, "%s%s", scratch->len ? "\n" : "", prefix); maybe_colorize_sideband(scratch, buf + 1, len); *sideband_type = SIDEBAND_REMOTE_ERROR; @@ -203,7 +380,7 @@ int demultiplex_sideband(const char *me, int status, strbuf_addstr(scratch, suffix); if (!scratch->len) - strbuf_addstr(scratch, DISPLAY_PREFIX); + strbuf_addstr(scratch, prefix); /* * A use case that we should not add clear-to-eol suffix @@ -229,8 +406,8 @@ int demultiplex_sideband(const char *me, int status, } if (*b) { - strbuf_addstr(scratch, scratch->len ? - "" : DISPLAY_PREFIX); + if (!scratch->len) + strbuf_addstr(scratch, prefix); maybe_colorize_sideband(scratch, b, strlen(b)); } return 0;
diff --git a/sideband.h b/sideband.h index 5a25331..d15fa40 100644 --- a/sideband.h +++ b/sideband.h
@@ -30,4 +30,18 @@ int demultiplex_sideband(const char *me, int status, void send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max); +/* + * Apply sideband configuration for the given URL. This should be called + * when a transport is created to allow URL-specific configuration of + * sideband behavior (e.g., sideband.<url>.allowControlCharacters). + */ +void sideband_apply_url_config(const char *url); + +/* + * Parse and set the sideband allow control characters configuration. + * The var parameter should be the key name (without section prefix). + * Returns 0 if the variable was recognized and handled, non-zero otherwise. + */ +int sideband_allow_control_characters_config(const char *var, const char *value); + #endif
diff --git a/sparse-index.c b/sparse-index.c index 13629c0..1ed769b 100644 --- a/sparse-index.c +++ b/sparse-index.c
@@ -154,7 +154,7 @@ int is_sparse_index_allowed(struct index_state *istate, int flags) { struct repo_config_values *cfg = repo_config_values(the_repository); - if (!cfg->apply_sparse_checkout || !core_sparse_checkout_cone) + if (!cfg->apply_sparse_checkout || !cfg->core_sparse_checkout_cone) return 0; if (!(flags & SPARSE_INDEX_MEMORY_ONLY)) { @@ -675,7 +675,7 @@ void clear_skip_worktree_from_present_files(struct index_state *istate) struct repo_config_values *cfg = repo_config_values(the_repository); if (!cfg->apply_sparse_checkout || - sparse_expect_files_outside_of_patterns) + cfg->sparse_expect_files_outside_of_patterns) return; if (clear_skip_worktree_from_present_files_sparse(istate)) {
diff --git a/src/meson.build b/src/meson.build index 4573995..41a4b23 100644 --- a/src/meson.build +++ b/src/meson.build
@@ -29,6 +29,7 @@ ) libgit_dependencies += declare_dependency(link_with: libgit_rs) +cargo = find_program('cargo', dirs: program_path, native: true, required: get_option('rust')) if get_option('tests') test('rust', cargo, args: [
diff --git a/statinfo.c b/statinfo.c index 30a164b..5e00af1 100644 --- a/statinfo.c +++ b/statinfo.c
@@ -3,6 +3,7 @@ #include "git-compat-util.h" #include "environment.h" #include "statinfo.h" +#include "repository.h" /* * Munge st_size into an unsigned int. @@ -63,22 +64,23 @@ void fake_lstat_data(const struct stat_data *sd, struct stat *st) int match_stat_data(const struct stat_data *sd, struct stat *st) { int changed = 0; + struct repo_config_values *cfg = repo_config_values(the_repository); if (sd->sd_mtime.sec != (unsigned int)st->st_mtime) changed |= MTIME_CHANGED; - if (trust_ctime && check_stat && + if (cfg->trust_ctime && cfg->check_stat && sd->sd_ctime.sec != (unsigned int)st->st_ctime) changed |= CTIME_CHANGED; #ifdef USE_NSEC - if (check_stat && sd->sd_mtime.nsec != ST_MTIME_NSEC(*st)) + if (cfg->check_stat && sd->sd_mtime.nsec != ST_MTIME_NSEC(*st)) changed |= MTIME_CHANGED; - if (trust_ctime && check_stat && + if (cfg->trust_ctime && cfg->check_stat && sd->sd_ctime.nsec != ST_CTIME_NSEC(*st)) changed |= CTIME_CHANGED; #endif - if (check_stat) { + if (cfg->check_stat) { if (sd->sd_uid != (unsigned int) st->st_uid || sd->sd_gid != (unsigned int) st->st_gid) changed |= OWNER_CHANGED; @@ -92,7 +94,7 @@ int match_stat_data(const struct stat_data *sd, struct stat *st) * clients will have different views of what "device" * the filesystem is on */ - if (check_stat && sd->sd_dev != (unsigned int) st->st_dev) + if (cfg->check_stat && sd->sd_dev != (unsigned int) st->st_dev) changed |= INODE_CHANGED; #endif
diff --git a/strbuf.c b/strbuf.c index 3e04add..764b629 100644 --- a/strbuf.c +++ b/strbuf.c
@@ -106,12 +106,10 @@ void strbuf_attach(struct strbuf *sb, void *buf, size_t len, size_t alloc) void strbuf_grow(struct strbuf *sb, size_t extra) { int new_buf = !sb->alloc; - if (unsigned_add_overflows(extra, 1) || - unsigned_add_overflows(sb->len, extra + 1)) - die("you want to use way too much memory"); + size_t new_len = st_add3(sb->len, extra, 1); if (new_buf) sb->buf = NULL; - ALLOC_GROW(sb->buf, sb->len + extra + 1, sb->alloc); + ALLOC_GROW(sb->buf, new_len, sb->alloc); if (new_buf) sb->buf[0] = '\0'; } @@ -361,6 +359,18 @@ void strbuf_addf(struct strbuf *sb, const char *fmt, ...) va_end(ap); } +void strbuf_add_uint(struct strbuf *sb, uintmax_t value) +{ + char buf[DIV_ROUND_UP(bitsizeof(value) * 10, 33)]; + char *end = buf + sizeof(buf); + char *p = end; + + do + *--p = "0123456789"[value % 10]; + while (value /= 10); + strbuf_add(sb, p, end - p); +} + static void add_lines(struct strbuf *out, const char *prefix, const char *buf, size_t size,
diff --git a/strbuf.h b/strbuf.h index 06e284f..1089ae6 100644 --- a/strbuf.h +++ b/strbuf.h
@@ -410,6 +410,12 @@ void strbuf_humanise_rate(struct strbuf *buf, off_t bytes); __attribute__((format (printf,2,3))) void strbuf_addf(struct strbuf *sb, const char *fmt, ...); + +/** + * Add an unsigned decimal number. + */ +void strbuf_add_uint(struct strbuf *sb, uintmax_t value); + /** * Add a formatted string prepended by a comment character and a * blank to the buffer.
diff --git a/sub-process.c b/sub-process.c index 83bf0a0..2d5c965 100644 --- a/sub-process.c +++ b/sub-process.c
@@ -132,17 +132,24 @@ static int handshake_version(struct child_process *process, if (packet_flush_gently(process->in)) return error("Could not write flush packet"); - if (!(line = packet_read_line(process->out, NULL)) || - !skip_prefix(line, welcome_prefix, &p) || + if (packet_read_line_gently(process->out, NULL, &line) < 0) + return error("could not read greeting from subprocess '%s'", + process->args.v[0]); + if (!line || !skip_prefix(line, welcome_prefix, &p) || strcmp(p, "-server")) return error("Unexpected line '%s', expected %s-server", line ? line : "<flush packet>", welcome_prefix); - if (!(line = packet_read_line(process->out, NULL)) || - !skip_prefix(line, "version=", &p) || + if (packet_read_line_gently(process->out, NULL, &line) < 0) + return error("could not read version from subprocess '%s'", + process->args.v[0]); + if (!line || !skip_prefix(line, "version=", &p) || strtol_i(p, 10, chosen_version)) return error("Unexpected line '%s', expected version", line ? line : "<flush packet>"); - if ((line = packet_read_line(process->out, NULL))) + if (packet_read_line_gently(process->out, NULL, &line) < 0) + return error("could not read version flush from subprocess '%s'", + process->args.v[0]); + if (line) return error("Unexpected line '%s', expected flush", line); /* Check to make sure that the version received is supported */ @@ -171,8 +178,15 @@ static int handshake_capabilities(struct child_process *process, if (packet_flush_gently(process->in)) return error("Could not write flush packet"); - while ((line = packet_read_line(process->out, NULL))) { + for (;;) { const char *p; + int len = packet_read_line_gently(process->out, NULL, &line); + + if (len < 0) + return error("could not read capabilities from subprocess '%s'", + process->args.v[0]); + if (!line) + break; if (!skip_prefix(line, "capability=", &p)) continue;
diff --git a/submodule-config.c b/submodule-config.c index 72a46b7..a81897b 100644 --- a/submodule-config.c +++ b/submodule-config.c
@@ -1038,5 +1038,5 @@ static int gitmodules_update_clone_config(const char *var, const char *value, void update_clone_config_from_gitmodules(int *max_jobs) { - config_from_gitmodules(gitmodules_update_clone_config, the_repository, &max_jobs); + config_from_gitmodules(gitmodules_update_clone_config, the_repository, max_jobs); }
diff --git a/submodule.c b/submodule.c index b1a0363..fd91201 100644 --- a/submodule.c +++ b/submodule.c
@@ -898,12 +898,13 @@ static void collect_changed_submodules(struct repository *r, struct setup_revision_opt s_r_opt = { .assume_dashdash = 1, }; + struct repo_config_values *cfg = repo_config_values(the_repository); - save_warning = warn_on_object_refname_ambiguity; - warn_on_object_refname_ambiguity = 0; + save_warning = cfg->warn_on_object_refname_ambiguity; + cfg->warn_on_object_refname_ambiguity = 0; repo_init_revisions(r, &rev, NULL); setup_revisions_from_strvec(argv, &rev, &s_r_opt); - warn_on_object_refname_ambiguity = save_warning; + cfg->warn_on_object_refname_ambiguity = save_warning; if (prepare_revision_walk(&rev)) die(_("revision walk setup failed")); @@ -2620,7 +2621,7 @@ int get_superproject_working_tree(struct strbuf *buf) int code; ssize_t len; - if (!is_inside_work_tree()) + if (!is_inside_work_tree(the_repository)) /* * FIXME: * We might have a superproject, but it is harder
diff --git a/t/README b/t/README index adbbd9a..085921b 100644 --- a/t/README +++ b/t/README
@@ -972,7 +972,7 @@ - test_lazy_prereq <prereq> <script> Declare the way to determine if a test prerequisite <prereq> is - satisified or not, but delay the actual determination until the + satisfied or not, but delay the actual determination until the prerequisite is actually used by "test_have_prereq" or the three-arg form of the test_expect_* functions. For example, this is how the SYMLINKS prerequisite is declared to see if the platform
diff --git a/t/chainlint.pl b/t/chainlint.pl index f0598e3..2d07a99 100755 --- a/t/chainlint.pl +++ b/t/chainlint.pl
@@ -35,7 +35,7 @@ # # In other languages, `1+2` would typically be scanned as three tokens # (`1`, `+`, and `2`), but in shell it is a single token. However, the similar -# `1 + 2`, which embeds whitepace, is scanned as three token in shell, as well. +# `1 + 2`, which embeds whitespace, is scanned as three token in shell, as well. # In shell, several characters with special meaning lose that meaning when not # surrounded by whitespace. For instance, the negation operator `!` is special # when standing alone surrounded by whitespace; whereas in `foo!uucp` it is
diff --git a/t/chainlint/chain-break-false.expect b/t/chainlint/chain-break-false.expect index f6a0a30..db6f8b1 100644 --- a/t/chainlint/chain-break-false.expect +++ b/t/chainlint/chain-break-false.expect
@@ -1,4 +1,4 @@ -2 if condition not satisified +2 if condition not satisfied 3 then 4 echo it did not work... 5 echo failed!
diff --git a/t/chainlint/chain-break-false.test b/t/chainlint/chain-break-false.test index f78ad91..924c962 100644 --- a/t/chainlint/chain-break-false.test +++ b/t/chainlint/chain-break-false.test
@@ -1,6 +1,6 @@ test_expect_success 'chain-break-false' ' # LINT: broken &&-chain okay if explicit "false" signals failure -if condition not satisified +if condition not satisfied then echo it did not work... echo failed!
diff --git a/t/helper/test-advise.c b/t/helper/test-advise.c index 81ed93a..8f9db26 100644 --- a/t/helper/test-advise.c +++ b/t/helper/test-advise.c
@@ -11,7 +11,7 @@ int cmd__advise_if_enabled(int argc, const char **argv) if (argc != 2) die("usage: %s <advice>", argv[0]); - setup_git_directory(); + setup_git_directory(the_repository); repo_config(the_repository, git_default_config, NULL); /*
diff --git a/t/helper/test-bitmap.c b/t/helper/test-bitmap.c index 16a0166..b130832 100644 --- a/t/helper/test-bitmap.c +++ b/t/helper/test-bitmap.c
@@ -2,7 +2,10 @@ #include "test-tool.h" #include "git-compat-util.h" +#include "hex.h" +#include "odb.h" #include "pack-bitmap.h" +#include "pseudo-merge.h" #include "setup.h" static int bitmap_list_commits(void) @@ -35,9 +38,114 @@ static int bitmap_dump_pseudo_merge_objects(uint32_t n) return test_bitmap_pseudo_merge_objects(the_repository, n); } +static int add_packed_object(const struct object_id *oid, + struct packed_git *pack, + uint32_t pos, + void *_data) +{ + struct packing_data *packed = _data; + struct object_entry *entry; + struct object_info oi = OBJECT_INFO_INIT; + enum object_type type; + + oi.typep = &type; + + entry = packlist_alloc(packed, oid); + entry->idx.offset = nth_packed_object_offset(pack, pos); + if (packed_object_info(pack, entry->idx.offset, &oi) < 0) + die("could not get type of object %s", + oid_to_hex(oid)); + oe_set_type(entry, type); + oe_set_in_pack(packed, entry, pack); + + return 0; +} + +static int idx_oid_cmp(const void *va, const void *vb) +{ + const struct pack_idx_entry *a = *(const struct pack_idx_entry **)va; + const struct pack_idx_entry *b = *(const struct pack_idx_entry **)vb; + + return oidcmp(&a->oid, &b->oid); +} + +static int bitmap_write(const char *basename) +{ + struct packed_git *p = NULL; + struct packing_data packed = { 0 }; + struct bitmap_writer writer; + struct pack_idx_entry **index; + struct strbuf buf = STRBUF_INIT; + uint32_t i; + + prepare_repo_settings(the_repository); + repo_for_each_pack(the_repository, p) { + if (!strcmp(pack_basename(p), basename)) + break; + } + + if (!p) + die("could not find pack '%s'", basename); + + if (open_pack_index(p)) + die("cannot open pack index for '%s'", p->pack_name); + + prepare_packing_data(the_repository, &packed); + + for_each_object_in_pack(p, add_packed_object, &packed, + ODB_FOR_EACH_OBJECT_PACK_ORDER); + + /* + * Build the index array now that data.packed.objects[] is + * fully allocated (packlist_alloc() may have reallocated it + * during the loop above). + */ + ALLOC_ARRAY(index, p->num_objects); + for (i = 0; i < p->num_objects; i++) + index[i] = &packed.objects[i].idx; + + bitmap_writer_init(&writer, the_repository, &packed, NULL); + bitmap_writer_build_type_index(&writer, index); + + while (strbuf_getline_lf(&buf, stdin) != EOF) { + struct object_id oid; + struct commit *c; + + if (get_oid_hex(buf.buf, &oid)) + die("invalid OID: %s", buf.buf); + + c = lookup_commit(the_repository, &oid); + if (!c || repo_parse_commit(the_repository, c)) + die("could not parse commit %s", buf.buf); + + bitmap_writer_push_commit(&writer, c, 0); + } + + select_pseudo_merges(&writer); + if (bitmap_writer_build(&writer) < 0) + die("failed to build bitmaps"); + + bitmap_writer_set_checksum(&writer, p->hash); + + QSORT(index, p->num_objects, idx_oid_cmp); + + strbuf_reset(&buf); + strbuf_addstr(&buf, p->pack_name); + strbuf_strip_suffix(&buf, ".pack"); + strbuf_addstr(&buf, ".bitmap"); + bitmap_writer_finish(&writer, index, buf.buf, 0); + + bitmap_writer_free(&writer); + strbuf_release(&buf); + free(index); + clear_packing_data(&packed); + + return 0; +} + int cmd__bitmap(int argc, const char **argv) { - setup_git_directory(); + setup_git_directory(the_repository); if (argc == 2 && !strcmp(argv[1], "list-commits")) return bitmap_list_commits(); @@ -51,13 +159,16 @@ int cmd__bitmap(int argc, const char **argv) return bitmap_dump_pseudo_merge_commits(atoi(argv[2])); if (argc == 3 && !strcmp(argv[1], "dump-pseudo-merge-objects")) return bitmap_dump_pseudo_merge_objects(atoi(argv[2])); + if (argc == 3 && !strcmp(argv[1], "write")) + return bitmap_write(argv[2]); usage("\ttest-tool bitmap list-commits\n" "\ttest-tool bitmap list-commits-with-offset\n" "\ttest-tool bitmap dump-hashes\n" "\ttest-tool bitmap dump-pseudo-merges\n" "\ttest-tool bitmap dump-pseudo-merge-commits <n>\n" - "\ttest-tool bitmap dump-pseudo-merge-objects <n>"); + "\ttest-tool bitmap dump-pseudo-merge-objects <n>\n" + "\ttest-tool bitmap write <pack-basename> < <commit-list>"); return -1; }
diff --git a/t/helper/test-bloom.c b/t/helper/test-bloom.c index 3283544..0c65bef 100644 --- a/t/helper/test-bloom.c +++ b/t/helper/test-bloom.c
@@ -52,7 +52,7 @@ static const char *const bloom_usage = "\n" int cmd__bloom(int argc, const char **argv) { - setup_git_directory(); + setup_git_directory(the_repository); if (argc < 2) usage(bloom_usage);
diff --git a/t/helper/test-cache-tree.c b/t/helper/test-cache-tree.c index ff61d0c..d42e260 100644 --- a/t/helper/test-cache-tree.c +++ b/t/helper/test-cache-tree.c
@@ -33,7 +33,7 @@ int cmd__cache_tree(int argc, const char **argv) OPT_END() }; - setup_git_directory(); + setup_git_directory(the_repository); argc = parse_options(argc, argv, NULL, options, test_cache_tree_usage, 0);
diff --git a/t/helper/test-config.c b/t/helper/test-config.c index 9f8cca7..cfb3f4b 100644 --- a/t/helper/test-config.c +++ b/t/helper/test-config.c
@@ -102,7 +102,7 @@ int cmd__config(int argc, const char **argv) return 0; } - setup_git_directory(); + setup_git_directory(the_repository); git_configset_init(&cs);
diff --git a/t/helper/test-dump-cache-tree.c b/t/helper/test-dump-cache-tree.c index 3f0c7d0..ccb41a4 100644 --- a/t/helper/test-dump-cache-tree.c +++ b/t/helper/test-dump-cache-tree.c
@@ -66,7 +66,7 @@ int cmd__dump_cache_tree(int ac UNUSED, const char **av UNUSED) struct cache_tree *another = cache_tree(); int ret; - setup_git_directory(); + setup_git_directory(the_repository); if (repo_read_index(the_repository) < 0) die("unable to read index file"); istate = *the_repository->index;
diff --git a/t/helper/test-dump-fsmonitor.c b/t/helper/test-dump-fsmonitor.c index efd017c..c991cbb 100644 --- a/t/helper/test-dump-fsmonitor.c +++ b/t/helper/test-dump-fsmonitor.c
@@ -9,7 +9,7 @@ int cmd__dump_fsmonitor(int ac UNUSED, const char **av UNUSED) { struct index_state *istate = the_repository->index; - setup_git_directory(); + setup_git_directory(the_repository); if (do_read_index(istate, the_repository->index_file, 0) < 0) die("unable to read index file"); if (!istate->fsmonitor_last_update) {
diff --git a/t/helper/test-dump-split-index.c b/t/helper/test-dump-split-index.c index f855a38..aae0a40 100644 --- a/t/helper/test-dump-split-index.c +++ b/t/helper/test-dump-split-index.c
@@ -17,7 +17,7 @@ int cmd__dump_split_index(int ac UNUSED, const char **av) { struct split_index *si; - setup_git_directory(); + setup_git_directory(the_repository); do_read_index(the_repository->index, av[1], 1); printf("own %s\n", oid_to_hex(&the_repository->index->oid));
diff --git a/t/helper/test-dump-untracked-cache.c b/t/helper/test-dump-untracked-cache.c index 01a1094..24308bd 100644 --- a/t/helper/test-dump-untracked-cache.c +++ b/t/helper/test-dump-untracked-cache.c
@@ -54,7 +54,7 @@ int cmd__dump_untracked_cache(int ac UNUSED, const char **av UNUSED) xsetenv("GIT_CONFIG_KEY_0", "core.untrackedCache", 1); xsetenv("GIT_CONFIG_VALUE_0", "keep", 1); - setup_git_directory(); + setup_git_directory(the_repository); if (repo_read_index(the_repository) < 0) die("unable to read index file"); uc = the_repository->index->untracked;
diff --git a/t/helper/test-find-pack.c b/t/helper/test-find-pack.c index fc4b8a7..28d5b1f 100644 --- a/t/helper/test-find-pack.c +++ b/t/helper/test-find-pack.c
@@ -25,7 +25,7 @@ int cmd__find_pack(int argc, const char **argv) struct object_id oid; struct packed_git *p; int count = -1, actual_count = 0; - const char *prefix = setup_git_directory(); + const char *prefix = setup_git_directory(the_repository); struct option options[] = { OPT_INTEGER('c', "check-count", &count, "expected number of packs"),
diff --git a/t/helper/test-fsmonitor-client.c b/t/helper/test-fsmonitor-client.c index 02bfe92..dc1dff2 100644 --- a/t/helper/test-fsmonitor-client.c +++ b/t/helper/test-fsmonitor-client.c
@@ -210,7 +210,7 @@ int cmd__fsmonitor_client(int argc, const char **argv) subcmd = argv[0]; - setup_git_directory(); + setup_git_directory(the_repository); if (!strcmp(subcmd, "query")) return !!do_send_query(token);
diff --git a/t/helper/test-lazy-init-name-hash.c b/t/helper/test-lazy-init-name-hash.c index 40f5df4..e542985 100644 --- a/t/helper/test-lazy-init-name-hash.c +++ b/t/helper/test-lazy-init-name-hash.c
@@ -211,7 +211,7 @@ int cmd__lazy_init_name_hash(int argc, const char **argv) const char *prefix; uint64_t avg_single, avg_multi; - prefix = setup_git_directory(); + prefix = setup_git_directory(the_repository); argc = parse_options(argc, argv, prefix, options, usage, 0);
diff --git a/t/helper/test-match-trees.c b/t/helper/test-match-trees.c index 2ed064b..006ce52 100644 --- a/t/helper/test-match-trees.c +++ b/t/helper/test-match-trees.c
@@ -13,7 +13,7 @@ int cmd__match_trees(int ac UNUSED, const char **av) struct object_id hash1, hash2, shifted; struct tree *one, *two; - setup_git_directory(); + setup_git_directory(the_repository); if (repo_get_oid(the_repository, av[1], &hash1)) die("cannot parse %s as an object name", av[1]);
diff --git a/t/helper/test-pack-deltas.c b/t/helper/test-pack-deltas.c index 4981401..c493b75 100644 --- a/t/helper/test-pack-deltas.c +++ b/t/helper/test-pack-deltas.c
@@ -95,7 +95,7 @@ int cmd__pack_deltas(int argc, const char **argv) if (argc || num_objects < 0) usage_with_options(usage_str, options); - setup_git_directory(); + setup_git_directory(the_repository); f = hashfd(the_repository->hash_algo, 1, "<stdout>"); write_pack_header(f, num_objects);
diff --git a/t/helper/test-pack-mtimes.c b/t/helper/test-pack-mtimes.c index 7a8ee1d..b774056 100644 --- a/t/helper/test-pack-mtimes.c +++ b/t/helper/test-pack-mtimes.c
@@ -32,7 +32,7 @@ int cmd__pack_mtimes(int argc, const char **argv) struct strbuf buf = STRBUF_INIT; struct packed_git *p; - setup_git_directory(); + setup_git_directory(the_repository); if (argc != 2) usage(pack_mtimes_usage);
diff --git a/t/helper/test-partial-clone.c b/t/helper/test-partial-clone.c index d848800..a7aab42 100644 --- a/t/helper/test-partial-clone.c +++ b/t/helper/test-partial-clone.c
@@ -1,3 +1,5 @@ +#define USE_THE_REPOSITORY_VARIABLE + #include "test-tool.h" #include "hex.h" #include "repository.h" @@ -32,7 +34,7 @@ static void object_info(const char *gitdir, const char *oid_hex) int cmd__partial_clone(int argc, const char **argv) { - setup_git_directory(); + setup_git_directory(the_repository); if (argc < 4) die("too few arguments");
diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c index 874542e..15eb444 100644 --- a/t/helper/test-path-utils.c +++ b/t/helper/test-path-utils.c
@@ -377,9 +377,9 @@ int cmd__path_utils(int argc, const char **argv) const char *prefix = argv[2]; int prefix_len = strlen(prefix); int nongit_ok; - setup_git_directory_gently(&nongit_ok); + setup_git_directory_gently(the_repository, &nongit_ok); while (argc > 3) { - char *pfx = prefix_path(prefix, prefix_len, argv[3]); + char *pfx = prefix_path(the_repository, prefix, prefix_len, argv[3]); puts(pfx); free(pfx);
diff --git a/t/helper/test-path-walk.c b/t/helper/test-path-walk.c index fe63002..4233bad 100644 --- a/t/helper/test-path-walk.c +++ b/t/helper/test-path-walk.c
@@ -4,6 +4,7 @@ #include "dir.h" #include "environment.h" #include "hex.h" +#include "list-objects-filter-options.h" #include "object-name.h" #include "object.h" #include "pretty.h" @@ -67,10 +68,12 @@ static int emit_block(const char *path, struct oid_array *oids, int cmd__path_walk(int argc, const char **argv) { - int res, stdin_pl = 0; + int res, stdin_pl = 0, pl_sparse_trees = -1; struct rev_info revs = REV_INFO_INIT; struct path_walk_info info = PATH_WALK_INFO_INIT; struct path_walk_test_data data = { 0 }; + struct list_objects_filter_options filter_options = + LIST_OBJECTS_FILTER_INIT; struct option options[] = { OPT_BOOL(0, "blobs", &info.blobs, N_("toggle inclusion of blob objects")), @@ -86,11 +89,14 @@ int cmd__path_walk(int argc, const char **argv) N_("toggle aggressive edge walk")), OPT_BOOL(0, "stdin-pl", &stdin_pl, N_("read a pattern list over stdin")), + OPT_BOOL(0, "pl-sparse-trees", &pl_sparse_trees, + N_("toggle pruning of trees by sparse patterns")), + OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), OPT_END(), }; - setup_git_directory(); - revs.repo = the_repository; + setup_git_directory(the_repository); + repo_init_revisions(the_repository, &revs, NULL); argc = parse_options(argc, argv, NULL, options, path_walk_usage, @@ -101,6 +107,10 @@ int cmd__path_walk(int argc, const char **argv) else usage(path_walk_usage[0]); + /* Apply the filter after setup_revisions to avoid the --objects check. */ + if (filter_options.choice) + list_objects_filter_copy(&revs.filter, &filter_options); + info.revs = &revs; info.path_fn = emit_block; info.path_fn_data = &data; @@ -108,6 +118,8 @@ int cmd__path_walk(int argc, const char **argv) if (stdin_pl) { struct strbuf in = STRBUF_INIT; CALLOC_ARRAY(info.pl, 1); + info.pl_sparse_trees = (pl_sparse_trees >= 0) ? + pl_sparse_trees : 1; info.pl->use_cone_patterns = 1; @@ -129,6 +141,7 @@ int cmd__path_walk(int argc, const char **argv) free(info.pl); } + list_objects_filter_release(&filter_options); release_revisions(&revs); return res; }
diff --git a/t/helper/test-reach.c b/t/helper/test-reach.c index 3131b54..5d86a96 100644 --- a/t/helper/test-reach.c +++ b/t/helper/test-reach.c
@@ -39,7 +39,7 @@ int cmd__reach(int ac, const char **av) struct strbuf buf = STRBUF_INIT; struct repository *r = the_repository; - setup_git_directory(); + setup_git_directory(the_repository); if (ac < 2) exit(1);
diff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c index 9ae71ce..6b08ba8 100644 --- a/t/helper/test-read-cache.c +++ b/t/helper/test-read-cache.c
@@ -19,7 +19,7 @@ int cmd__read_cache(int argc, const char **argv) if (argc == 2) cnt = strtol(argv[1], NULL, 0); - setup_git_directory(); + setup_git_directory(the_repository); repo_config(the_repository, git_default_config, NULL); for (i = 0; i < cnt; i++) {
diff --git a/t/helper/test-read-graph.c b/t/helper/test-read-graph.c index 6a5f64e..9f07b9c 100644 --- a/t/helper/test-read-graph.c +++ b/t/helper/test-read-graph.c
@@ -76,7 +76,7 @@ int cmd__read_graph(int argc, const char **argv) struct odb_source *source; int ret = 0; - setup_git_directory(); + setup_git_directory(the_repository); source = the_repository->objects->sources; prepare_repo_settings(the_repository);
diff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c index 388d29e..790000f 100644 --- a/t/helper/test-read-midx.c +++ b/t/helper/test-read-midx.c
@@ -14,7 +14,7 @@ static struct multi_pack_index *setup_midx(const char *object_dir) { struct odb_source *source; - setup_git_directory(); + setup_git_directory(the_repository); source = odb_find_source(the_repository->objects, object_dir); if (!source) source = odb_add_to_alternates_memory(the_repository->objects,
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c index 74edf20..3866d0a 100644 --- a/t/helper/test-ref-store.c +++ b/t/helper/test-ref-store.c
@@ -340,7 +340,7 @@ int cmd__ref_store(int argc UNUSED, const char **argv) const char *func; struct command *cmd; - setup_git_directory(); + setup_git_directory(the_repository); argv = get_store(argv + 1, &refs);
diff --git a/t/helper/test-revision-walking.c b/t/helper/test-revision-walking.c index 071f5bd..70051ee 100644 --- a/t/helper/test-revision-walking.c +++ b/t/helper/test-revision-walking.c
@@ -56,7 +56,7 @@ int cmd__revision_walking(int argc, const char **argv) if (argc < 2) return 1; - setup_git_directory(); + setup_git_directory(the_repository); if (!strcmp(argv[1], "run-twice")) { printf("1st\n");
diff --git a/t/helper/test-scrap-cache-tree.c b/t/helper/test-scrap-cache-tree.c index 64fff6e..7b5ce50 100644 --- a/t/helper/test-scrap-cache-tree.c +++ b/t/helper/test-scrap-cache-tree.c
@@ -12,7 +12,7 @@ int cmd__scrap_cache_tree(int ac UNUSED, const char **av UNUSED) { struct lock_file index_lock = LOCK_INIT; - setup_git_directory(); + setup_git_directory(the_repository); repo_hold_locked_index(the_repository, &index_lock, LOCK_DIE_ON_ERROR); if (repo_read_index(the_repository) < 0) die("unable to read index file");
diff --git a/t/helper/test-serve-v2.c b/t/helper/test-serve-v2.c index 63a200b..27f3ed8 100644 --- a/t/helper/test-serve-v2.c +++ b/t/helper/test-serve-v2.c
@@ -23,7 +23,7 @@ int cmd__serve_v2(int argc, const char **argv) N_("exit immediately after advertising capabilities")), OPT_END() }; - const char *prefix = setup_git_directory(); + const char *prefix = setup_git_directory(the_repository); /* ignore all unknown cmdline switches for now */ argc = parse_options(argc, argv, prefix, options, serve_usage,
diff --git a/t/helper/test-submodule-config.c b/t/helper/test-submodule-config.c index cbe93f2..3f30292 100644 --- a/t/helper/test-submodule-config.c +++ b/t/helper/test-submodule-config.c
@@ -34,7 +34,7 @@ int cmd__submodule_config(int argc, const char **argv) if (my_argc % 2 != 0) die_usage(argc, argv, "Wrong number of arguments."); - setup_git_directory(); + setup_git_directory(the_repository); while (*arg) { struct object_id commit_oid;
diff --git a/t/helper/test-submodule-nested-repo-config.c b/t/helper/test-submodule-nested-repo-config.c index 2710341..7e31d3f 100644 --- a/t/helper/test-submodule-nested-repo-config.c +++ b/t/helper/test-submodule-nested-repo-config.c
@@ -19,7 +19,7 @@ int cmd__submodule_nested_repo_config(int argc, const char **argv) if (argc < 3) die_usage(argv, "Wrong number of arguments."); - setup_git_directory(); + setup_git_directory(the_repository); if (repo_submodule_init(&subrepo, the_repository, argv[1], null_oid(the_hash_algo))) { die_usage(argv, "Submodule not found.");
diff --git a/t/helper/test-submodule.c b/t/helper/test-submodule.c index 0133852..3c5c4c4 100644 --- a/t/helper/test-submodule.c +++ b/t/helper/test-submodule.c
@@ -99,7 +99,7 @@ static int cmd__submodule_is_active(int argc, const char **argv) if (argc != 1) usage_with_options(submodule_is_active_usage, options); - setup_git_directory(); + setup_git_directory(the_repository); return !is_submodule_active(the_repository, argv[0]); } @@ -142,7 +142,7 @@ static int cmd__submodule_config_list(int argc, const char **argv) argc = parse_options(argc, argv, "test-tools", options, usage, PARSE_OPT_KEEP_ARGV0); - setup_git_directory(); + setup_git_directory(the_repository); if (argc == 2) return print_config_from_gitmodules(the_repository, argv[1]); @@ -161,7 +161,7 @@ static int cmd__submodule_config_set(int argc, const char **argv) argc = parse_options(argc, argv, "test-tools", options, usage, PARSE_OPT_KEEP_ARGV0); - setup_git_directory(); + setup_git_directory(the_repository); /* Equivalent to ACTION_SET in builtin/config.c */ if (argc == 3) { @@ -183,7 +183,7 @@ static int cmd__submodule_config_unset(int argc, const char **argv) NULL }; - setup_git_directory(); + setup_git_directory(the_repository); if (argc == 2) { if (!is_writing_gitmodules_ok()) @@ -202,7 +202,7 @@ static int cmd__submodule_config_writeable(int argc, const char **argv UNUSED) "test-tool submodule config-writeable", NULL }; - setup_git_directory(); + setup_git_directory(the_repository); if (argc == 1) return is_writing_gitmodules_ok() ? 0 : -1;
diff --git a/t/helper/test-subprocess.c b/t/helper/test-subprocess.c index c344f16..a8194d2 100644 --- a/t/helper/test-subprocess.c +++ b/t/helper/test-subprocess.c
@@ -1,3 +1,5 @@ +#define USE_THE_REPOSITORY_VARIABLE + #include "test-tool.h" #include "run-command.h" #include "setup.h" @@ -7,11 +9,11 @@ int cmd__subprocess(int argc, const char **argv) struct child_process cp = CHILD_PROCESS_INIT; int nogit = 0; - setup_git_directory_gently(&nogit); + setup_git_directory_gently(the_repository, &nogit); if (nogit) die("No git repo found"); if (argc > 1 && !strcmp(argv[1], "--setup-work-tree")) { - setup_work_tree(); + setup_work_tree(the_repository); argv++; } cp.git_cmd = 1;
diff --git a/t/helper/test-synthesize.c b/t/helper/test-synthesize.c index 1f28ecf..3fa534f 100644 --- a/t/helper/test-synthesize.c +++ b/t/helper/test-synthesize.c
@@ -506,7 +506,7 @@ static int cmd__synthesize__pack(int argc, const char **argv, OPT_END() }; - setup_git_directory_gently(&non_git); + setup_git_directory_gently(the_repository, &non_git); repo = the_repository; algo = unsafe_hash_algo(repo->hash_algo);
diff --git a/t/helper/test-userdiff.c b/t/helper/test-userdiff.c index aa3a989..fc34c58 100644 --- a/t/helper/test-userdiff.c +++ b/t/helper/test-userdiff.c
@@ -40,7 +40,7 @@ int cmd__userdiff(int argc, const char **argv) return error("unknown argument %s", argv[1]); if (want & USERDIFF_DRIVER_TYPE_CUSTOM) { - setup_git_directory(); + setup_git_directory(the_repository); repo_config(the_repository, cmd__userdiff_config, NULL); }
diff --git a/t/helper/test-write-cache.c b/t/helper/test-write-cache.c index b37dd2c..98e1477 100644 --- a/t/helper/test-write-cache.c +++ b/t/helper/test-write-cache.c
@@ -12,7 +12,7 @@ int cmd__write_cache(int argc, const char **argv) int i, cnt = 1; if (argc == 2) cnt = strtol(argv[1], NULL, 0); - setup_git_directory(); + setup_git_directory(the_repository); repo_read_index(the_repository); for (i = 0; i < cnt; i++) { repo_hold_locked_index(the_repository, &index_lock,
diff --git a/t/lib-git-daemon.sh b/t/lib-git-daemon.sh index e625692..d172aa5 100644 --- a/t/lib-git-daemon.sh +++ b/t/lib-git-daemon.sh
@@ -85,14 +85,16 @@ # kill git-daemon child of git say >&3 "Stopping git daemon ..." + kill "$GIT_DAEMON_PID" - wait "$GIT_DAEMON_PID" >&3 2>&4 - ret=$? + ret=0; wait "$GIT_DAEMON_PID" >&3 2>&4 || ret=$? + if ! test_match_signal 15 $ret then error "git daemon exited with status: $ret" fi - kill "$(cat "$GIT_DAEMON_PIDFILE")" 2>/dev/null + + kill "$(cat "$GIT_DAEMON_PIDFILE")" 2>/dev/null || : GIT_DAEMON_PID= rm -f git_daemon_output "$GIT_DAEMON_PIDFILE" }
diff --git a/t/lib-git-p4.sh b/t/lib-git-p4.sh index d22e9c6..9108868 100644 --- a/t/lib-git-p4.sh +++ b/t/lib-git-p4.sh
@@ -65,6 +65,7 @@ stop_p4d_and_watchdog () { kill -9 $p4d_pid $watchdog_pid + wait $p4d_pid $watchdog_pid 2>/dev/null } # git p4 submit generates a temp file, which will @@ -174,8 +175,7 @@ } stop_and_cleanup_p4d () { - kill -9 $p4d_pid $watchdog_pid - wait $p4d_pid + stop_p4d_and_watchdog rm -rf "$db" "$cli" "$pidfile" }
diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index 2fde235..52843f6 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh
@@ -15,8 +15,7 @@ SVN_TREE=$GIT_SVN_DIR/svn-tree test_set_port SVNSERVE_PORT -svn >/dev/null 2>&1 -if test $? -ne 1 +if ! svn help >/dev/null 2>&1 then skip_all='skipping git svn tests, svn not found' test_done @@ -27,13 +26,13 @@ svnconf=$PWD/svnconf export svnconf +x=0 perl -w -e " use SVN::Core; use SVN::Repos; \$SVN::Core::VERSION gt '1.1.0' or exit(42); system(qw/svnadmin create --fs-type fsfs/, \$ENV{svnrepo}) == 0 or exit(41); -" >&3 2>&4 -x=$? +" >&3 2>&4 || x=$? if test $x -ne 0 then if test $x -eq 42; then
diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh index 4c76e81..fc64644 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh
@@ -235,11 +235,10 @@ test_atexit stop_httpd - "$LIB_HTTPD_PATH" -d "$HTTPD_ROOT_PATH" \ + if ! "$LIB_HTTPD_PATH" -d "$HTTPD_ROOT_PATH" \ -f "$TEST_PATH/apache.conf" $HTTPD_PARA \ -c "Listen 127.0.0.1:$LIB_HTTPD_PORT" -k start \ >&3 2>&4 - if test $? -ne 0 then cat "$HTTPD_ROOT_PATH"/error.log >&4 2>/dev/null test_skip_or_die GIT_TEST_HTTPD "web server setup failed"
diff --git a/t/meson.build b/t/meson.build index 7528e5c..3219264 100644 --- a/t/meson.build +++ b/t/meson.build
@@ -6,6 +6,7 @@ 'unit-tests/u-hashmap.c', 'unit-tests/u-list-objects-filter-options.c', 'unit-tests/u-mem-pool.c', + 'unit-tests/u-odb-inmemory.c', 'unit-tests/u-oid-array.c', 'unit-tests/u-oidmap.c', 'unit-tests/u-oidtree.c', @@ -397,6 +398,7 @@ 't3450-history.sh', 't3451-history-reword.sh', 't3452-history-split.sh', + 't3453-history-fixup.sh', 't3500-cherry.sh', 't3501-revert-cherry-pick.sh', 't3502-cherry-pick-merge.sh', @@ -704,6 +706,7 @@ 't5563-simple-http-auth.sh', 't5564-http-proxy.sh', 't5565-push-multiple.sh', + 't5566-push-group.sh', 't5570-git-daemon.sh', 't5571-pre-push-hook.sh', 't5572-pull-submodule.sh', @@ -951,6 +954,7 @@ 't7702-repack-cyclic-alternate.sh', 't7703-repack-geometric.sh', 't7704-repack-cruft.sh', + 't7705-repack-incremental-midx.sh', 't7800-difftool.sh', 't7810-grep.sh', 't7811-grep-open.sh', @@ -1114,6 +1118,7 @@ 't9901-git-web--browse.sh', 't9902-completion.sh', 't9903-bash-prompt.sh', + 't9904-url-parse.sh', ] benchmarks = [ @@ -1136,6 +1141,7 @@ 'perf/p1500-graph-walks.sh', 'perf/p1501-rev-parse-oneline.sh', 'perf/p2000-sparse-operations.sh', + 'perf/p3010-ls-files.sh', 'perf/p3400-rebase.sh', 'perf/p3404-rebase-interactive.sh', 'perf/p4000-diff-algorithms.sh',
diff --git a/t/perf/p3010-ls-files.sh b/t/perf/p3010-ls-files.sh new file mode 100755 index 0000000..ae14449 --- /dev/null +++ b/t/perf/p3010-ls-files.sh
@@ -0,0 +1,31 @@ +#!/bin/sh + +test_description='Tests ls-files worktree performance' + +. ./perf-lib.sh + +test_perf_large_repo +test_checkout_worktree + +test_expect_success 'select a zero-prefix pathspec' ' + tracked_file=$(git ls-files | sed -n 1p) && + test -n "$tracked_file" && + pathspec="?${tracked_file#?}" && + test_export pathspec +' + +test_perf 'ls-files --deleted with pathspec' ' + git -c core.fsmonitor=false ls-files --deleted \ + -- "$pathspec" >/dev/null +' + +test_perf 'ls-files --deleted with all-matching pathspec' ' + git -c core.fsmonitor=false ls-files --deleted -- "*" >/dev/null +' + +test_perf 'ls-files --modified with pathspec' ' + git -c core.fsmonitor=false ls-files --modified \ + -- "$pathspec" >/dev/null +' + +test_done
diff --git a/t/perf/p3904-stash-patch.sh b/t/perf/p3904-stash-patch.sh new file mode 100755 index 0000000..4cfce63 --- /dev/null +++ b/t/perf/p3904-stash-patch.sh
@@ -0,0 +1,43 @@ +#!/bin/sh + +test_description="Performance tests for git stash -p" + +. ./perf-lib.sh + +test_perf_fresh_repo + +test_expect_success "setup" ' + mkdir files && + test_seq 1 100000 | while read i; do + echo "content $i" >files/$i.txt || return 1 + done && + git add files/ && + git commit -q -m "add tracked files" && + echo modified >files/1.txt +' + +test_perf "stash -p, no fsmonitor" \ + --setup 'echo modified >files/1.txt' ' + printf "q\n" | git stash -p >/dev/null 2>&1 || true +' + +if test_have_prereq FSMONITOR_DAEMON +then + test_expect_success "enable builtin fsmonitor" ' + git config core.fsmonitor true && + git fsmonitor--daemon start && + git update-index --fsmonitor && + git status >/dev/null 2>&1 + ' + + test_perf "stash -p, builtin fsmonitor" \ + --setup 'echo modified >files/1.txt && git status >/dev/null 2>&1' ' + printf "q\n" | git stash -p >/dev/null 2>&1 || true + ' + + test_expect_success "stop builtin fsmonitor" ' + git fsmonitor--daemon stop + ' +fi + +test_done
diff --git a/t/perf/p5315-pack-objects-filter.sh b/t/perf/p5315-pack-objects-filter.sh new file mode 100755 index 0000000..445ff25 --- /dev/null +++ b/t/perf/p5315-pack-objects-filter.sh
@@ -0,0 +1,129 @@ +#!/bin/sh + +test_description='Tests pack-objects performance with filters and --path-walk' +. ./perf-lib.sh + +test_perf_large_repo + +test_expect_success 'setup filter inputs' ' + # Sample a few depth-2 directories from the test repo to build + # a cone-mode sparse-checkout definition. The sampling picks + # directories at evenly-spaced positions so the choice is stable + # and scales to repos of any shape. + + git ls-tree -d HEAD >top-entries && + grep "^040000" top-entries | + awk "{print \$4;}" >top-dirs && + top_nr=$(wc -l <top-dirs) && + + while read tdir + do + git ls-tree -d --format="$tdir/%(path)" "HEAD:$tdir" || return 1 + done <top-dirs >depth2-dirs && + + d2_nr=$(wc -l <depth2-dirs) && + + if test "$d2_nr" -ge 2 + then + # Pick two directories from evenly-spaced positions. + first=$(sed -n "1p" depth2-dirs) && + mid=$(sed -n "$((d2_nr / 2 + 1))p" depth2-dirs) && + + p1=$(dirname "$first") && + p2=$(dirname "$mid") && + + # Build cone-mode sparse-checkout patterns. + { + echo "/*" && + echo "!/*/" && + echo "/$p1/" && + echo "!/$p1/*/" && + if test "$p1" != "$p2" + then + echo "/$p2/" && + echo "!/$p2/*/" + fi && + echo "/$first/" && + if test "$first" != "$mid" + then + echo "/$mid/" + fi + } >sparse-patterns && + + git hash-object -w sparse-patterns >sparse-oid && + echo "Sparse cone: $first $mid" && + cat sparse-patterns && + test_set_prereq SPARSE_OID + elif test "$top_nr" -ge 1 + then + # Fallback: use a single top-level directory. + first=$(sed -n "1p" top-dirs) && + { + echo "/*" && + echo "!/*/" && + echo "/$first/" + } >sparse-patterns && + + git hash-object -w sparse-patterns >sparse-oid && + echo "Sparse cone: $first" && + cat sparse-patterns && + test_set_prereq SPARSE_OID + fi +' + +test_perf 'repack (no filter)' ' + git pack-objects --stdout --no-reuse-delta --revs --all </dev/null >pk +' + +test_size 'repack size (no filter)' ' + test_file_size pk +' + +test_perf 'repack (no filter, --path-walk)' ' + git pack-objects --stdout --no-reuse-delta --revs --all --path-walk </dev/null >pk +' + +test_size 'repack size (no filter, --path-walk)' ' + test_file_size pk +' + +test_perf 'repack (blob:none)' ' + git pack-objects --stdout --no-reuse-delta --revs --all --filter=blob:none </dev/null >pk +' + +test_size 'repack size (blob:none)' ' + test_file_size pk +' + +test_perf 'repack (blob:none, --path-walk)' ' + git pack-objects --stdout --no-reuse-delta --revs --all --path-walk \ + --filter=blob:none </dev/null >pk +' + +test_size 'repack size (blob:none, --path-walk)' ' + test_file_size pk +' + +test_perf 'repack (sparse:oid)' \ + --prereq SPARSE_OID ' + git pack-objects --stdout --no-reuse-delta --revs --all \ + --filter=sparse:oid=$(cat sparse-oid) </dev/null >pk +' + +test_size 'repack size (sparse:oid)' \ + --prereq SPARSE_OID ' + test_file_size pk +' + +test_perf 'repack (sparse:oid, --path-walk)' \ + --prereq SPARSE_OID ' + git pack-objects --stdout --no-reuse-delta --revs --all --path-walk \ + --filter=sparse:oid=$(cat sparse-oid) </dev/null >pk +' + +test_size 'repack size (sparse:oid, --path-walk)' \ + --prereq SPARSE_OID ' + test_file_size pk +' + +test_done
diff --git a/t/perf/p6100-describe.sh b/t/perf/p6100-describe.sh index 069f91c..b1c6152 100755 --- a/t/perf/p6100-describe.sh +++ b/t/perf/p6100-describe.sh
@@ -27,4 +27,16 @@ git describe --match=new HEAD ' +test_expect_success 'set up many unrelated refs' ' + ref_count=10000 && + git tag -m tip tip HEAD && + test_seq -f "create refs/heads/describe-perf/%05d HEAD" $ref_count | + git update-ref --stdin && + git pack-refs --all +' + +test_perf 'describe exact tag with many unrelated refs' ' + git describe --exact-match HEAD +' + test_done
diff --git a/t/t0001-init.sh b/t/t0001-init.sh index e4d32bb..e89feca 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh
@@ -980,4 +980,14 @@ test_cmp expect err ' +test_expect_success 'init honors GIT_OBJECT_DIRECTORY' ' + test_when_finished "rm -rf init-objdir custom-odb" && + mkdir custom-odb && + env GIT_OBJECT_DIRECTORY="$(pwd)/custom-odb" \ + git init init-objdir && + test_path_is_missing init-objdir/.git/objects/pack && + test_path_is_dir custom-odb/pack && + test_path_is_dir custom-odb/info +' + test_done
diff --git a/t/t0005-signals.sh b/t/t0005-signals.sh index afba0fc..84319cf 100755 --- a/t/t0005-signals.sh +++ b/t/t0005-signals.sh
@@ -42,12 +42,12 @@ ' test_expect_success !MINGW 'a constipated git dies with SIGPIPE' ' - OUT=$( ((large_git; echo $? 1>&3) | :) 3>&1 ) && + OUT=$( ((large_git && echo 0 1>&3 || echo $? 1>&3) | :) 3>&1 ) && test_match_signal 13 "$OUT" ' test_expect_success !MINGW 'a constipated git dies with SIGPIPE even if parent ignores it' ' - OUT=$( ((trap "" PIPE && large_git; echo $? 1>&3) | :) 3>&1 ) && + OUT=$( ((trap "" PIPE && large_git && echo 0 1>&3 || echo $? 1>&3) | :) 3>&1 ) && test_match_signal 13 "$OUT" '
diff --git a/t/t0006-date.sh b/t/t0006-date.sh index 53ced36..6e63130 100755 --- a/t/t0006-date.sh +++ b/t/t0006-date.sh
@@ -138,6 +138,13 @@ check_parse '1969-12-31 23:59:59 +11' bad check_parse '1969-12-31 23:59:59 -11' bad +# pathologically small timestamps requiring `@` prefix +check_parse '@0 +0000' '1970-01-01 00:00:00 +0000' +check_parse '@99999999 +0000' '1973-03-03 09:46:39 +0000' +check_parse '99999999 +0000' bad +check_parse '@100000000 +0000' '1973-03-03 09:46:40 +0000' +check_parse '100000000 +0000' '1973-03-03 09:46:40 +0000' + REQUIRE_64BIT_TIME=HAVE_64BIT_TIME check_parse '2099-12-31 23:59:59' '2099-12-31 23:59:59 +0000' check_parse '2099-12-31 23:59:59 +00' '2099-12-31 23:59:59 +0000' @@ -155,15 +162,45 @@ check_parse '2100-00-00 00:00:00 +11' bad REQUIRE_64BIT_TIME= +add_time_offset() { + case "$3" in + hours) + unit=$(( 60*60 )) + ;; + days) + unit=$(( 24*60*60 )) + ;; + esac + offset=$(( $2 * unit )) + echo $(( $1 + offset )) +} + check_approxidate() { + old_date=$GIT_TEST_DATE_NOW + if test "$3" = "failure" + then + expection="$3" + else + expection=${4:-success} + offset="$3" + fi + if test -n "$offset" + then + GIT_TEST_DATE_NOW=$(add_time_offset $old_date $offset) + caption="$1; offset $offset" + else + caption=$1 + fi echo "$1 -> $2 +0000" >expect - test_expect_${3:-success} "parse approxidate ($1)" " + test_expect_$expection "parse approxidate ($caption)" " test-tool date approxidate '$1' >actual && test_cmp expect actual " + GIT_TEST_DATE_NOW=$old_date } check_approxidate now '2009-08-30 19:20:00' +check_approxidate today '2009-08-30 00:00:00' check_approxidate '5 seconds ago' '2009-08-30 19:19:55' check_approxidate 5.seconds.ago '2009-08-30 19:19:55' check_approxidate 10.minutes.ago '2009-08-30 19:10:00' @@ -179,14 +216,26 @@ check_approxidate '3:00' '2009-08-30 03:00:00' check_approxidate '15:00' '2009-08-30 15:00:00' check_approxidate 'noon today' '2009-08-30 12:00:00' +check_approxidate 'today at noon' '2009-08-30 12:00:00' '-12 hours' +check_approxidate 'noon today' '2009-09-01 12:00:00' '+36 hours' check_approxidate 'noon yesterday' '2009-08-29 12:00:00' +check_approxidate 'noon yesterday' '2009-08-29 12:00:00' '-12 hours' +check_approxidate 'last Friday at noon' '2009-08-28 12:00:00' +check_approxidate 'last Friday at noon' '2009-08-28 12:00:00' '-12 hours' +check_approxidate 'tea last saturday' '2009-08-29 17:00:00' +check_approxidate 'tea last saturday' '2009-08-29 17:00:00' '-12 hours' check_approxidate 'January 5th noon pm' '2009-01-05 12:00:00' +check_approxidate 'January 5th noon pm' '2009-01-05 12:00:00' '-12 hours' +check_approxidate 'January 5th today pm' '2009-01-30 12:00:00' check_approxidate '10am noon' '2009-08-29 12:00:00' +check_approxidate 'January 5th yesterday' '2009-01-29 19:20:00' +check_approxidate 'January 5th yesterday' '2008-12-31 19:20:00' '+2 days' check_approxidate 'last tuesday' '2009-08-25 19:20:00' check_approxidate 'July 5th' '2009-07-05 19:20:00' check_approxidate '06/05/2009' '2009-06-05 19:20:00' check_approxidate '06.05.2009' '2009-05-06 19:20:00' +check_approxidate 'Jan 5 today' '2009-01-30 00:00:00' check_approxidate 'Jun 6, 5AM' '2009-06-06 05:00:00' check_approxidate '5AM Jun 6' '2009-06-06 05:00:00' @@ -195,6 +244,10 @@ check_approxidate '2008-12-01' '2008-12-01 19:20:00' check_approxidate '2009-12-01' '2009-12-01 19:20:00' +# ambiguous raw timestamp +check_approxidate '2000 +0000' '2000-08-30 19:20:00' +check_approxidate '@2000 +0000' '1970-01-01 00:33:20' + check_date_format_human() { t=$(($GIT_TEST_DATE_NOW - $1)) echo "$t -> $2" >expect
diff --git a/t/t0008-ignores.sh b/t/t0008-ignores.sh index e716b5c..d77a179 100755 --- a/t/t0008-ignores.sh +++ b/t/t0008-ignores.sh
@@ -122,8 +122,8 @@ fi testname="$1" expect_all="$2" code="$3" - expect_verbose=$( echo "$expect_all" | grep -v '^:: ' ) - expect=$( echo "$expect_verbose" | sed -e 's/.* //' ) + expect_verbose=$(echo "$expect_all" | grep -v '^:: ' || :) + expect=$(echo "$expect_verbose" | sed -e 's/.* //') test_expect_success $prereq "$testname${no_index_opt:+ with $no_index_opt}" ' expect "$expect" &&
diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh index 68b4903..5144b0e 100755 --- a/t/t0014-alias.sh +++ b/t/t0014-alias.sh
@@ -128,6 +128,12 @@ test_grep "ran-subsection" output ' +test_expect_success 'simple dotted alias syntax still works' ' + test_config alias.simple.dotted "!echo ran-simple-dotted" && + git simple.dotted >output && + test_grep "ran-simple-dotted" output +' + test_expect_success 'subsection syntax only accepts command key' ' test_config alias.invalid.notcommand value && test_must_fail git invalid 2>error && @@ -183,6 +189,12 @@ test_grep "förgrena" output ' +test_expect_success 'simple dotted aliases listed in help -a' ' + test_config alias.simple.listed "!echo test" && + git help -a >output && + test_grep "simple.listed" output +' + test_expect_success 'empty subsection treated as no subsection' ' test_config "alias..something" "!echo foobar" && git something >actual &&
diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh index f0d50d7..033b00a 100755 --- a/t/t0021-conversion.sh +++ b/t/t0021-conversion.sh
@@ -857,6 +857,23 @@ ) ' +test_expect_success 'missing process filter with space in path does not die' ' + test_config_global filter.protocol.process "/non existent/tool" && + test_config_global filter.protocol.required true && + rm -rf repo && + mkdir repo && + ( + cd repo && + git init && + + echo "*.r filter=protocol" >.gitattributes && + + cp "$TEST_ROOT/test.o" test.r && + test_must_fail git add . 2>git-stderr.log && + test_grep "clean filter.*protocol.*failed" git-stderr.log + ) +' + test_expect_success 'delayed checkout in process filter' ' test_config_global filter.a.process "test-tool rot13-filter --log=a.log clean smudge delay" && test_config_global filter.a.required true &&
diff --git a/t/t0035-safe-bare-repository.sh b/t/t0035-safe-bare-repository.sh index ae7ef09..1d3d19f 100755 --- a/t/t0035-safe-bare-repository.sh +++ b/t/t0035-safe-bare-repository.sh
@@ -44,11 +44,16 @@ test_path_is_dir outer-repo/.git/modules/subn ' -test_expect_success 'safe.bareRepository unset' ' +test_expect_success !WITH_BREAKING_CHANGES 'safe.bareRepository unset' ' test_unconfig --global safe.bareRepository && expect_accepted_implicit -C outer-repo/bare-repo ' +test_expect_success WITH_BREAKING_CHANGES 'safe.bareRepository unset (defaults to explicit)' ' + test_unconfig --global safe.bareRepository && + expect_rejected -C outer-repo/bare-repo +' + test_expect_success 'safe.bareRepository=all' ' test_config_global safe.bareRepository all && expect_accepted_implicit -C outer-repo/bare-repo @@ -63,7 +68,8 @@ # safe.bareRepository must not be "explicit", otherwise # git config fails with "fatal: not in a git directory" (like # safe.directory) - test_config -C outer-repo/bare-repo safe.bareRepository all && + test_when_finished "git config --file outer-repo/bare-repo/config --unset safe.bareRepository" && + git config --file outer-repo/bare-repo/config safe.bareRepository all && test_config_global safe.bareRepository explicit && expect_rejected -C outer-repo/bare-repo '
diff --git a/t/t0090-cache-tree.sh b/t/t0090-cache-tree.sh index d901588..0964718 100755 --- a/t/t0090-cache-tree.sh +++ b/t/t0090-cache-tree.sh
@@ -278,4 +278,12 @@ ) ' +test_expect_success 'cache-tree is used by write-tree when valid' ' + test_commit use-valid && + + # write-tree with a valid cache-tree should skip cache_tree_update + GIT_TRACE2_PERF="$(pwd)/trace.output" git write-tree && + test_grep ! region_enter.*cache_tree.*update trace.output +' + test_done
diff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh index 52e1972..dff442d 100755 --- a/t/t0410-partial-clone.sh +++ b/t/t0410-partial-clone.sh
@@ -717,7 +717,29 @@ git -C server rm foo.t && git -C server commit -m remove && git -C server config uploadpack.allowanysha1inwant 1 && - git -C server config uploadpack.allowfilter 1 + git -C server config uploadpack.allowfilter 1 && + + # Setup for submodule repo test: superproject whose submodule is a + # partial clone, so that promisor.quiet is read via a non-main repo. + rm -rf sub-pc-src sub-pc-srv.bare super-src super-work && + git init sub-pc-src && + test_commit -C sub-pc-src initial file.txt "hello" && + + git clone --bare sub-pc-src sub-pc-srv.bare && + git -C sub-pc-srv.bare config uploadpack.allowfilter 1 && + git -C sub-pc-srv.bare config uploadpack.allowanysha1inwant 1 && + + git init super-src && + git -C super-src -c protocol.file.allow=always \ + submodule add "file://$(pwd)/sub-pc-srv.bare" sub && + git -C super-src commit -m "add submodule" && + + git -c protocol.file.allow=always clone super-src super-work && + git -C super-work -c protocol.file.allow=always \ + submodule update --init --filter=blob:none sub && + + # Allow file:// in the submodule so that lazy-fetch subprocesses work. + git -C super-work/sub config protocol.file.allow always ' test_expect_success TTY 'promisor.quiet=false shows progress messages' ' @@ -752,6 +774,27 @@ grep "Receiving objects" err ' +test_expect_success 'promisor.quiet from submodule repo is honored' ' + rm -f pc-quiet-trace && + + # Set promisor.quiet only in the submodule, not the superproject. + git -C super-work/sub config promisor.quiet true && + + # Push a new commit+blob to the server; the blob stays missing in the + # partial-clone submodule until a lazy fetch is triggered. + test_commit -C sub-pc-src updated new-file.txt "world" && + git -C sub-pc-src push "$(pwd)/sub-pc-srv.bare" HEAD:master && + git -C super-work/sub -c protocol.file.allow=always fetch origin && + git -C super-work/sub reset --mixed origin/master && + + # grep descends into the submodule and triggers a lazy fetch for the + # missing blob; verify the fetch subprocess carries --quiet. + GIT_TRACE2_EVENT="$(pwd)/pc-quiet-trace" \ + git -C super-work grep --cached --recurse-submodules "world" \ + 2>/dev/null && + grep negotiationAlgorithm pc-quiet-trace | grep -e --quiet +' + . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh index 3c1f553..1325982 100755 --- a/t/t0602-reffiles-fsck.sh +++ b/t/t0602-reffiles-fsck.sh
@@ -87,6 +87,47 @@ ) ' +test_expect_success 'lock files should be ignored' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + git commit --allow-empty -m initial && + git checkout -b branch-1 && + + touch .git/refs/heads/branch-1.lock && + git refs verify 2>err && + test_must_be_empty err && + + echo "foobar" >.git/refs/heads/branch-2 && + test_must_fail git refs verify 2>err && + cat >expect <<-EOF && + error: refs/heads/branch-2: badRefContent: foobar + EOF + test_cmp expect err + ) +' + +test_expect_success 'bare lock files should not be ignored' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + git commit --allow-empty -m initial && + git checkout -b branch-1 && + + # invalid refname should be reported + cp .git/refs/heads/branch-1 .git/refs/heads/.branch-1.lock && + # invalid refname and content should be reported + touch .git/refs/heads/.lock && + + test_must_fail git refs verify 2>err && + test_grep "error: refs/heads/.branch-1.lock: badRefName: invalid refname format" err && + test_grep "error: refs/heads/.lock: badRefName: invalid refname format" err && + test_grep "error: refs/heads/.lock: badRefContent: " err + ) +' + test_expect_success 'ref name check should be adapted into fsck messages' ' test_when_finished "rm -rf repo" && git init repo &&
diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh index d98cb4a..8186da5 100755 --- a/t/t1092-sparse-checkout-compatibility.sh +++ b/t/t1092-sparse-checkout-compatibility.sh
@@ -2573,4 +2573,54 @@ ensure_not_expanded merge -s ours merge-right ' +test_expect_success 'restore --staged with sparse definition' ' + init_repos && + + # Stage changes within the sparse definition + test_all_match git checkout -b restore-staged-1 base && + test_all_match git reset --soft update-deep && + test_all_match git restore --staged . && + test_all_match git status --porcelain=v2 && + test_all_match git diff --cached +' + +test_expect_success 'restore --staged with outside sparse definition' ' + init_repos && + + # Stage changes that include paths outside the sparse definition. + # Although the working tree differs between full and sparse checkouts + # after restore, the state of the index should be the same. + test_all_match git checkout -b restore-staged-2 base && + test_all_match git reset --soft update-folder1 && + test_sparse_match git restore --staged . && + git -C full-checkout restore --staged . && + test_all_match git ls-files -s -- folder1 && + test_all_match git diff --cached -- folder1 +' + +test_expect_success 'restore --staged with wildcards' ' + init_repos && + + test_all_match git checkout -b restore-staged-3 base && + test_all_match git reset --soft update-deep && + test_all_match git restore --staged "deep/*" && + test_all_match git status --porcelain=v2 && + test_all_match git diff --cached +' + +test_expect_success 'sparse-index is not expanded: restore --staged' ' + init_repos && + + git -C sparse-index checkout -b restore-staged-exp base && + git -C sparse-index reset --soft update-folder1 && + ensure_not_expanded restore --staged . +' + +test_expect_success 'sparse-index is not expanded: restore --source --staged' ' + init_repos && + + git -C sparse-index checkout -b restore-source-staged base && + ensure_not_expanded restore --source update-folder1 --staged . +' + test_done
diff --git a/t/t1300-config.sh b/t/t1300-config.sh index 128971e..87ca11a 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh
@@ -11,6 +11,13 @@ . ./test-lib.sh . "$TEST_DIRECTORY"/lib-terminal.sh +# test-lib.sh may have added global config (e.g. safe.bareRepository) +# that would appear in "git config --list" output and break tests +# that expect exact config contents. +test_expect_success 'remove global config from test-lib.sh' ' + test_might_fail git config --global --unset-all safe.bareRepository +' + for mode in legacy subcommands do @@ -462,6 +469,62 @@ test_must_fail git config inval.2key blabla ' +test_expect_success 'set with 1 arg of "key=value": valid key suggests split form' ' + test_must_fail git config set pull.rebase=false 2>err && + test_grep "missing value to set to the variable .pull\\.rebase=false." err && + test_grep "did you mean .git config set pull\\.rebase false." err +' + +test_expect_success 'set with 1 arg of "key=value": implicit form suggests split form' ' + test_must_fail git config pull.rebase=false 2>err && + test_grep "missing value to set to the variable .pull\\.rebase=false." err && + test_grep "did you mean .git config set pull\\.rebase false." err +' + +test_expect_success 'set with 1 arg of "key=value": invalid key does not suggest split form' ' + test_must_fail git config set foo=bar 2>err && + test_grep "missing value to set to a variable with an invalid name .foo=bar." err && + test_grep ! "did you mean" err +' + +test_expect_success 'set with 1 arg: variable name starting with digit is invalid' ' + test_must_fail git config set foo.1bar=baz 2>err && + test_grep "missing value to set to a variable with an invalid name .foo\\.1bar=baz." err && + test_grep ! "did you mean" err +' + +test_expect_success 'set with 1 arg: digit-led section name is valid' ' + test_must_fail git config set 1foo.bar=baz 2>err && + test_grep "missing value to set to the variable .1foo\\.bar=baz." err && + test_grep "did you mean .git config set 1foo\\.bar baz." err +' + +test_expect_success 'set with 1 arg: subsection plus invalid variable name' ' + test_must_fail git config set foo.some.b_r=baz 2>err && + test_grep "missing value to set to a variable with an invalid name .foo\\.some\\.b_r=baz." err && + test_grep ! "did you mean" err +' + +test_expect_success 'set with 1 arg of valid key reports missing value' ' + test_must_fail git config set pull.rebase 2>err && + test_grep "missing value to set to the variable .pull\\.rebase." err && + test_grep ! "did you mean" err +' + +test_expect_success 'set with 2 args including "=" in invalid key does not suggest' ' + test_must_fail git config set pull.rebase=false true 2>err && + test_grep "invalid key: pull\\.rebase=false" err && + test_grep ! "did you mean" err +' + +test_expect_success '"=" inside subsection is valid' ' + test_when_finished "rm -f subsection.cfg" && + git config set -f subsection.cfg foo.bar=baz.boo qux && + echo qux >expect && + git config get -f subsection.cfg foo.bar=baz.boo >actual && + test_cmp expect actual +' + test_expect_success 'correct key' ' git config 123456.a123 987 '
diff --git a/t/t1301-shared-repo.sh b/t/t1301-shared-repo.sh index 630a47a..0e0d07a 100755 --- a/t/t1301-shared-repo.sh +++ b/t/t1301-shared-repo.sh
@@ -12,7 +12,7 @@ . ./test-lib.sh # Remove a default ACL from the test dir if possible. -setfacl -k . 2>/dev/null +setfacl -k . 2>/dev/null || : # User must have read permissions to the repo -> failure on --shared=0400 test_expect_success 'shared = 0400 (faulty permission u-w)' '
diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh index 6e51f89..f389257 100755 --- a/t/t1305-config-include.sh +++ b/t/t1305-config-include.sh
@@ -350,9 +350,9 @@ test_expect_success 'include cycles are detected' ' git init --bare cycle && - git -C cycle config include.path cycle && + git -C cycle --git-dir=. config include.path cycle && git config -f cycle/cycle include.path config && - test_must_fail git -C cycle config --get-all test.value 2>stderr && + test_must_fail git -C cycle --git-dir=. config --get-all test.value 2>stderr && grep "exceeded maximum include depth" stderr '
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index b2858a9..f059d63 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh
@@ -1196,6 +1196,20 @@ test_must_fail git rev-parse --verify -q $c ' +test_expect_success 'stdin -z create ref fails with non commit object' ' + printf $F "create $c" "$(test_oid 001)" >stdin && + test_must_fail git update-ref -z --stdin <stdin 2>err && + grep "fatal: trying to write ref ${SQ}$c${SQ} with nonexistent object" err && + test_must_fail git rev-parse --verify -q $c +' + +test_expect_success 'stdin -z update ref fails with non commit object' ' + printf $F "update $b" "$(test_oid 001)" "" >stdin && + test_must_fail git update-ref -z --stdin <stdin 2>err && + grep "fatal: trying to write ref ${SQ}$b${SQ} with nonexistent object" err && + test_must_fail git rev-parse --verify -q $c +' + test_expect_success 'stdin -z update ref works with right old value' ' printf $F "update $b" "$m~1" "$m" >stdin && git update-ref -z --stdin <stdin && @@ -1610,6 +1624,7 @@ ' test_expect_success PIPE 'transaction flushes status updates' ' + test_when_finished "rm -f in out" && mkfifo in out && (git update-ref --stdin <in >out &) &&
diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh index 98c5a77..38067d9 100755 --- a/t/t1500-rev-parse.sh +++ b/t/t1500-rev-parse.sh
@@ -337,6 +337,31 @@ test_cmp expect actual ' +test_expect_success 'rev-parse --bisect works with alternate terms' ' + test_commit_bulk 6 && + + git bisect start --term-old=known --term-new=curious && + + git update-ref refs/bisect/curious-1 HEAD~1 && + git update-ref refs/bisect/bad HEAD~2 && + git update-ref refs/bisect/curious-3 HEAD~3 && + git update-ref refs/bisect/known-3 HEAD~3 && + git update-ref refs/bisect/curious-4 HEAD~4 && + git update-ref refs/bisect/good HEAD~4 && + + # Note: refs/bisect/bad and refs/bisect/goood should be ignored because this + # is a bisect with custom terms (known/curious) + cat >expect <<-EOF && + refs/bisect/curious-1 + refs/bisect/curious-3 + refs/bisect/curious-4 + ^refs/bisect/known-3 + EOF + + git rev-parse --symbolic-full-name --bisect >actual && + test_cmp expect actual +' + test_expect_success '--short= truncates to the actual hash length' ' git rev-parse HEAD >expect && git rev-parse --short=100 HEAD >actual &&
diff --git a/t/t1517-outside-repo.sh b/t/t1517-outside-repo.sh index e1d3517..c557f2f 100755 --- a/t/t1517-outside-repo.sh +++ b/t/t1517-outside-repo.sh
@@ -122,7 +122,8 @@ archimport | citool | credential-netrc | credential-libsecret | \ credential-osxkeychain | cvsexportcommit | cvsimport | cvsserver | \ daemon | \ - difftool--helper | filter-branch | fsck-objects | get-tar-commit-id | \ + difftool--helper | filter-branch | format-rev | fsck-objects | \ + get-tar-commit-id | \ gui | gui--askpass | \ http-backend | http-fetch | http-push | init-db | \ merge-octopus | merge-one-file | merge-resolve | mergetool | \
diff --git a/t/t1700-split-index.sh b/t/t1700-split-index.sh index ac4a5b2..887e72a 100755 --- a/t/t1700-split-index.sh +++ b/t/t1700-split-index.sh
@@ -502,7 +502,7 @@ git checkout main && git update-index --split-index && test_commit more && - # must not write a new shareindex, or we wont catch the problem + # must not write a new shareindex, or we will not catch the problem git -c splitIndex.maxPercentChange=100 merge --no-edit side-branch 2>err && # i.e. do not expect warnings like # could not freshen shared index .../shareindex.00000...
diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh index 33decc6..0132e77 100755 --- a/t/t1800-hook.sh +++ b/t/t1800-hook.sh
@@ -21,6 +21,57 @@ test_when_finished rm -rf .git/hooks } +# write_sentinel_hook <path> [sentinel] +# +# Writes a hook that marks itself as started, sleeps for a few seconds, then +# marks itself done. The sleep must be long enough that sentinel_detector can +# observe <sentinel>.started before <sentinel>.done appears when both hooks +# run concurrently in parallel mode. +write_sentinel_hook () { + sentinel="${2:-sentinel}" + write_script "$1" <<-EOF + touch ${sentinel}.started && + sleep 2 && + touch ${sentinel}.done + EOF +} + +# sentinel_detector <sentinel> <output> +# +# Returns a shell command string suitable for use as hook.<name>.command. +# The detector must be registered after the sentinel: +# 1. In serial mode, the sentinel has completed (and <sentinel>.done exists) +# before the detector starts. +# 2. In parallel mode, both run concurrently so <sentinel>.done has not appeared +# yet and the detector just sees <sentinel>.started. +# +# At start, poll until <sentinel>.started exists to absorb startup jitter, then +# write to <output>: +# 1. 'serial' if <sentinel>.done exists (sentinel finished before we started), +# 2. 'parallel' if only <sentinel>.started exists (sentinel still running), +# 3. 'timeout' if <sentinel>.started never appeared. +# +# The command ends with ':' so when git appends "$@" for hooks that receive +# positional arguments (e.g. pre-push), the result ': "$@"' is valid shell +# rather than a syntax error 'fi "$@"'. +sentinel_detector () { + cat <<-EOF + i=0 + while ! test -f ${1}.started && test \$i -lt 10; do + sleep 1 + i=\$((i+1)) + done + if test -f ${1}.done; then + echo serial >${2} + elif test -f ${1}.started; then + echo parallel >${2} + else + echo timeout >${2} + fi + : + EOF +} + test_expect_success 'git hook usage' ' test_expect_code 129 git hook && test_expect_code 129 git hook run && @@ -217,10 +268,20 @@ ' test_hook_tty () { - cat >expect <<-\EOF - STDOUT TTY - STDERR TTY - EOF + expect_tty=$1 + shift + + if test "$expect_tty" != "no_tty"; then + cat >expect <<-\EOF + STDOUT TTY + STDERR TTY + EOF + else + cat >expect <<-\EOF + STDOUT NO TTY + STDERR NO TTY + EOF + fi test_when_finished "rm -rf repo" && git init repo && @@ -238,12 +299,21 @@ test_cmp expect repo/actual } -test_expect_success TTY 'git hook run: stdout and stderr are connected to a TTY' ' - test_hook_tty hook run pre-commit +test_expect_success TTY 'git hook run -j1: stdout and stderr are connected to a TTY' ' + # hooks running sequentially (-j1) are always connected to the tty for + # optimum real-time performance. + test_hook_tty tty hook run -j1 pre-commit +' + +test_expect_success TTY 'git hook run -jN: stdout and stderr are not connected to a TTY' ' + # Hooks are not connected to the tty when run in parallel, instead they + # output to a pipe through which run-command collects and de-interlaces + # their outputs, which then gets passed either to the tty or a sideband. + test_hook_tty no_tty hook run -j2 pre-commit ' test_expect_success TTY 'git commit: stdout and stderr are connected to a TTY' ' - test_hook_tty commit -m"B.new" + test_hook_tty tty commit -m"B.new" ' test_expect_success 'git hook list orders by config order' ' @@ -658,4 +728,504 @@ check_stdout_merged_to_stderr push-to-checkout ' +test_expect_success 'parallel hook output is not interleaved' ' + test_when_finished "rm -rf .git/hooks" && + + write_script .git/hooks/test-hook <<-EOF && + echo "Hook 1 Start" + sleep 1 + echo "Hook 1 End" + EOF + + test_config hook.hook-2.event test-hook && + test_config hook.hook-2.command \ + "echo \"Hook 2 Start\"; sleep 2; echo \"Hook 2 End\"" && + test_config hook.hook-2.parallel true && + test_config hook.hook-3.event test-hook && + test_config hook.hook-3.command \ + "echo \"Hook 3 Start\"; sleep 3; echo \"Hook 3 End\"" && + test_config hook.hook-3.parallel true && + + git hook run --allow-unknown-hook-name -j3 test-hook >out 2>err.parallel && + + # Verify Hook 1 output is grouped + sed -n "/Hook 1 Start/,/Hook 1 End/p" err.parallel >hook1_out && + test_line_count = 2 hook1_out && + + # Verify Hook 2 output is grouped + sed -n "/Hook 2 Start/,/Hook 2 End/p" err.parallel >hook2_out && + test_line_count = 2 hook2_out && + + # Verify Hook 3 output is grouped + sed -n "/Hook 3 Start/,/Hook 3 End/p" err.parallel >hook3_out && + test_line_count = 2 hook3_out +' + +test_expect_success 'git hook run -j1 runs hooks in series' ' + test_when_finished "rm -rf .git/hooks" && + + test_config hook.series-1.event "test-hook" && + test_config hook.series-1.command "echo 1" --add && + test_config hook.series-2.event "test-hook" && + test_config hook.series-2.command "echo 2" --add && + + mkdir -p .git/hooks && + write_script .git/hooks/test-hook <<-EOF && + echo 3 + EOF + + cat >expected <<-\EOF && + 1 + 2 + 3 + EOF + + git hook run --allow-unknown-hook-name -j1 test-hook 2>actual && + test_cmp expected actual +' + +test_expect_success 'git hook run -j2 runs hooks in parallel' ' + test_when_finished "rm -f sentinel.started sentinel.done hook.order" && + test_when_finished "rm -rf .git/hooks" && + + mkdir -p .git/hooks && + write_sentinel_hook .git/hooks/test-hook && + + test_config hook.hook-2.event test-hook && + test_config hook.hook-2.command \ + "$(sentinel_detector sentinel hook.order)" && + test_config hook.hook-2.parallel true && + + git hook run --allow-unknown-hook-name -j2 test-hook >out 2>err && + echo parallel >expect && + test_cmp expect hook.order +' + +test_expect_success 'git hook run -j2 overrides parallel=false' ' + test_when_finished "rm -f sentinel.started sentinel.done hook.order" && + test_config hook.hook-1.event test-hook && + test_config hook.hook-1.command \ + "touch sentinel.started; sleep 2; touch sentinel.done" && + # hook-1 intentionally has no parallel=true + test_config hook.hook-2.event test-hook && + test_config hook.hook-2.command \ + "$(sentinel_detector sentinel hook.order)" && + # hook-2 also has no parallel=true + + # -j2 overrides parallel=false; hooks run in parallel with a warning. + git hook run --allow-unknown-hook-name -j2 test-hook >out 2>err && + echo parallel >expect && + test_cmp expect hook.order +' + +test_expect_success 'git hook run -j2 warns for hooks not marked parallel=true' ' + test_config hook.hook-1.event test-hook && + test_config hook.hook-1.command "true" && + test_config hook.hook-2.event test-hook && + test_config hook.hook-2.command "true" && + # neither hook has parallel=true + + git hook run --allow-unknown-hook-name -j2 test-hook >out 2>err && + grep "hook .hook-1. is not marked as parallel=true" err && + grep "hook .hook-2. is not marked as parallel=true" err +' + +test_expect_success 'hook.jobs=1 config runs hooks in series' ' + test_when_finished "rm -f sentinel.started sentinel.done hook.order" && + + # Use two configured hooks so the execution order is deterministic: + # hook-1 (sentinel) is listed before hook-2 (detector), so hook-1 + # always runs first even in serial mode. + test_config hook.hook-1.event test-hook && + test_config hook.hook-1.command \ + "touch sentinel.started; sleep 2; touch sentinel.done" && + test_config hook.hook-2.event test-hook && + test_config hook.hook-2.command \ + "$(sentinel_detector sentinel hook.order)" && + + test_config hook.jobs 1 && + + git hook run --allow-unknown-hook-name test-hook >out 2>err && + echo serial >expect && + test_cmp expect hook.order +' + +test_expect_success 'hook.jobs=2 config runs hooks in parallel' ' + test_when_finished "rm -f sentinel.started sentinel.done hook.order" && + test_when_finished "rm -rf .git/hooks" && + + mkdir -p .git/hooks && + write_sentinel_hook .git/hooks/test-hook && + + test_config hook.hook-2.event test-hook && + test_config hook.hook-2.command \ + "$(sentinel_detector sentinel hook.order)" && + test_config hook.hook-2.parallel true && + + test_config hook.jobs 2 && + + git hook run --allow-unknown-hook-name test-hook >out 2>err && + echo parallel >expect && + test_cmp expect hook.order +' + +test_expect_success 'hook.<name>.parallel=true enables parallel execution' ' + test_when_finished "rm -f sentinel.started sentinel.done hook.order" && + test_config hook.hook-1.event test-hook && + test_config hook.hook-1.command \ + "touch sentinel.started; sleep 2; touch sentinel.done" && + test_config hook.hook-1.parallel true && + test_config hook.hook-2.event test-hook && + test_config hook.hook-2.command \ + "$(sentinel_detector sentinel hook.order)" && + test_config hook.hook-2.parallel true && + + test_config hook.jobs 2 && + + git hook run --allow-unknown-hook-name test-hook >out 2>err && + echo parallel >expect && + test_cmp expect hook.order +' + +test_expect_success 'hook.<name>.parallel=false (default) forces serial execution' ' + test_when_finished "rm -f sentinel.started sentinel.done hook.order" && + test_config hook.hook-1.event test-hook && + test_config hook.hook-1.command \ + "touch sentinel.started; sleep 2; touch sentinel.done" && + test_config hook.hook-2.event test-hook && + test_config hook.hook-2.command \ + "$(sentinel_detector sentinel hook.order)" && + + test_config hook.jobs 2 && + + git hook run --allow-unknown-hook-name test-hook >out 2>err && + echo serial >expect && + test_cmp expect hook.order +' + +test_expect_success 'one non-parallel hook forces the whole event to run serially' ' + test_when_finished "rm -f sentinel.started sentinel.done hook.order" && + test_config hook.hook-1.event test-hook && + test_config hook.hook-1.command \ + "touch sentinel.started; sleep 2; touch sentinel.done" && + test_config hook.hook-1.parallel true && + test_config hook.hook-2.event test-hook && + test_config hook.hook-2.command \ + "$(sentinel_detector sentinel hook.order)" && + # hook-2 has no parallel=true: should force serial for all + + test_config hook.jobs 2 && + + git hook run --allow-unknown-hook-name test-hook >out 2>err && + echo serial >expect && + test_cmp expect hook.order +' + +test_expect_success 'client hooks: pre-push parallel execution merges stdout to stderr' ' + test_when_finished "rm -rf remote-par stdout.actual stderr.actual" && + git init --bare remote-par && + git remote add origin-par remote-par && + test_commit par-commit && + mkdir -p .git/hooks && + setup_hooks pre-push && + test_config hook.jobs 2 && + git push origin-par HEAD:main >stdout.actual 2>stderr.actual && + check_stdout_merged_to_stderr pre-push +' + +test_expect_success 'client hooks: pre-push runs in parallel when hook.jobs > 1' ' + test_when_finished "rm -rf repo-parallel remote-parallel" && + git init --bare remote-parallel && + git init repo-parallel && + git -C repo-parallel remote add origin ../remote-parallel && + test_commit -C repo-parallel A && + + write_sentinel_hook repo-parallel/.git/hooks/pre-push && + git -C repo-parallel config hook.hook-2.event pre-push && + git -C repo-parallel config hook.hook-2.command \ + "$(sentinel_detector sentinel hook.order)" && + git -C repo-parallel config hook.hook-2.parallel true && + + git -C repo-parallel config hook.jobs 2 && + + git -C repo-parallel push origin HEAD >out 2>err && + echo parallel >expect && + test_cmp expect repo-parallel/hook.order +' + +test_expect_success 'hook.jobs=2 is ignored for force-serial hooks (pre-commit)' ' + test_when_finished "rm -f sentinel.started sentinel.done hook.order" && + test_config hook.hook-1.event pre-commit && + test_config hook.hook-1.command \ + "touch sentinel.started; sleep 2; touch sentinel.done" && + test_config hook.hook-1.parallel true && + test_config hook.hook-2.event pre-commit && + test_config hook.hook-2.command \ + "$(sentinel_detector sentinel hook.order)" && + test_config hook.hook-2.parallel true && + test_config hook.jobs 2 && + git commit --allow-empty -m "test: verify force-serial on pre-commit" && + echo serial >expect && + test_cmp expect hook.order +' + +test_expect_success 'hook.<event>.jobs overrides hook.jobs for that event' ' + test_when_finished "rm -f sentinel.started sentinel.done hook.order" && + test_config hook.hook-1.event test-hook && + test_config hook.hook-1.command \ + "touch sentinel.started; sleep 2; touch sentinel.done" && + test_config hook.hook-1.parallel true && + test_config hook.hook-2.event test-hook && + test_config hook.hook-2.command \ + "$(sentinel_detector sentinel hook.order)" && + test_config hook.hook-2.parallel true && + + # Global hook.jobs=1 (serial), but per-event override allows parallel. + test_config hook.jobs 1 && + test_config hook.test-hook.jobs 2 && + + git hook run --allow-unknown-hook-name test-hook >out 2>err && + echo parallel >expect && + test_cmp expect hook.order +' + +test_expect_success 'hook.<event>.jobs=1 forces serial even when hook.jobs>1' ' + test_when_finished "rm -f sentinel.started sentinel.done hook.order" && + test_config hook.hook-1.event test-hook && + test_config hook.hook-1.command \ + "touch sentinel.started; sleep 2; touch sentinel.done" && + test_config hook.hook-1.parallel true && + test_config hook.hook-2.event test-hook && + test_config hook.hook-2.command \ + "$(sentinel_detector sentinel hook.order)" && + test_config hook.hook-2.parallel true && + + # Global hook.jobs=4 allows parallel, but per-event override forces serial. + test_config hook.jobs 4 && + test_config hook.test-hook.jobs 1 && + + git hook run --allow-unknown-hook-name test-hook >out 2>err && + echo serial >expect && + test_cmp expect hook.order +' + +test_expect_success 'hook.<event>.jobs still requires hook.<name>.parallel=true' ' + test_when_finished "rm -f sentinel.started sentinel.done hook.order" && + test_config hook.hook-1.event test-hook && + test_config hook.hook-1.command \ + "touch sentinel.started; sleep 2; touch sentinel.done" && + # hook-1 intentionally has no parallel=true + test_config hook.hook-2.event test-hook && + test_config hook.hook-2.command \ + "$(sentinel_detector sentinel hook.order)" && + # hook-2 also has no parallel=true + + # Per-event jobs=2 but no hook has parallel=true: must still run serially. + test_config hook.test-hook.jobs 2 && + + git hook run --allow-unknown-hook-name test-hook >out 2>err && + echo serial >expect && + test_cmp expect hook.order +' + +test_expect_success 'hook.<friendly-name>.jobs warns when name has .command' ' + test_config hook.my-hook.command "true" && + test_config hook.my-hook.jobs 2 && + git hook run --allow-unknown-hook-name --ignore-missing test-hook >out 2>err && + test_grep "hook.my-hook.jobs.*friendly-name" err +' + +test_expect_success 'hook.<friendly-name>.jobs warns when name has .event' ' + test_config hook.my-hook.event test-hook && + test_config hook.my-hook.command "true" && + test_config hook.my-hook.jobs 2 && + git hook run --allow-unknown-hook-name --ignore-missing test-hook >out 2>err && + test_grep "hook.my-hook.jobs.*friendly-name" err +' + +test_expect_success 'hook.<friendly-name>.jobs warns when name has .parallel' ' + test_config hook.my-hook.event test-hook && + test_config hook.my-hook.command "true" && + test_config hook.my-hook.parallel true && + test_config hook.my-hook.jobs 2 && + git hook run --allow-unknown-hook-name --ignore-missing test-hook >out 2>err && + test_grep "hook.my-hook.jobs.*friendly-name" err +' + +test_expect_success 'hook.<event>.jobs does not warn for a real event name' ' + test_config hook.test-hook.jobs 2 && + git hook run --allow-unknown-hook-name --ignore-missing test-hook >out 2>err && + test_grep ! "friendly-name" err +' + +test_expect_success 'hook.jobs=-1 resolves to online_cpus()' ' + test_config hook.hook-1.event test-hook && + test_config hook.hook-1.command "true" && + test_config hook.hook-1.parallel true && + + test_config hook.jobs -1 && + + cpus=$(test-tool online-cpus) && + GIT_TRACE2_EVENT="$(pwd)/trace.txt" \ + git hook run --allow-unknown-hook-name test-hook >out 2>err && + grep "\"region_enter\".*\"hook\".*\"test-hook\".*\"max:$cpus\"" trace.txt +' + +test_expect_success 'hook.<event>.jobs=-1 resolves to online_cpus()' ' + test_config hook.hook-1.event test-hook && + test_config hook.hook-1.command "true" && + test_config hook.hook-1.parallel true && + + test_config hook.test-hook.jobs -1 && + + cpus=$(test-tool online-cpus) && + GIT_TRACE2_EVENT="$(pwd)/trace.txt" \ + git hook run --allow-unknown-hook-name test-hook >out 2>err && + grep "\"region_enter\".*\"hook\".*\"test-hook\".*\"max:$cpus\"" trace.txt +' + +test_expect_success 'git hook run -j-1 resolves to online_cpus()' ' + test_config hook.hook-1.event test-hook && + test_config hook.hook-1.command "true" && + test_config hook.hook-1.parallel true && + + cpus=$(test-tool online-cpus) && + GIT_TRACE2_EVENT="$(pwd)/trace.txt" \ + git hook run --allow-unknown-hook-name -j-1 test-hook >out 2>err && + grep "\"region_enter\".*\"hook\".*\"test-hook\".*\"max:$cpus\"" trace.txt +' + +test_expect_success 'hook.jobs rejects values less than -1' ' + test_config hook.jobs -2 && + git hook run --allow-unknown-hook-name --ignore-missing test-hook >out 2>err && + test_grep "hook.jobs must be a positive integer or -1" err +' + +test_expect_success 'hook.<event>.jobs rejects values less than -1' ' + test_config hook.test-hook.jobs -5 && + git hook run --allow-unknown-hook-name --ignore-missing test-hook >out 2>err && + test_grep "hook.test-hook.jobs must be a positive integer or -1" err +' + +test_expect_success 'hook.<event>.enabled=false skips all hooks for event' ' + test_config hook.hook-1.event test-hook && + test_config hook.hook-1.command "echo ran" && + test_config hook.test-hook.enabled false && + git hook run --allow-unknown-hook-name test-hook >out 2>err && + test_must_be_empty out +' + +test_expect_success 'hook.<event>.enabled=true does not suppress hooks' ' + test_config hook.hook-1.event test-hook && + test_config hook.hook-1.command "echo ran" && + test_config hook.test-hook.enabled true && + git hook run --allow-unknown-hook-name test-hook >out 2>err && + test_grep "ran" err +' + +test_expect_success 'hook.<event>.enabled=false does not affect other events' ' + test_config hook.hook-1.event test-hook && + test_config hook.hook-1.command "echo ran" && + test_config hook.other-event.enabled false && + git hook run --allow-unknown-hook-name test-hook >out 2>err && + test_grep "ran" err +' + +test_expect_success 'hook.<friendly-name>.enabled=false still disables that hook' ' + test_config hook.hook-1.event test-hook && + test_config hook.hook-1.command "echo hook-1" && + test_config hook.hook-2.event test-hook && + test_config hook.hook-2.command "echo hook-2" && + test_config hook.hook-1.enabled false && + git hook run --allow-unknown-hook-name test-hook >out 2>err && + test_grep ! "hook-1" err && + test_grep "hook-2" err +' + +test_expect_success 'git hook list shows event-disabled hooks as event-disabled' ' + test_config hook.hook-1.event test-hook && + test_config hook.hook-1.command "echo ran" && + test_config hook.hook-2.event test-hook && + test_config hook.hook-2.command "echo ran" && + test_config hook.test-hook.enabled false && + git hook list --allow-unknown-hook-name test-hook >actual && + test_grep "^event-disabled hook-1$" actual && + test_grep "^event-disabled hook-2$" actual +' + +test_expect_success 'git hook list shows scope with event-disabled' ' + test_config hook.hook-1.event test-hook && + test_config hook.hook-1.command "echo ran" && + test_config hook.test-hook.enabled false && + git hook list --allow-unknown-hook-name --show-scope test-hook >actual && + test_grep "^local event-disabled hook-1$" actual +' + +test_expect_success 'git hook list still shows hooks when event is disabled' ' + test_config hook.hook-1.event test-hook && + test_config hook.hook-1.command "echo ran" && + test_config hook.test-hook.enabled false && + git hook list --allow-unknown-hook-name test-hook >actual && + test_grep "event-disabled" actual +' + +test_expect_success 'friendly-name matching known event name is rejected' ' + test_config hook.pre-commit.event pre-commit && + test_config hook.pre-commit.command "echo oops" && + test_must_fail git hook run pre-commit 2>err && + test_grep "collides with a known event name" err +' + +test_expect_success 'friendly-name matching known event name is rejected even for different event' ' + test_config hook.pre-commit.event post-commit && + test_config hook.pre-commit.command "echo oops" && + test_must_fail git hook run post-commit 2>err && + test_grep "collides with a known event name" err +' + +test_expect_success 'friendly-name matching unknown event warns' ' + test_config hook.test-hook.event test-hook && + test_config hook.test-hook.command "echo ran" && + git hook run --allow-unknown-hook-name test-hook >out 2>err && + test_grep "same as its event" err +' + +test_expect_success 'hooks in parallel that do not read input' ' + # Add this to our $PATH to avoid having to write the whole trash + # directory into our config options, which would require quoting. + mkdir bin && + PATH=$PWD/bin:$PATH && + + write_script bin/hook-fast <<-\EOF && + # This hook does not read its input, so the parent process + # may see SIGPIPE if it is not ignored. It should happen + # relatively quickly. + exit 0 + EOF + + write_script bin/hook-slow <<-\EOF && + # This hook is slow, so we expect it to still be running + # when the other hook has exited (and the parent has a pipe error + # writing to it). + # + # So we want to be slow enough that we expect this to happen, but not + # so slow that the test takes forever. 1 second is probably enough + # in practice (and if it is occasionally not on a loaded system, we + # will err on the side of having the test pass). + sleep 1 + exit 0 + EOF + + git init --bare parallel.git && + git -C parallel.git config hook.fast.command "hook-fast" && + git -C parallel.git config hook.fast.event pre-receive && + git -C parallel.git config hook.fast.parallel true && + git -C parallel.git config hook.slow.command "hook-slow" && + git -C parallel.git config hook.slow.event pre-receive && + git -C parallel.git config hook.slow.parallel true && + git -C parallel.git config hook.jobs 2 && + + git push ./parallel.git "+refs/heads/*:refs/heads/*" +' + test_done
diff --git a/t/t2000-conflict-when-checking-files-out.sh b/t/t2000-conflict-when-checking-files-out.sh index af199d8..7b61370 100755 --- a/t/t2000-conflict-when-checking-files-out.sh +++ b/t/t2000-conflict-when-checking-files-out.sh
@@ -23,17 +23,6 @@ . ./test-lib.sh -show_files() { - # show filesystem files, just [-dl] for type and name - find path? -ls | - sed -e 's/^[0-9]* * [0-9]* * \([-bcdl]\)[^ ]* *[0-9]* *[^ ]* *[^ ]* *[0-9]* [A-Z][a-z][a-z] [0-9][0-9] [^ ]* /fs: \1 /' - # what's in the cache, just mode and name - git ls-files --stage | - sed -e 's/^\([0-9]*\) [0-9a-f]* [0-3] /ca: \1 /' - # what's in the tree, just mode and name. - git ls-tree -r "$1" | - sed -e 's/^\([0-9]*\) [^ ]* [0-9a-f]* /tr: \1 /' -} test_expect_success 'prepare files path0 and path1/file1' ' date >path0 && @@ -83,59 +72,21 @@ # path path3 is occupied by a non-directory. With "-f" it should remove # the symlink path3 and create directory path3 and file path3/file1. -test_expect_success 'prepare path2/file0 and index' ' +test_expect_success 'checkout-index -f resolves symlink conflict on leading path' ' mkdir path2 && date >path2/file0 && - git update-index --add path2/file0 -' - -test_expect_success 'write tree with path2/file0' ' - tree1=$(git write-tree) -' - -test_debug 'show_files $tree1' - -test_expect_success 'prepare path3/file1 and index' ' + git update-index --add path2/file0 && + tree1=$(git write-tree) && mkdir path3 && date >path3/file1 && - git update-index --add path3/file1 -' - -test_expect_success 'write tree with path3/file1' ' - tree2=$(git write-tree) -' - -test_debug 'show_files $tree2' - -test_expect_success 'read previously written tree and checkout.' ' + git update-index --add path3/file1 && + tree2=$(git write-tree) && rm -fr path3 && git read-tree -m $tree1 && - git checkout-index -f -a -' - -test_debug 'show_files $tree1' - -test_expect_success 'add a symlink' ' - test_ln_s_add path2 path3 -' - -test_expect_success 'write tree with symlink path3' ' - tree3=$(git write-tree) -' - -test_debug 'show_files $tree3' - -# Morten says "Got that?" here. -# Test begins. - -test_expect_success 'read previously written tree and checkout.' ' + git checkout-index -f -a && + test_ln_s_add path2 path3 && git read-tree $tree2 && - git checkout-index -f -a -' - -test_debug 'show_files $tree2' - -test_expect_success 'checking out conflicting path with -f' ' + git checkout-index -f -a && test_path_is_dir_not_symlink path2 && test_path_is_dir_not_symlink path3 && test_path_is_file_not_symlink path2/file0 &&
diff --git a/t/t3000-ls-files-others.sh b/t/t3000-ls-files-others.sh index b41e7f0..b4f0fbf 100755 --- a/t/t3000-ls-files-others.sh +++ b/t/t3000-ls-files-others.sh
@@ -53,16 +53,19 @@ test_expect_success 'ls-files --others' ' git ls-files --others >output && + test_filter_gitconfig output && test_cmp expected1 output ' test_expect_success 'ls-files --others --directory' ' git ls-files --others --directory >output && + test_filter_gitconfig output && test_cmp expected2 output ' test_expect_success '--no-empty-directory hides empty directory' ' git ls-files --others --directory --no-empty-directory >output && + test_filter_gitconfig output && test_cmp expected3 output ' @@ -70,6 +73,7 @@ mkdir not-a-submodule && echo foo >not-a-submodule/.git && git ls-files -o >output && + test_filter_gitconfig output && test_cmp expected1 output '
diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh index 4b67646..202fb8d 100755 --- a/t/t3001-ls-files-others-exclude.sh +++ b/t/t3001-ls-files-others-exclude.sh
@@ -72,6 +72,7 @@ --exclude-per-directory=.gitignore \ --exclude-from=.git/ignore \ >output && + test_filter_gitconfig output && test_cmp expect output ' @@ -84,6 +85,7 @@ --exclude-per-directory=.gitignore \ --exclude-from=.git/ignore \ >output && + test_filter_gitconfig output && test_cmp expect output ' @@ -99,6 +101,7 @@ --exclude-per-directory=.gitignore \ --exclude-from=.git/ignore \ >output && + test_filter_gitconfig output && test_cmp expect output '
diff --git a/t/t3002-ls-files-dashpath.sh b/t/t3002-ls-files-dashpath.sh index 31462cb..6acaadb 100755 --- a/t/t3002-ls-files-dashpath.sh +++ b/t/t3002-ls-files-dashpath.sh
@@ -24,6 +24,7 @@ test_expect_success 'git ls-files without path restriction.' ' test_when_finished "rm -f expect" && git ls-files --others >output && + test_filter_gitconfig output && cat >expect <<-\EOF && -- -foo @@ -63,6 +64,7 @@ test_expect_success 'git ls-files with no path restriction.' ' test_when_finished "rm -f expect" && git ls-files --others -- >output && + test_filter_gitconfig output && cat >expect <<-\EOF && -- -foo
diff --git a/t/t3009-ls-files-others-nonsubmodule.sh b/t/t3009-ls-files-others-nonsubmodule.sh index 963f346..dc990c2 100755 --- a/t/t3009-ls-files-others-nonsubmodule.sh +++ b/t/t3009-ls-files-others-nonsubmodule.sh
@@ -36,6 +36,7 @@ test_expect_success 'ls-files --others handles untracked git repositories' ' git ls-files -o >output && + test_filter_gitconfig output && cat >expect <<-EOF && nonrepo-untracked-file/untracked output
diff --git a/t/t3010-ls-files-killed-modified.sh b/t/t3010-ls-files-killed-modified.sh index 7af4532..6e38e10 100755 --- a/t/t3010-ls-files-killed-modified.sh +++ b/t/t3010-ls-files-killed-modified.sh
@@ -124,4 +124,22 @@ test_cmp .expected .output ' +test_expect_success 'worktree modes honor wildcard pathspecs' ' + cat >.expected <<-\EOF && + path2/file2 + path3/file3 + EOF + git ls-files --deleted -- "path?/file?" >.output && + test_cmp .expected .output && + + cat >.expected <<-\EOF && + path7 + path8 + EOF + git ls-files --modified --error-unmatch -- "path[78]" >.output && + test_cmp .expected .output && + + test_must_fail git ls-files --modified --error-unmatch -- path10 +' + test_done
diff --git a/t/t3011-common-prefixes-and-directory-traversal.sh b/t/t3011-common-prefixes-and-directory-traversal.sh index 3da5b2b..455e979 100755 --- a/t/t3011-common-prefixes-and-directory-traversal.sh +++ b/t/t3011-common-prefixes-and-directory-traversal.sh
@@ -26,7 +26,7 @@ ' test_expect_success 'git ls-files -o shows the right entries' ' - cat <<-EOF >expect && + cat >expect <<-EOF && .gitignore actual an_ignored_dir/ignored @@ -39,6 +39,7 @@ untracked_repo/ EOF git ls-files -o >actual && + test_filter_gitconfig actual && test_cmp expect actual '
diff --git a/t/t3070-wildmatch.sh b/t/t3070-wildmatch.sh index 655bb1a..3394122 100755 --- a/t/t3070-wildmatch.sh +++ b/t/t3070-wildmatch.sh
@@ -99,6 +99,13 @@ match_function=$4 ls_files_args=$5 + prereqs=EXPENSIVE_ON_WINDOWS + case "$pattern" in + *\\*) + prereqs="$prereqs,BSLASHPSPEC" + ;; + esac + match_stdout_stderr_cmp=" tr -d '\0' <actual.raw >actual && test_must_be_empty actual.err && @@ -108,36 +115,36 @@ then if test -e .git/created_test_file then - test_expect_success EXPENSIVE_ON_WINDOWS "$match_function (via ls-files): match dies on '$pattern' '$text'" " + test_expect_success $prereqs "$match_function (via ls-files): match dies on '$pattern' '$text'" " printf '%s' '$text' >expect && test_must_fail git$ls_files_args ls-files -z -- '$pattern' " else - test_expect_failure EXPENSIVE_ON_WINDOWS "$match_function (via ls-files): match skip '$pattern' '$text'" 'false' + test_expect_failure $prereqs "$match_function (via ls-files): match skip '$pattern' '$text'" 'false' fi elif test "$match_expect" = 1 then if test -e .git/created_test_file then - test_expect_success EXPENSIVE_ON_WINDOWS "$match_function (via ls-files): match '$pattern' '$text'" " + test_expect_success $prereqs "$match_function (via ls-files): match '$pattern' '$text'" " printf '%s' '$text' >expect && git$ls_files_args ls-files -z -- '$pattern' >actual.raw 2>actual.err && $match_stdout_stderr_cmp " else - test_expect_failure EXPENSIVE_ON_WINDOWS "$match_function (via ls-files): match skip '$pattern' '$text'" 'false' + test_expect_failure $prereqs "$match_function (via ls-files): match skip '$pattern' '$text'" 'false' fi elif test "$match_expect" = 0 then if test -e .git/created_test_file then - test_expect_success EXPENSIVE_ON_WINDOWS "$match_function (via ls-files): no match '$pattern' '$text'" " + test_expect_success $prereqs "$match_function (via ls-files): no match '$pattern' '$text'" " >expect && git$ls_files_args ls-files -z -- '$pattern' >actual.raw 2>actual.err && $match_stdout_stderr_cmp " else - test_expect_failure EXPENSIVE_ON_WINDOWS "$match_function (via ls-files): no match skip '$pattern' '$text'" 'false' + test_expect_failure $prereqs "$match_function (via ls-files): no match skip '$pattern' '$text'" 'false' fi else test_expect_success "PANIC: Test framework error. Unknown matches value $match_expect" 'false'
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 3e44562..58b3bb0 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh
@@ -1960,6 +1960,24 @@ ) ' +test_expect_success '--update-refs ignores non-branch decorations' ' + test_when_finished "git branch -D update-refs" && + test_when_finished "git checkout primary" && + git checkout -B update-refs no-conflict-branch && + ( + set_cat_todo_editor && + + # rebase.instructionFormat=%d loads normal log decorations before + # --update-refs adds its branch placeholders so we must ignore + # all non-local decorations. + test_must_fail git -c rebase.instructionFormat="%s%d" \ + rebase -i --update-refs HEAD^ >todo + ) && + grep ^update-ref todo >actual && + test_write_lines "update-ref refs/heads/no-conflict-branch" >expect && + test_cmp expect actual +' + test_expect_success '--update-refs updates refs correctly' ' git checkout -B update-refs no-conflict-branch && git branch -f base HEAD~4 &&
diff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh index ad3ba6a..f0bbc47 100755 --- a/t/t3420-rebase-autostash.sh +++ b/t/t3420-rebase-autostash.sh
@@ -61,18 +61,22 @@ First, rewinding head to replay your work on top of it... Applying: second commit Applying: third commit - Applying autostash resulted in conflicts. - Your changes are safe in the stash. - You can run "git stash pop" or "git stash drop" at any time. + Your local changes are stashed, however applying them + resulted in conflicts. You can either resolve the conflicts + and then discard the stash with "git stash drop", or, if you + do not want to resolve them now, run "git reset --hard" and + apply the local changes later by running "git stash pop". EOF } create_expected_failure_merge () { cat >expected <<-EOF $(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual) - Applying autostash resulted in conflicts. - Your changes are safe in the stash. - You can run "git stash pop" or "git stash drop" at any time. + Your local changes are stashed, however applying them + resulted in conflicts. You can either resolve the conflicts + and then discard the stash with "git stash drop", or, if you + do not want to resolve them now, run "git reset --hard" and + apply the local changes later by running "git stash pop". Successfully rebased and updated refs/heads/rebased-feature-branch. EOF }
diff --git a/t/t3453-history-fixup.sh b/t/t3453-history-fixup.sh new file mode 100755 index 0000000..868298e --- /dev/null +++ b/t/t3453-history-fixup.sh
@@ -0,0 +1,680 @@ +#!/bin/sh + +test_description='tests for git-history fixup subcommand' + +. ./test-lib.sh + +fixup_with_message () { + cat >message && + write_script fake-editor.sh <<-\EOF && + cp message "$1" + EOF + test_set_editor "$(pwd)"/fake-editor.sh && + git history fixup --reedit-message "$@" && + rm fake-editor.sh message +} + +expect_changes () { + git log --format="%s" --numstat "$@" >actual.raw && + sed '/^$/d' <actual.raw >actual && + cat >expect && + test_cmp expect actual +} + +test_expect_success 'errors on missing commit argument' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + test_must_fail git history fixup 2>err && + test_grep "command expects a single revision" err + ) +' + +test_expect_success 'errors on too many arguments' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + test_must_fail git history fixup HEAD HEAD 2>err && + test_grep "command expects a single revision" err + ) +' + +test_expect_success 'errors on unknown revision' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + test_must_fail git history fixup does-not-exist 2>err && + test_grep "commit cannot be found: does-not-exist" err + ) +' + +test_expect_success 'errors when nothing is staged' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + test_must_fail git history fixup HEAD 2>err && + test_grep "nothing to fixup: no staged changes" err + ) +' + +test_expect_success 'errors in a bare repository' ' + test_when_finished "rm -rf repo repo.git" && + git init repo && + test_commit -C repo initial && + git clone --bare repo repo.git && + test_must_fail git -C repo.git history fixup HEAD 2>err && + test_grep "cannot run fixup in a bare repository" err +' + +test_expect_success 'errors with invalid --empty= value' ' + test_when_finished "rm -rf repo" && + git init repo && + test_must_fail git -C repo history fixup --empty=bogus HEAD 2>err && + test_grep "unrecognized.*--empty.*bogus" err +' + +test_expect_success 'can fixup the tip commit' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + echo content >file.txt && + git add file.txt && + git commit -m "add file" && + + echo fix >>file.txt && + git add file.txt && + + expect_changes <<-\EOF && + add file + 1 0 file.txt + initial + 1 0 initial.t + EOF + + git symbolic-ref HEAD >branch-expect && + git history fixup HEAD && + git symbolic-ref HEAD >branch-actual && + test_cmp branch-expect branch-actual && + + expect_changes <<-\EOF && + add file + 2 0 file.txt + initial + 1 0 initial.t + EOF + + # Verify the fix is in the tip commit tree + git show HEAD:file.txt >actual && + printf "content\nfix\n" >expect && + test_cmp expect actual && + + git reflog >reflog && + test_grep "fixup: updating HEAD" reflog + ) +' + +test_expect_success 'can fixup a commit in the middle of history' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit first && + echo content >file.txt && + git add file.txt && + git commit -m "add file" && + test_commit third && + + echo fix >>file.txt && + git add file.txt && + + expect_changes <<-\EOF && + third + 1 0 third.t + add file + 1 0 file.txt + first + 1 0 first.t + EOF + + git history fixup HEAD~ && + + expect_changes <<-\EOF && + third + 1 0 third.t + add file + 2 0 file.txt + first + 1 0 first.t + EOF + + # Verify the fix landed in the "add file" commit. + git show HEAD~:file.txt >actual && + printf "content\nfix\n" >expect && + test_cmp expect actual && + + # And verify that the replayed commit also has the change. + git show HEAD:file.txt >actual && + printf "content\nfix\n" >expect && + test_cmp expect actual + ) +' + +test_expect_success 'can fixup root commit' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + echo initial >root.txt && + git add root.txt && + git commit -m "root" && + test_commit second && + + expect_changes <<-\EOF && + second + 1 0 second.t + root + 1 0 root.txt + EOF + + echo fix >>root.txt && + git add root.txt && + git history fixup HEAD~ && + + expect_changes <<-\EOF && + second + 1 0 second.t + root + 2 0 root.txt + EOF + + git show HEAD~:root.txt >actual && + printf "initial\nfix\n" >expect && + test_cmp expect actual + ) +' + +test_expect_success 'preserves commit message and authorship' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + echo content >file.txt && + git add file.txt && + git commit --author="Original <original@example.com>" -m "original message" && + + echo fix >>file.txt && + git add file.txt && + git history fixup HEAD && + + # Message preserved + git log -1 --format="%s" >actual && + echo "original message" >expect && + test_cmp expect actual && + + # Authorship preserved + git log -1 --format="%an <%ae>" >actual && + echo "Original <original@example.com>" >expect && + test_cmp expect actual + ) +' + +test_expect_success 'updates all descendant branches by default' ' + test_when_finished "rm -rf repo" && + git init repo --initial-branch=main && + ( + cd repo && + test_commit base && + git branch branch && + test_commit ours && + git switch branch && + test_commit theirs && + git switch main && + + echo fix >fix.txt && + git add fix.txt && + git history fixup base && + + expect_changes --branches <<-\EOF && + theirs + 1 0 theirs.t + ours + 1 0 ours.t + base + 1 0 base.t + 1 0 fix.txt + EOF + + # Both branches should have the fix in the base + git show main~:fix.txt >actual && + echo fix >expect && + test_cmp expect actual && + git show branch~:fix.txt >actual && + test_cmp expect actual + ) +' + +test_expect_success 'can fixup commit on a different branch' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit base && + git branch theirs && + test_commit ours && + git switch theirs && + test_commit theirs && + + # Stage a change while on "theirs" + echo fix >fix.txt && + git add fix.txt && + + # Ensure that "ours" does not change, as it does not contain + # the commit in question. + git rev-parse ours >ours-before && + git history fixup theirs && + git rev-parse ours >ours-after && + test_cmp ours-before ours-after && + + git show HEAD:fix.txt >actual && + echo fix >expect && + test_cmp expect actual + ) +' + +test_expect_success '--dry-run prints ref updates without modifying repo' ' + test_when_finished "rm -rf repo" && + git init repo --initial-branch=main && + ( + cd repo && + test_commit base && + git branch branch && + test_commit main-tip && + git switch branch && + test_commit branch-tip && + git switch main && + + echo fix >fix.txt && + git add fix.txt && + + git refs list >refs-before && + git history fixup --dry-run base >updates && + git refs list >refs-after && + test_cmp refs-before refs-after && + + test_grep "update refs/heads/main" updates && + test_grep "update refs/heads/branch" updates && + + expect_changes --branches <<-\EOF && + branch-tip + 1 0 branch-tip.t + main-tip + 1 0 main-tip.t + base + 1 0 base.t + EOF + + git update-ref --stdin <updates && + expect_changes --branches <<-\EOF + branch-tip + 1 0 branch-tip.t + main-tip + 1 0 main-tip.t + base + 1 0 base.t + 1 0 fix.txt + EOF + ) +' + +test_expect_success '--update-refs=head updates only HEAD' ' + test_when_finished "rm -rf repo" && + git init repo --initial-branch=main && + ( + cd repo && + test_commit base && + git branch branch && + test_commit main-tip && + git switch branch && + test_commit branch-tip && + + echo fix >fix.txt && + git add fix.txt && + + # Only HEAD (branch) should be updated + git history fixup --update-refs=head base && + + # The main branch should be unaffected. + expect_changes main <<-\EOF && + main-tip + 1 0 main-tip.t + base + 1 0 base.t + EOF + + # But the currently checked out branch should be modified. + expect_changes branch <<-\EOF + branch-tip + 1 0 branch-tip.t + base + 1 0 base.t + 1 0 fix.txt + EOF + ) +' + +test_expect_success '--update-refs=head refuses to rewrite commits not in HEAD ancestry' ' + test_when_finished "rm -rf repo" && + git init repo --initial-branch=main && + ( + cd repo && + test_commit base && + git branch other && + test_commit main-tip && + git switch other && + test_commit other-tip && + + echo fix >fix.txt && + git add fix.txt && + + test_must_fail git history fixup --update-refs=head main-tip 2>err && + test_grep "rewritten commit must be an ancestor of HEAD" err + ) +' + +test_expect_success 'aborts when fixup would produce conflicts' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + + echo "line one" >file.txt && + git add file.txt && + git commit -m "first" && + + echo "line two" >file.txt && + git add file.txt && + git commit -m "second" && + + echo "conflicting change" >file.txt && + git add file.txt && + + git refs list >refs-before && + test_must_fail git history fixup HEAD~ 2>err && + test_grep "fixup would produce conflicts" err && + git refs list >refs-after && + test_cmp refs-before refs-after + ) +' + +test_expect_success '--reedit-message opens editor for the commit message' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + echo content >file.txt && + git add file.txt && + git commit -m "add file" && + + echo fix >>file.txt && + git add file.txt && + + fixup_with_message HEAD <<-\EOF && + add file with fix + EOF + + expect_changes --branches <<-\EOF + add file with fix + 2 0 file.txt + initial + 1 0 initial.t + EOF + ) +' + +test_expect_success 'retains unstaged working tree changes after fixup' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + touch a b && + git add . && + git commit -m "initial commit" && + echo staged >a && + echo unstaged >b && + git add a && + git history fixup HEAD && + + # b is still modified in the worktree but not staged + cat >expect <<-\EOF && + M b + EOF + git status --porcelain --untracked-files=no >actual && + test_cmp expect actual + ) +' + +test_expect_success 'index is clean after fixup when target is HEAD' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + + test_commit initial && + echo fix >fix.txt && + git add fix.txt && + git history fixup HEAD && + + git status --porcelain --untracked-files=no >actual && + test_must_be_empty actual + ) +' + +test_expect_success 'index is unchanged on conflict' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + + echo base >file.txt && + git add file.txt && + git commit -m base && + echo change >file.txt && + git add file.txt && + git commit -m change && + + echo conflict >file.txt && + git add file.txt && + + git diff --cached >index-before && + test_must_fail git history fixup HEAD~ && + git diff --cached >index-after && + test_cmp index-before index-after + ) +' + +test_expect_success '--empty=drop removes target commit and replays descendants onto its parent' ' + test_when_finished "rm -rf repo" && + git init repo --initial-branch=main && + ( + cd repo && + + test_commit first && + test_commit second && + test_commit third && + + git rm second.t && + git history fixup --empty=drop HEAD~ && + + expect_changes <<-\EOF && + third + 1 0 third.t + first + 1 0 first.t + EOF + test_must_fail git show HEAD:second.t + ) +' + +test_expect_success '--empty=drop errors out when dropping the root commit' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + + test_commit first && + test_commit second && + + git rm first.t && + test_must_fail git history fixup --empty=drop HEAD~ 2>err && + test_grep "cannot drop root commit" err + ) +' + +test_expect_success '--empty=drop can drop the HEAD commit' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + + test_commit first && + test_commit second && + + git rm second.t && + git history fixup --empty=drop HEAD && + + expect_changes <<-\EOF + first + 1 0 first.t + EOF + ) +' + +test_expect_success '--empty=drop drops empty replayed commits' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + + touch base remove-me && + git add . && + git commit -m "base" && + git rm remove-me && + git commit -m "remove" && + touch reintroduce remove-me && + git add . && + git commit -m "reintroduce" && + + git rm remove-me && + git history fixup --empty=drop HEAD~2 && + + expect_changes <<-\EOF + reintroduce + 0 0 reintroduce + 0 0 remove-me + base + 0 0 base + EOF + ) +' + +test_expect_success '--empty=keep keeps commit when fixup target becomes empty' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + + test_commit first && + test_commit second && + test_commit third && + + git rm second.t && + git history fixup --empty=keep HEAD~ && + + expect_changes <<-\EOF + third + 1 0 third.t + second + first + 1 0 first.t + EOF + ) +' + +test_expect_success '--empty=keep keeps commit when replayed commit becomes empty' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + + touch base remove-me && + git add . && + git commit -m "base" && + git rm remove-me && + git commit -m "remove" && + touch reintroduce remove-me && + git add . && + git commit -m "reintroduce" && + + git rm remove-me && + git history fixup --empty=keep HEAD~2 && + + expect_changes <<-\EOF + reintroduce + 0 0 reintroduce + 0 0 remove-me + remove + base + 0 0 base + EOF + ) +' + +test_expect_success '--empty=abort errors out when fixup target becomes empty' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + + test_commit first && + test_commit second && + + git rm first.t && + test_must_fail git history fixup --empty=abort HEAD~ 2>err && + test_grep "fixup makes commit.*empty" err + ) +' + +test_expect_success '--empty=abort errors out when a descendant becomes empty during replay' ' + test_when_finished "rm -rf repo" && + git init repo --initial-branch=main && + ( + cd repo && + + touch base remove-me && + git add . && + git commit -m "base" && + git rm remove-me && + git commit -m "remove" && + touch reintroduce remove-me && + git add . && + git commit -m "reintroduce" && + + git rm remove-me && + test_must_fail git history fixup --empty=abort HEAD~2 2>err && + test_grep "became empty after replay" err + ) +' + +test_done
diff --git a/t/t3500-cherry.sh b/t/t3500-cherry.sh index 78c3eac..3e66827 100755 --- a/t/t3500-cherry.sh +++ b/t/t3500-cherry.sh
@@ -78,4 +78,31 @@ test_cmp expect actual ' +# Reuse the expect file from the previous test, in a partial clone +test_expect_success 'cherry in partial clone does bulk prefetch' ' + test_config uploadpack.allowfilter 1 && + test_config uploadpack.allowanysha1inwant 1 && + test_when_finished "rm -rf copy" && + + git clone --bare --filter=blob:none file://"$(pwd)" copy && + ( + cd copy && + GIT_TRACE2_EVENT="$(pwd)/trace.output" git cherry upstream-with-space feature-without-space >actual && + test_cmp ../expect actual && + + grep "child_start.*fetch.negotiationAlgorithm" trace.output >fetches && + test_line_count = 1 fetches && + test_trace2_data promisor fetch_count 4 <trace.output && + + # A second invocation should not refetch any blobs, since + # the prefetch is expected to filter out OIDs that are + # already present locally. + GIT_TRACE2_EVENT="$(pwd)/trace2.output" git cherry upstream-with-space feature-without-space >actual && + test_cmp ../expect actual && + + ! grep "child_start.*fetch.negotiationAlgorithm" trace2.output && + ! grep "\"key\":\"fetch_count\"" trace2.output + ) +' + test_done
diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index 1f16e6b..a371ea6 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh
@@ -260,7 +260,7 @@ test_expect_success !MINGW 'choking "git rm" should not let it die with cruft (induce and check SIGPIPE)' ' choke_git_rm_setup && - OUT=$( ((trap "" PIPE && git rm -n "some-file-*"; echo $? 1>&3) | :) 3>&1 ) && + OUT=$( ((trap "" PIPE && git rm -n "some-file-*" && echo 0 1>&3 || echo $? 1>&3) | :) 3>&1 ) && test_match_signal 13 "$OUT" && test_path_is_missing .git/index.lock '
diff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh index f03601b..ef7d7e1 100755 --- a/t/t3901-i18n-patch.sh +++ b/t/t3901-i18n-patch.sh
@@ -28,7 +28,8 @@ 8859) grep "^encoding ISO8859-1" ;; *) - grep "^encoding ISO8859-1"; test "$?" != 0 ;; + ret=0; grep "^encoding ISO8859-1" || ret=$? + test "$ret" != 0 ;; esac || return 1 j=$i i=$(($i+1))
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 7087994..ecc35aa 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh
@@ -56,6 +56,7 @@ git add other-file && test_tick && git commit -m initial && + git tag initial && echo 2 >file && git add file && echo 3 >file && @@ -1790,4 +1791,44 @@ test_cmp expect file ' +test_expect_success 'apply with custom conflict labels' ' + git reset --hard initial && + test_commit label-base conflict-file base-content && + echo stashed >conflict-file && + git stash push -m "stashed" && + test_commit label-upstream conflict-file upstream-content && + test_must_fail git -c merge.conflictStyle=diff3 stash apply --label-ours=UP --label-theirs=STASH && + test_grep "^<<<<<<< UP" conflict-file && + test_grep "^||||||| Stash base" conflict-file && + test_grep "^>>>>>>> STASH" conflict-file +' + +test_expect_success 'apply with empty conflict labels' ' + git reset --hard initial && + test_commit empty-label-base conflict-file base-content && + echo stashed >conflict-file && + git stash push -m "stashed" && + test_commit empty-label-upstream conflict-file upstream-content && + test_must_fail git stash apply --label-ours= --label-theirs= && + test_grep "^<<<<<<<$" conflict-file && + test_grep "^>>>>>>>$" conflict-file +' + +test_expect_success 'stash show --include-untracked includes untracked files' ' + git reset --hard && + + echo tracked >tracked && + git add tracked && + git commit -m "base" && + + echo change >>tracked && + echo untracked >untracked && + + git stash push --include-untracked && + test_path_is_missing untracked && + + git stash show --include-untracked >actual && + test_grep "untracked" actual +' + test_done
diff --git a/t/t3909-stash-pathspec-file.sh b/t/t3909-stash-pathspec-file.sh index 73f2dbd..e34cea6 100755 --- a/t/t3909-stash-pathspec-file.sh +++ b/t/t3909-stash-pathspec-file.sh
@@ -29,7 +29,7 @@ test_expect_success 'simplest' ' restore_checkpoint && - # More files are written to make sure that git didnt ignore + # More files are written to make sure that git did not ignore # --pathspec-from-file, stashing everything echo A >fileA.t && echo B >fileB.t && @@ -47,7 +47,7 @@ test_expect_success '--pathspec-file-nul' ' restore_checkpoint && - # More files are written to make sure that git didnt ignore + # More files are written to make sure that git did not ignore # --pathspec-from-file, stashing everything echo A >fileA.t && echo B >fileB.t && @@ -66,7 +66,7 @@ test_expect_success 'only touches what was listed' ' restore_checkpoint && - # More files are written to make sure that git didnt ignore + # More files are written to make sure that git did not ignore # --pathspec-from-file, stashing everything echo A >fileA.t && echo B >fileB.t &&
diff --git a/t/t4018/scheme-lisp-defun-a b/t/t4018/scheme-lisp-defun-a new file mode 100644 index 0000000..c3c750f --- /dev/null +++ b/t/t4018/scheme-lisp-defun-a
@@ -0,0 +1,4 @@ +(defun some-func (x y z) RIGHT + (let ((a x) + (b y)) + (ChangeMe a b)))
diff --git a/t/t4018/scheme-lisp-defun-b b/t/t4018/scheme-lisp-defun-b new file mode 100644 index 0000000..21be305 --- /dev/null +++ b/t/t4018/scheme-lisp-defun-b
@@ -0,0 +1,4 @@ +(macrolet ((foo (x) `(bar ,x))) + (defun mumble (x) ; RIGHT + (when (> x 0) + (foo x)))) ; ChangeMe
diff --git a/t/t4018/scheme-lisp-eval-when b/t/t4018/scheme-lisp-eval-when new file mode 100644 index 0000000..5d941d7 --- /dev/null +++ b/t/t4018/scheme-lisp-eval-when
@@ -0,0 +1,4 @@ +(eval-when (:compile-toplevel :load-toplevel :execute) ; RIGHT + (set-macro-character #\? + (lambda (stream char) + `(make-pattern-variable ,(read stream))))) ; ChangeMe
diff --git a/t/t4018/scheme-module b/t/t4018/scheme-module-a similarity index 100% rename from t/t4018/scheme-module rename to t/t4018/scheme-module-a
diff --git a/t/t4018/scheme-module-b b/t/t4018/scheme-module-b new file mode 100644 index 0000000..77bc0c5 --- /dev/null +++ b/t/t4018/scheme-module-b
@@ -0,0 +1,6 @@ +(module A + (export with-display-exception) + (extern (display-exception display-exception)) + (def (with-display-exception thunk) RIGHT + (with-catch (lambda (e) (display-exception e (current-error-port)) e) + thunk ChangeMe)))
diff --git a/t/t4032-diff-inter-hunk-context.sh b/t/t4032-diff-inter-hunk-context.sh index bada0cb..7d44396 100755 --- a/t/t4032-diff-inter-hunk-context.sh +++ b/t/t4032-diff-inter-hunk-context.sh
@@ -17,7 +17,7 @@ t() { use_config= - git config --unset diff.interHunkContext + git config --unset diff.interHunkContext || : case $# in 4) hunks=$4; cmd="diff -U$3";; @@ -40,11 +40,13 @@ test $(git $cmd $file | grep '^@@ ' | wc -l) = $hunks " - test -f $expected && - test_expect_success "$label: check output" " - git $cmd $file | grep -v '^index ' >actual && - test_cmp $expected actual - " + if test -f $expected + then + test_expect_success "$label: check output" " + git $cmd $file | grep -v '^index ' >actual && + test_cmp $expected actual + " + fi } cat <<EOF >expected.f1.0.1 || exit 1 @@ -114,4 +116,10 @@ test_must_fail git diff ' +test_expect_success '--inter-hunk-context rejects negative value' ' + test_unconfig diff.interHunkContext && + test_must_fail git diff --inter-hunk-context=-1 2>err && + test_grep "expects a non-negative integer" err +' + test_done
diff --git a/t/t4034/scheme/expect b/t/t4034/scheme/expect index 496cd5d..fb7f261 100644 --- a/t/t4034/scheme/expect +++ b/t/t4034/scheme/expect
@@ -2,10 +2,11 @@ <BOLD>index 74b6605..63b6ac4 100644<RESET> <BOLD>--- a/pre<RESET> <BOLD>+++ b/post<RESET> -<CYAN>@@ -1,6 +1,6 @@<RESET> +<CYAN>@@ -1,7 +1,7 @@<RESET> (define (<RED>myfunc a b<RESET><GREEN>my-func first second<RESET>) ; This is a <RED>really<RESET><GREEN>(moderately)<RESET> cool function. (<RED>this\place<RESET><GREEN>that\place<RESET> (+ 3 4)) - (define <RED>some-text<RESET><GREEN>|a greeting|<RESET> "hello") + (define <RED>|the \| \greeting|<RESET><GREEN>|a \greeting|<RESET> |hello there|) + ({<RED>}<RESET>(([<RED>]<RESET>(func-n)<RED>[<RESET>]))<RED>{<RESET>}) (let ((c (<RED>+ a b<RESET><GREEN>add1 first<RESET>))) (format "one more than the total is %d" (<RED>add1<RESET><GREEN>+<RESET> c <GREEN>second<RESET>))))
diff --git a/t/t4034/scheme/post b/t/t4034/scheme/post index 63b6ac4..450cc23 100644 --- a/t/t4034/scheme/post +++ b/t/t4034/scheme/post
@@ -1,6 +1,7 @@ (define (my-func first second) ; This is a (moderately) cool function. (that\place (+ 3 4)) - (define |a greeting| "hello") + (define |a \greeting| |hello there|) + ({(([(func-n)]))}) (let ((c (add1 first))) (format "one more than the total is %d" (+ c second))))
diff --git a/t/t4034/scheme/pre b/t/t4034/scheme/pre index 74b6605..e16ee75 100644 --- a/t/t4034/scheme/pre +++ b/t/t4034/scheme/pre
@@ -1,6 +1,7 @@ (define (myfunc a b) ; This is a really cool function. (this\place (+ 3 4)) - (define some-text "hello") + (define |the \| \greeting| |hello there|) + ({}(([](func-n)[])){}) (let ((c (+ a b))) (format "one more than the total is %d" (add1 c))))
diff --git a/t/t4052-stat-output.sh b/t/t4052-stat-output.sh index 7c74906..62d3d26 100755 --- a/t/t4052-stat-output.sh +++ b/t/t4052-stat-output.sh
@@ -420,7 +420,7 @@ # enough terminal display width, will contain the following line: # "<RED>|<RESET> ${FILENAME} | 0" # where "<RED>" and "<RESET>" are ANSI escape codes to color the text. -# To calculate the minimium terminal display width MIN_TERM_WIDTH so that the +# To calculate the minimum terminal display width MIN_TERM_WIDTH so that the # FILENAME in the diffstat will not be shortened, we take the FILENAME length # and add 9 to it. # To check if the diffstat width, when the line_prefix (the "<RED>|<RESET>" of @@ -445,4 +445,29 @@ test_grep "<RED>|<RESET> ${FILENAME_TRIMMED} | 0" out ' +test_expect_success 'diffstat truncation with invalid UTF-8 does not crash' ' + empty_blob=$(git hash-object -w --stdin </dev/null) && + printf "100644 blob $empty_blob\taaa-\300-aaa\n" | + git mktree >tree_file && + tree=$(cat tree_file) && + empty_tree=$(git mktree </dev/null) && + c1=$(git commit-tree -m before $empty_tree) && + c2=$(git commit-tree -m after -p $c1 $tree) && + git -c core.quotepath=false diff --stat --stat-name-width=5 $c1..$c2 >output && + test_grep "| 0" output +' + +test_expect_success FUNNYNAMES 'diffstat truncation with control chars does not read out of bounds' ' + FNAME=$(printf "aaa-\302\237\302\237\302\237-aaa") && + git commit --allow-empty -m setup && + >$FNAME && + git add -- $FNAME && + git commit -m "add file with control char name" && + git -c core.quotepath=false diff --stat --stat-name-width=5 HEAD~1..HEAD >output && + test_grep "| 0" output && + rm -- $FNAME && + git rm -- $FNAME && + git commit -m "remove test file" +' + test_done
diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh index 1384a81..b26f6ee 100755 --- a/t/t4055-diff-context.sh +++ b/t/t4055-diff-context.sh
@@ -82,6 +82,11 @@ test_grep "bad config variable" output ' +test_expect_success '-U-1 is rejected' ' + test_must_fail git diff -U-1 2>err && + test_grep "expects a non-negative integer" err +' + test_expect_success '-U0 is valid, so is diff.context=0' ' test_config diff.context 0 && git diff >output &&
diff --git a/t/t4067-diff-partial-clone.sh b/t/t4067-diff-partial-clone.sh index 3081310..a9dec84 100755 --- a/t/t4067-diff-partial-clone.sh +++ b/t/t4067-diff-partial-clone.sh
@@ -159,7 +159,7 @@ # We need baz to trigger break-rewrites detection. git -C client reset --hard HEAD && - # break-rewrites detction in reset. + # break-rewrites detection in reset. git -C client reset HEAD~1 '
diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 05cee9e..75edb0e 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh
@@ -1882,6 +1882,46 @@ test_cmp_graph --name-status tangle..reach ' +test_expect_success 'log --max-count-oldest=3 --oneline' ' + test_when_finished rm expect && + git log --oneline | tail -n3 >expect && + git log --oneline --max-count-oldest=3 >actual && + test_cmp expect actual +' + +test_expect_success 'log --max-count-oldest=3 --reverse --oneline' ' + test_when_finished rm expect && + git log --oneline --reverse | head -n3 >expect && + git log --oneline --max-count-oldest=3 --reverse >actual && + test_cmp expect actual +' + +test_expect_success 'log --max-count-oldest with --max-count' ' + test_when_finished rm stderr && + test_must_fail git log --max-count-oldest=3 --max-count=3 2>stderr && + test_grep "cannot be used together" stderr +' + +test_expect_success 'log --max-count-oldest with --skip' ' + test_when_finished rm stderr && + test_must_fail git log --max-count-oldest=3 --skip=1 2>stderr && + test_grep "cannot be used together" stderr +' + +test_expect_success 'log --max-count-oldest=1000 --graph --boundary' ' + test_when_finished rm expect actual && + git log --graph --boundary >expect && + git log --max-count-oldest=1000 --graph --boundary >actual && + test_cmp expect actual +' + +test_expect_success 'log --oneline --graph --boundary --max-count-oldest=1' ' + test_when_finished rm -f actual && + git log --oneline --graph --boundary --max-count-oldest=1 \ + HEAD~1..HEAD >actual && + test_line_count = 2 actual +' + cat >expect <<-\EOF * reach |
diff --git a/t/t4203-mailmap.sh b/t/t4203-mailmap.sh index 74b7ddc..c9817c1 100755 --- a/t/t4203-mailmap.sh +++ b/t/t4203-mailmap.sh
@@ -180,7 +180,7 @@ git shortlog HEAD >actual && test_cmp expect actual && - # The internal_mailmap/.mailmap file is an a subdirectory, but + # The internal_mailmap/.mailmap file is in a subdirectory, but # as shown here it can also be outside the repository test_when_finished "rm -rf sub-repo" && git clone . sub-repo && @@ -1133,6 +1133,111 @@ test_cmp expect actual ' +test_expect_success 'git cat-file --batch-command mailmap yes enables mailmap mid-stream' ' + test_when_finished "rm .mailmap" && + cat >.mailmap <<-\EOF && + C O Mitter <committer@example.com> Orig <orig@example.com> + EOF + commit_sha=$(git rev-parse HEAD) && + git cat-file commit HEAD >commit_no_mailmap.out && + git cat-file --use-mailmap commit HEAD >commit_mailmap.out && + size_no_mailmap=$(wc -c <commit_no_mailmap.out) && + size_mailmap=$(wc -c <commit_mailmap.out) && + printf "info HEAD\nmailmap yes\ninfo HEAD\n" | git cat-file --batch-command >actual && + echo $commit_sha commit $size_no_mailmap >expect && + echo $commit_sha commit $size_mailmap >>expect && + test_cmp expect actual +' + +test_expect_success 'git cat-file --batch-command mailmap no disables mailmap mid-stream' ' + test_when_finished "rm .mailmap" && + cat >.mailmap <<-\EOF && + C O Mitter <committer@example.com> Orig <orig@example.com> + EOF + commit_sha=$(git rev-parse HEAD) && + git cat-file commit HEAD >commit_no_mailmap.out && + git cat-file --use-mailmap commit HEAD >commit_mailmap.out && + size_no_mailmap=$(wc -c <commit_no_mailmap.out) && + size_mailmap=$(wc -c <commit_mailmap.out) && + printf "mailmap yes\ninfo HEAD\nmailmap no\ninfo HEAD\n" | git cat-file --batch-command >actual && + echo $commit_sha commit $size_mailmap >expect && + echo $commit_sha commit $size_no_mailmap >>expect && + test_cmp expect actual +' + +test_expect_success 'git cat-file --batch-command mailmap works in --buffer mode' ' + test_when_finished "rm .mailmap" && + cat >.mailmap <<-\EOF && + C O Mitter <committer@example.com> Orig <orig@example.com> + EOF + commit_sha=$(git rev-parse HEAD) && + git cat-file commit HEAD >commit_no_mailmap.out && + git cat-file --use-mailmap commit HEAD >commit_mailmap.out && + size_no_mailmap=$(wc -c <commit_no_mailmap.out) && + size_mailmap=$(wc -c <commit_mailmap.out) && + printf "mailmap yes\ninfo HEAD\nmailmap no\ninfo HEAD\nflush\n" | git cat-file --batch-command --buffer >actual && + echo $commit_sha commit $size_mailmap >expect && + echo $commit_sha commit $size_no_mailmap >>expect && + test_cmp expect actual +' + +test_expect_success 'git cat-file --batch-command mailmap no overrides startup --mailmap' ' + test_when_finished "rm .mailmap" && + cat >.mailmap <<-\EOF && + C O Mitter <committer@example.com> Orig <orig@example.com> + EOF + commit_sha=$(git rev-parse HEAD) && + git cat-file --use-mailmap commit HEAD >commit_mailmap.out && + size_mailmap=$(wc -c <commit_mailmap.out) && + git cat-file commit HEAD >commit_no_mailmap.out && + size_no_mailmap=$(wc -c <commit_no_mailmap.out) && + printf "info HEAD\nmailmap no\ninfo HEAD\n" | \ + git cat-file --mailmap --batch-command >actual && + echo $commit_sha commit $size_mailmap >expect && + echo $commit_sha commit $size_no_mailmap >>expect && + test_cmp expect actual +' + +test_expect_success 'git cat-file --batch-command mailmap yes overrides startup --no-mailmap' ' + test_when_finished "rm .mailmap" && + cat >.mailmap <<-\EOF && + C O Mitter <committer@example.com> Orig <orig@example.com> + EOF + commit_sha=$(git rev-parse HEAD) && + git cat-file commit HEAD >commit_no_mailmap.out && + size_no_mailmap=$(wc -c <commit_no_mailmap.out) && + git cat-file --use-mailmap commit HEAD >commit_mailmap.out && + size_mailmap=$(wc -c <commit_mailmap.out) && + printf "info HEAD\nmailmap yes\ninfo HEAD\n" | \ + git cat-file --no-mailmap --batch-command >actual && + echo $commit_sha commit $size_no_mailmap >expect && + echo $commit_sha commit $size_mailmap >>expect && + test_cmp expect actual +' + +test_expect_success 'git cat-file --batch-command mailmap accepts true/false' ' + test_when_finished "rm .mailmap" && + cat >.mailmap <<-\EOF && + C O Mitter <committer@example.com> Orig <orig@example.com> + EOF + commit_sha=$(git rev-parse HEAD) && + git cat-file commit HEAD >commit_no_mailmap.out && + size_no_mailmap=$(wc -c <commit_no_mailmap.out) && + git cat-file --use-mailmap commit HEAD >commit_mailmap.out && + size_mailmap=$(wc -c <commit_mailmap.out) && + printf "mailmap true\ninfo HEAD\nmailmap false\ninfo HEAD\n" | \ + git cat-file --batch-command >actual && + echo $commit_sha commit $size_mailmap >expect && + echo $commit_sha commit $size_no_mailmap >>expect && + test_cmp expect actual +' + +test_expect_success 'git cat-file --batch-command mailmap rejects invalid boolean' ' + echo "mailmap maybe" >in && + test_must_fail git cat-file --batch-command <in 2>err && + test_grep "mailmap: invalid boolean .*maybe" err +' + test_expect_success 'git cat-file --mailmap works with different author and committer' ' test_when_finished "rm .mailmap" && cat >.mailmap <<-\EOF &&
diff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh index aaf197d..ca4eb7b 100755 --- a/t/t4211-line-log.sh +++ b/t/t4211-line-log.sh
@@ -155,8 +155,45 @@ test_cmp expect actual ' -test_expect_success '--raw is forbidden' ' - test_must_fail git log -L1,24:b.c --raw +test_expect_success '--raw shows mode, oid, status and path' ' + git log -L1,24:b.c --raw --format= >actual && + test_grep "^:100644 100644 [0-9a-f]\{7\} [0-9a-f]\{7\} M b.c$" actual && + test_grep ! "^diff --git" actual && + test_grep ! "^@@" actual +' + +test_expect_success '--name-only shows path' ' + git log -L1,24:b.c --name-only --format= >actual && + test_grep "^b.c$" actual && + test_grep ! "^diff --git" actual && + test_grep ! "^@@" actual +' + +test_expect_success '--name-status shows status and path' ' + git log -L1,24:b.c --name-status --format= >actual && + test_grep "^M b.c$" actual && + test_grep ! "^diff --git" actual && + test_grep ! "^@@" actual +' + +test_expect_success '--stat is not yet supported with -L' ' + test_must_fail git log -L1,24:b.c --stat 2>err && + test_grep "does not yet support" err +' + +test_expect_success '--numstat is not yet supported with -L' ' + test_must_fail git log -L1,24:b.c --numstat 2>err && + test_grep "does not yet support" err +' + +test_expect_success '--shortstat is not yet supported with -L' ' + test_must_fail git log -L1,24:b.c --shortstat 2>err && + test_grep "does not yet support" err +' + +test_expect_success '--dirstat is not yet supported with -L' ' + test_must_fail git log -L1,24:b.c --dirstat 2>err && + test_grep "does not yet support" err ' test_expect_success 'setup for checking fancy rename following' ' @@ -368,7 +405,6 @@ test_expect_success '-L with --word-diff' ' cat >expect <<-\EOF && - diff --git a/file.c b/file.c --- a/file.c +++ b/file.c @@ -377,7 +413,6 @@ { return [-F2;-]{+F2 + 2;+} } - diff --git a/file.c b/file.c new file mode 100644 --- /dev/null @@ -433,7 +468,6 @@ null_blob=$(test_oid zero | cut -c1-7) && qz_to_tab_space >expect <<-EOF && * $head_oid Modify func2() in file.c - |Z | diff --git a/file.c b/file.c | index $head_blob_old..$head_blob_new 100644 | --- a/file.c @@ -445,7 +479,6 @@ | + return F2 + 2; | } * $root_oid Add func1() and func2() in file.c - ZZ diff --git a/file.c b/file.c new file mode 100644 index $null_blob..$root_blob @@ -494,23 +527,17 @@ --find-object=$(git rev-parse HEAD:file) >actual ' -# Commit-level filtering with pickaxe does not yet work for -L. -# show_log() prints the commit header before diffcore_std() runs -# pickaxe, so commits cannot be suppressed even when no diff pairs -# survive filtering. Fixing this would require deferring show_log() -# until after diffcore_std(), which is a larger restructuring of the -# log-tree output pipeline. -test_expect_failure '-L -G should filter commits by pattern' ' +test_expect_success '-L -G should filter commits by pattern' ' git log --format="%s" --no-patch -L 1,1:file -G "nomatch" >actual && test_must_be_empty actual ' -test_expect_failure '-L -S should filter commits by pattern' ' +test_expect_success '-L -S should filter commits by pattern' ' git log --format="%s" --no-patch -L 1,1:file -S "nomatch" >actual && test_must_be_empty actual ' -test_expect_failure '-L --find-object should filter commits by object' ' +test_expect_success '-L --find-object should filter commits by object' ' git log --format="%s" --no-patch -L 1,1:file \ --find-object=$ZERO_OID >actual && test_must_be_empty actual @@ -711,4 +738,47 @@ grep "F2 + 2" actual ' +test_expect_success '-L with --diff-filter=M excludes root commit' ' + git checkout parent-oids && + git log -L:func2:file.c --diff-filter=M --format=%s --no-patch >actual && + # Root commit is an Add (A), not a Modify (M), so it should + # be excluded; only the modification commit remains. + echo "Modify func2() in file.c" >expect && + test_cmp expect actual +' + +test_expect_success '-L with --diff-filter=A shows only root commit' ' + git checkout parent-oids && + git log -L:func2:file.c --diff-filter=A --format=%s --no-patch >actual && + echo "Add func1() and func2() in file.c" >expect && + test_cmp expect actual +' + +test_expect_success '-L with -S suppresses non-matching commits' ' + git checkout parent-oids && + git log -L:func2:file.c -S "F2 + 2" --format=%s --no-patch >actual && + # Only the commit that changes the count of "F2 + 2" should appear. + echo "Modify func2() in file.c" >expect && + test_cmp expect actual +' + +test_expect_success '--full-diff is not yet supported with -L' ' + test_must_fail git log -L1,24:b.c --full-diff 2>err && + test_grep "does not yet support" err +' + +test_expect_success '-L --oneline has no extra blank line before diff' ' + git checkout parent-oids && + git log --oneline -L:func2:file.c -1 >actual && + # Oneline header on line 1, diff starts immediately on line 2 + sed -n 2p actual >line2 && + test_grep "^diff --git" line2 +' + +test_expect_success '--summary shows new file on root commit' ' + git checkout parent-oids && + git log -L:func2:file.c --summary --format= >actual && + test_grep "create mode 100644 file.c" actual +' + test_done
diff --git a/t/t4211/sha1/expect.parallel-change-f-to-main b/t/t4211/sha1/expect.parallel-change-f-to-main index 65a8cc6..6d7a201 100644 --- a/t/t4211/sha1/expect.parallel-change-f-to-main +++ b/t/t4211/sha1/expect.parallel-change-f-to-main
@@ -5,7 +5,6 @@ Merge across the rename - commit 6ce3c4ff690136099bb17e1a8766b75764726ea7 Author: Thomas Rast <trast@student.ethz.ch> Date: Thu Feb 28 10:49:50 2013 +0100
diff --git a/t/t4211/sha256/expect.parallel-change-f-to-main b/t/t4211/sha256/expect.parallel-change-f-to-main index 3178989..c93e03b 100644 --- a/t/t4211/sha256/expect.parallel-change-f-to-main +++ b/t/t4211/sha256/expect.parallel-change-f-to-main
@@ -5,7 +5,6 @@ Merge across the rename - commit 4f7a58195a92c400e28a2354328587f1ff14fb77f5cf894536f17ccbc72931b9 Author: Thomas Rast <trast@student.ethz.ch> Date: Thu Feb 28 10:49:50 2013 +0100
diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh index 28d0779..1612f05 100755 --- a/t/t4215-log-skewed-merges.sh +++ b/t/t4215-log-skewed-merges.sh
@@ -370,4 +370,148 @@ EOF ' +test_expect_success 'log --graph --graph-lane-limit=2 limited to two lanes' ' + check_graph --graph-lane-limit=2 M_7 <<-\EOF + *-. 7_M4 + |\ \ + | | * 7_G + | | * 7_F + | * ~ 7_E + | * ~ 7_D + * | ~ 7_C + | |/ + |/| + * | 7_B + |/ + * 7_A + EOF +' + +test_expect_success 'log --graph --graph-lane-limit=1 truncate mid octopus merge' ' + check_graph --graph-lane-limit=1 M_7 <<-\EOF + *-~ 7_M4 + |\~ + | ~ 7_G + | ~ 7_F + | * 7_E + | * 7_D + * ~ 7_C + | ~ + |/~ + * ~ 7_B + |/ + * 7_A + EOF +' + +test_expect_success 'log --graph --graph-lane-limit=3 limited to three lanes' ' + check_graph --graph-lane-limit=3 M_1 M_3 M_5 M_7 <<-\EOF + * 7_M1 + |\ + | | * 7_M2 + | | |\ + | | | * 7_H + | | | ~ 7_M3 + | | | ~ 7_J + | | | ~ 7_I + | | | ~ 7_M4 + | |_|_~ + |/| | ~ + | | |_~ + | |/| ~ + | | | ~ + | | |/~ + | | * ~ 7_G + | | | ~ + | | |/~ + | | * ~ 7_F + | * | ~ 7_E + | | |/~ + | |/| ~ + | * | ~ 7_D + | | |/ + | |/| + * | | 7_C + | |/ + |/| + * | 7_B + |/ + * 7_A + EOF +' + +test_expect_success 'log --graph --graph-lane-limit=6 check if it only shows first of 3 parent merge' ' + check_graph --graph-lane-limit=6 M_1 M_3 M_5 M_7 <<-\EOF + * 7_M1 + |\ + | | * 7_M2 + | | |\ + | | | * 7_H + | | | | * 7_M3 + | | | | |\ + | | | | | * 7_J + | | | | * | 7_I + | | | | | | * 7_M4 + | |_|_|_|_|/~ + |/| | | | |/~ + | | |_|_|/| ~ + | |/| | | |/ + | | | |_|/| + | | |/| | | + | | * | | | 7_G + | | | |_|/ + | | |/| | + | | * | | 7_F + | * | | | 7_E + | | |/ / + | |/| | + | * | | 7_D + | | |/ + | |/| + * | | 7_C + | |/ + |/| + * | 7_B + |/ + * 7_A + EOF +' + +test_expect_success 'log --graph --graph-lane-limit=7 check if it shows all 3 parent merge' ' + check_graph --graph-lane-limit=7 M_1 M_3 M_5 M_7 <<-\EOF + * 7_M1 + |\ + | | * 7_M2 + | | |\ + | | | * 7_H + | | | | * 7_M3 + | | | | |\ + | | | | | * 7_J + | | | | * | 7_I + | | | | | | * 7_M4 + | |_|_|_|_|/|\ + |/| | | | |/ / + | | |_|_|/| / + | |/| | | |/ + | | | |_|/| + | | |/| | | + | | * | | | 7_G + | | | |_|/ + | | |/| | + | | * | | 7_F + | * | | | 7_E + | | |/ / + | |/| | + | * | | 7_D + | | |/ + | |/| + * | | 7_C + | |/ + |/| + * | 7_B + |/ + * 7_A + EOF +' + test_done
diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index 5465054..a8c2853 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh
@@ -503,8 +503,8 @@ # would generate the whole 64GB). test_expect_success LONG_IS_64BIT 'generate tar with huge size' ' { - git archive HEAD - echo $? >exit-code + { ret=0 && git archive HEAD || ret=$?; } && + echo "$ret" >exit-code } | test_copy_bytes 4096 >huge.tar && echo 141 >expect && test_cmp expect exit-code
diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh index f693cb5..efeb715 100755 --- a/t/t5310-pack-bitmaps.sh +++ b/t/t5310-pack-bitmaps.sh
@@ -648,4 +648,28 @@ test_grep corrupted.bitmap.index stderr ' +test_expect_success 'test-tool bitmap write determines bitmap selection' ' + test_when_finished "rm -fr bitmap-write-helper" && + git init bitmap-write-helper && + ( + cd bitmap-write-helper && + + test_commit_bulk 64 && + git repack -ad && + + pack="$(ls .git/objects/pack/pack-*.pack)" && + + git rev-parse HEAD >in && + test-tool bitmap write "$(basename $pack)" <in && + + test-tool bitmap list-commits >bitmaps.raw && + sort bitmaps.raw >bitmaps && + test_cmp in bitmaps && + + git rev-list --count --objects --use-bitmap-index HEAD >actual && + git rev-list --count --objects HEAD >expect && + test_cmp expect actual + ) +' + test_done
diff --git a/t/t5317-pack-objects-filter-objects.sh b/t/t5317-pack-objects-filter-objects.sh index 501d715..dddb79b 100755 --- a/t/t5317-pack-objects-filter-objects.sh +++ b/t/t5317-pack-objects-filter-objects.sh
@@ -478,4 +478,129 @@ EOF ' +# Test that --path-walk produces the same object set as standard traversal +# when using sparse:oid filters with cone-mode patterns. +# +# The sparse:oid filter restricts only blobs, not trees. Both standard +# and path-walk should produce identical sets of blobs, commits, and trees. + +test_expect_success 'setup pw_sparse for path-walk comparison' ' + git init pw_sparse && + mkdir -p pw_sparse/inc/sub pw_sparse/exc/sub && + + for n in 1 2 + do + echo "inc $n" >pw_sparse/inc/file$n && + echo "inc sub $n" >pw_sparse/inc/sub/file$n && + echo "exc $n" >pw_sparse/exc/file$n && + echo "exc sub $n" >pw_sparse/exc/sub/file$n && + echo "root $n" >pw_sparse/root$n || return 1 + done && + + git -C pw_sparse add . && + git -C pw_sparse commit -m "first" && + + echo "inc 1 modified" >pw_sparse/inc/file1 && + echo "exc 1 modified" >pw_sparse/exc/file1 && + echo "root 1 modified" >pw_sparse/root1 && + git -C pw_sparse add . && + git -C pw_sparse commit -m "second" && + + # Cone-mode sparse pattern: include root + inc/ + printf "/*\n!/*/\n/inc/\n" | + git -C pw_sparse hash-object -w --stdin >sparse_oid +' + +test_expect_success 'sparse:oid with --path-walk produces same blobs' ' + oid=$(cat sparse_oid) && + + git -C pw_sparse pack-objects --revs --stdout \ + --filter=sparse:oid=$oid >standard.pack <<-EOF && + HEAD + EOF + git -C pw_sparse index-pack ../standard.pack && + git -C pw_sparse verify-pack -v ../standard.pack >standard_verify && + + git -C pw_sparse pack-objects --revs --stdout \ + --path-walk --filter=sparse:oid=$oid >pathwalk.pack <<-EOF && + HEAD + EOF + git -C pw_sparse index-pack ../pathwalk.pack && + git -C pw_sparse verify-pack -v ../pathwalk.pack >pathwalk_verify && + + # Blobs must match exactly + grep -E "^[0-9a-f]{40} blob" standard_verify | + awk "{print \$1}" | sort >standard_blobs && + grep -E "^[0-9a-f]{40} blob" pathwalk_verify | + awk "{print \$1}" | sort >pathwalk_blobs && + test_cmp standard_blobs pathwalk_blobs && + + # Commits must match exactly + grep -E "^[0-9a-f]{40} commit" standard_verify | + awk "{print \$1}" | sort >standard_commits && + grep -E "^[0-9a-f]{40} commit" pathwalk_verify | + awk "{print \$1}" | sort >pathwalk_commits && + test_cmp standard_commits pathwalk_commits +' + +test_expect_success 'sparse:oid with --path-walk includes all trees' ' + # The sparse:oid filter restricts only blobs, not trees. + # Both standard and path-walk should include the same trees. + grep -E "^[0-9a-f]{40} tree" standard_verify | + awk "{print \$1}" | sort >standard_trees && + grep -E "^[0-9a-f]{40} tree" pathwalk_verify | + awk "{print \$1}" | sort >pathwalk_trees && + + test_cmp standard_trees pathwalk_trees +' + +# Test the edge case where the same tree/blob OID appears at both an +# in-cone and out-of-cone path. When sibling directories have identical +# contents, they share a tree OID. The path-walk defers marking objects +# SEEN until after checking sparse patterns, so an object at an out-of-cone +# path can still be discovered at an in-cone path. + +test_expect_success 'setup pw_shared for shared OID across cone boundary' ' + git init pw_shared && + mkdir pw_shared/aaa pw_shared/zzz && + echo "shared content" >pw_shared/aaa/file && + echo "shared content" >pw_shared/zzz/file && + echo "root file" >pw_shared/rootfile && + git -C pw_shared add . && + git -C pw_shared commit -m "aaa and zzz share tree OID" && + + # Verify they share a tree OID + aaa_tree=$(git -C pw_shared rev-parse HEAD:aaa) && + zzz_tree=$(git -C pw_shared rev-parse HEAD:zzz) && + test "$aaa_tree" = "$zzz_tree" && + + # Cone pattern: include root + zzz/ (not aaa/) + printf "/*\n!/*/\n/zzz/\n" | + git -C pw_shared hash-object -w --stdin >shared_sparse_oid +' + +test_expect_success 'shared tree OID: --path-walk blobs match standard' ' + oid=$(cat shared_sparse_oid) && + + git -C pw_shared pack-objects --revs --stdout \ + --filter=sparse:oid=$oid >shared_std.pack <<-EOF && + HEAD + EOF + git -C pw_shared index-pack ../shared_std.pack && + git -C pw_shared verify-pack -v ../shared_std.pack >shared_std_verify && + + git -C pw_shared pack-objects --revs --stdout \ + --path-walk --filter=sparse:oid=$oid >shared_pw.pack <<-EOF && + HEAD + EOF + git -C pw_shared index-pack ../shared_pw.pack && + git -C pw_shared verify-pack -v ../shared_pw.pack >shared_pw_verify && + + grep -E "^[0-9a-f]{40} blob" shared_std_verify | + awk "{print \$1}" | sort >shared_std_blobs && + grep -E "^[0-9a-f]{40} blob" shared_pw_verify | + awk "{print \$1}" | sort >shared_pw_blobs && + test_cmp shared_std_blobs shared_pw_blobs +' + test_done
diff --git a/t/t5328-commit-graph-64bit-time.sh b/t/t5328-commit-graph-64bit-time.sh index d8891e6..bc651b6 100755 --- a/t/t5328-commit-graph-64bit-time.sh +++ b/t/t5328-commit-graph-64bit-time.sh
@@ -74,6 +74,15 @@ git -C repo-uint32-max commit-graph verify ' +test_expect_success 'descendant of commit with date exceeding UINT32_MAX' ' + git init repo-uint32-max-descendant && + test_commit -C repo-uint32-max-descendant \ + --date "@4294967300 +0000" future-parent && + test_commit -C repo-uint32-max-descendant present-day-child && + git -C repo-uint32-max-descendant commit-graph write --reachable && + git -C repo-uint32-max-descendant commit-graph verify +' + test_expect_success PERL_TEST_HELPERS 'reader notices out-of-bounds generation overflow' ' graph=.git/objects/info/commit-graph && test_when_finished "rm -rf $graph" &&
diff --git a/t/t5333-pseudo-merge-bitmaps.sh b/t/t5333-pseudo-merge-bitmaps.sh index 1f7a5d8..305d677 100755 --- a/t/t5333-pseudo-merge-bitmaps.sh +++ b/t/t5333-pseudo-merge-bitmaps.sh
@@ -462,4 +462,233 @@ ) ' +test_expect_success 'apply pseudo-merges during fill-in traversal' ' + test_when_finished "rm -fr pseudo-merge-fill-in-traversal" && + git init pseudo-merge-fill-in-traversal && + ( + cd pseudo-merge-fill-in-traversal && + + git config bitmapPseudoMerge.test.pattern refs/tags/ && + git config bitmapPseudoMerge.test.maxMerges 1 && + git config bitmapPseudoMerge.test.stableThreshold never && + + test_commit_bulk 64 && + tag_everything && + git repack -ad && + + pack=$(ls .git/objects/pack/pack-*.pack) && + git rev-parse HEAD~63 >in && + test-tool bitmap write "$(basename $pack)" <in && + + test_pseudo_merges >merges && + test_line_count = 1 merges && + + test_commit stale && + + git rev-list --count --objects HEAD >expect && + + : >trace2.txt && + GIT_TRACE2_EVENT=$PWD/trace2.txt \ + git rev-list --count --objects --use-bitmap-index HEAD >actual && + test_pseudo_merges_satisfied 1 <trace2.txt && + + test_cmp expect actual + ) +' + +test_expect_success 'apply pseudo-merges from multiple groups during fill-in' ' + test_when_finished "rm -fr pseudo-merge-fill-in-multi" && + git init pseudo-merge-fill-in-multi && + ( + cd pseudo-merge-fill-in-multi && + + test_commit base && + base=$(git rev-parse HEAD) && + + for side in left right + do + git checkout -B $side base && + + test_commit_bulk --id=$side 64 && + git rev-list --no-object-names HEAD --not $base >in && + while read oid + do + echo "create refs/group-$side/$oid $oid" || return 1 + done <in | git update-ref --stdin || return 1 + done && + + git checkout left && + git merge right && + git repack -ad && + + git config bitmapPseudoMerge.left.pattern "refs/group-left/" && + git config bitmapPseudoMerge.left.maxMerges 1 && + git config bitmapPseudoMerge.left.stableThreshold never && + + git config bitmapPseudoMerge.right.pattern "refs/group-right/" && + git config bitmapPseudoMerge.right.maxMerges 1 && + git config bitmapPseudoMerge.right.stableThreshold never && + + pack="$(ls .git/objects/pack/pack-*.pack)" && + git rev-parse "$base" >in && + test-tool bitmap write "$(basename $pack)" <in && + + test_pseudo_merges >merges && + test_line_count = 2 merges && + + test_commit stale && + + git rev-list --count --objects HEAD >expect && + + : >trace2.txt && + GIT_TRACE2_EVENT=$PWD/trace2.txt \ + git rev-list --count --objects --use-bitmap-index HEAD >actual && + test_pseudo_merges_satisfied 2 <trace2.txt && + + test_cmp expect actual + ) +' + +test_expect_success 'apply pseudo-merges with overlapping groups during fill-in' ' + test_when_finished "rm -fr pseudo-merge-fill-in-overlap" && + git init pseudo-merge-fill-in-overlap && + ( + cd pseudo-merge-fill-in-overlap && + + test_commit_bulk 64 && + tag_everything && + git repack -ad && + + pack="$(ls .git/objects/pack/pack-*.pack)" && + + # Use two pseudo-merge group patterns that both match + # refs/tags/, so every tagged commit belongs to both + # groups. This exercises the extended lookup table + # path in apply_pseudo_merges_for_commit(). + git config bitmapPseudoMerge.all.pattern "refs/tags/" && + git config bitmapPseudoMerge.all.maxMerges 1 && + git config bitmapPseudoMerge.all.stableThreshold never && + + git config bitmapPseudoMerge.tags.pattern "refs/tags/" && + git config bitmapPseudoMerge.tags.maxMerges 1 && + git config bitmapPseudoMerge.tags.stableThreshold never && + + git rev-parse HEAD~63 >in && + test-tool bitmap write "$(basename $pack)" <in && + + test_pseudo_merges >merges && + test_line_count = 2 merges && + + test_commit stale && + + git rev-list --count --objects HEAD >expect && + + : >trace2.txt && + GIT_TRACE2_EVENT=$PWD/trace2.txt \ + git rev-list --count --objects --use-bitmap-index HEAD >actual && + test_pseudo_merges_satisfied 2 <trace2.txt && + + test_cmp expect actual + ) +' + +test_expect_success 'pseudo-merge commits are correctly classified by date' ' + test_when_finished "rm -fr pseudo-merge-date-classification" && + git init pseudo-merge-date-classification && + ( + cd pseudo-merge-date-classification && + + test_commit_bulk 64 && + + tag_everything && + git repack -ad && + + pack="$(ls .git/objects/pack/pack-*.pack)" && + + # Configure two pseudo-merge groups: one that only + # matches "stable" refs (older than one month), and + # one that matches all refs. With 64 tags whose + # commits are all younger than one month, the + # "stable" group should have zero pseudo-merges and + # the "all" group should have one. + # + # Use GIT_TEST_DATE_NOW to align "now" (and therefore + # "1.month.ago") with the test_tick timestamps so that + # the commits are within the last month. + # + # Without parsing the commit, its date field would + # be zero, causing it to satisfy date <= threshold + # for the "stable" group as well, and both groups + # would produce pseudo-merges. + git config bitmapPseudoMerge.stable.pattern "refs/tags/" && + git config bitmapPseudoMerge.stable.maxMerges 64 && + git config bitmapPseudoMerge.stable.stableThreshold never && + git config bitmapPseudoMerge.stable.threshold 1.month.ago && + + git config bitmapPseudoMerge.all.pattern "refs/tags/" && + git config bitmapPseudoMerge.all.maxMerges 1 && + git config bitmapPseudoMerge.all.stableThreshold never && + git config bitmapPseudoMerge.all.threshold now && + + git rev-parse HEAD~63 >in && + GIT_TEST_DATE_NOW=$test_tick \ + test-tool bitmap write "$(basename $pack)" <in && + + test_pseudo_merges >merges && + test_line_count = 1 merges + ) +' + +test_expect_success 'sampleRate=0 does not cause division by zero' ' + test_when_finished "rm -fr pseudo-merge-sample-rate-zero" && + git init pseudo-merge-sample-rate-zero && + ( + cd pseudo-merge-sample-rate-zero && + + test_commit_bulk 64 && + tag_everything && + git repack -ad && + + pack="$(ls .git/objects/pack/pack-*.pack)" && + + git config bitmapPseudoMerge.test.pattern "refs/tags/" && + git config bitmapPseudoMerge.test.maxMerges 1 && + git config bitmapPseudoMerge.test.sampleRate 0 && + git config bitmapPseudoMerge.test.threshold now && + git config bitmapPseudoMerge.test.stableThreshold never && + + git rev-parse HEAD~63 >in && + test-tool bitmap write "$(basename $pack)" <in + ) +' + +test_expect_success 'duplicate pseudo-merge pattern does not leak' ' + test_when_finished "rm -fr pseudo-merge-dup-pattern" && + git init pseudo-merge-dup-pattern && + ( + cd pseudo-merge-dup-pattern && + + test_commit_bulk 64 && + tag_everything && + git repack -ad && + + pack=$(ls .git/objects/pack/pack-*.pack) && + + # Set the same group'\''s pattern twice. The second + # assignment should cleanly release the compiled regex + # from the first without leaking. + git config bitmapPseudoMerge.test.pattern "refs/tags/" && + git config --add bitmapPseudoMerge.test.pattern "refs/tags/" && + git config bitmapPseudoMerge.test.maxMerges 1 && + git config bitmapPseudoMerge.test.threshold now && + git config bitmapPseudoMerge.test.stableThreshold never && + + git rev-parse HEAD~63 >in && + test-tool bitmap write "$(basename $pack)" <in && + + test_pseudo_merges >merges && + test_line_count = 1 merges + ) +' + test_done
diff --git a/t/t5334-incremental-multi-pack-index.sh b/t/t5334-incremental-multi-pack-index.sh index 99c7d44..68a103d 100755 --- a/t/t5334-incremental-multi-pack-index.sh +++ b/t/t5334-incremental-multi-pack-index.sh
@@ -96,6 +96,53 @@ git cat-file -p 2.2 ' +test_expect_success 'write MIDX layer with --no-write-chain-file' ' + test_commit no-write-chain-file && + git repack -d && + + cp "$midx_chain" "$midx_chain.bak" && + layer="$(git multi-pack-index write --bitmap --incremental \ + --no-write-chain-file)" && + + test_cmp "$midx_chain.bak" "$midx_chain" && + test_path_is_file "$midxdir/multi-pack-index-$layer.midx" +' + +test_expect_success 'write non-incremental MIDX layer with --no-write-chain-file' ' + test_must_fail git multi-pack-index write --bitmap --no-write-chain-file 2>err && + test_grep "cannot use --no-write-chain-file without --incremental" err +' + +test_expect_success 'write MIDX layer with --base without --no-write-chain-file' ' + test_must_fail git multi-pack-index write --bitmap --incremental \ + --base=none 2>err && + test_grep "cannot use --base without --no-write-chain-file" err +' + +test_expect_success 'write MIDX layer with --base=none and --no-write-chain-file' ' + test_commit base-none && + git repack -d && + + cp "$midx_chain" "$midx_chain.bak" && + layer="$(git multi-pack-index write --bitmap --incremental \ + --no-write-chain-file --base=none)" && + + test_cmp "$midx_chain.bak" "$midx_chain" && + test_path_is_file "$midxdir/multi-pack-index-$layer.midx" +' + +test_expect_success 'write MIDX layer with --base=<hash> and --no-write-chain-file' ' + test_commit base-hash && + git repack -d && + + cp "$midx_chain" "$midx_chain.bak" && + layer="$(git multi-pack-index write --bitmap --incremental \ + --no-write-chain-file --base="$(nth_line 1 "$midx_chain")")" && + + test_cmp "$midx_chain.bak" "$midx_chain" && + test_path_is_file "$midxdir/multi-pack-index-$layer.midx" +' + for reuse in false single multi do test_expect_success "full clone (pack.allowPackReuse=$reuse)" ' @@ -132,4 +179,20 @@ ' +test_expect_success 'non-incremental write with existing incremental chain' ' + git init non-incremental-write-with-existing && + test_when_finished "rm -fr non-incremental-write-with-existing" && + + ( + cd non-incremental-write-with-existing && + + git config set maintenance.auto false && + + write_midx_layer && + write_midx_layer && + + git multi-pack-index write + ) +' + test_done
diff --git a/t/t5335-compact-multi-pack-index.sh b/t/t5335-compact-multi-pack-index.sh index 40f3844..ec1dafe 100755 --- a/t/t5335-compact-multi-pack-index.sh +++ b/t/t5335-compact-multi-pack-index.sh
@@ -290,4 +290,117 @@ ) ' +test_expect_success 'MIDX compaction with --no-write-chain-file' ' + git init midx-compact-with--no-write-chain-file && + ( + cd midx-compact-with--no-write-chain-file && + + git config maintenance.auto false && + + write_packs A B C D && + + test_line_count = 4 $midx_chain && + cp "$midx_chain" "$midx_chain".bak && + + layer="$(git multi-pack-index compact --incremental \ + --no-write-chain-file \ + --base="$(nth_line 1 "$midx_chain")" \ + "$(nth_line 2 "$midx_chain")" \ + "$(nth_line 3 "$midx_chain")")" && + + test_cmp "$midx_chain.bak" "$midx_chain" && + + # After writing the new layer, insert it into the chain + # manually. This is done in order to make $layer visible + # to the read-midx test helper below, and matches what + # the MIDX command would do without --no-write-chain-file. + { + nth_line 1 "$midx_chain.bak" && + echo $layer && + nth_line 4 "$midx_chain.bak" + } >$midx_chain && + + test-tool read-midx $objdir $layer >midx.data && + grep "^pack-B-.*\.idx" midx.data && + grep "^pack-C-.*\.idx" midx.data + + ) +' + +test_expect_success 'MIDX compaction with --base' ' + git init midx-compact-with--base && + ( + cd midx-compact-with--base && + + git config maintenance.auto false && + + write_packs A B C D && + + test_line_count = 4 "$midx_chain" && + + cp "$midx_chain" "$midx_chain.bak" && + + git multi-pack-index compact --incremental \ + --base="$(nth_line 1 "$midx_chain")" \ + "$(nth_line 3 "$midx_chain")" \ + "$(nth_line 4 "$midx_chain")" && + test_line_count = 2 $midx_chain && + + nth_line 1 "$midx_chain.bak" >expect && + nth_line 1 "$midx_chain" >actual && + + test_cmp expect actual + ) +' + +test_expect_success 'MIDX compaction with --base=none' ' + git init midx-compact-base-none && + ( + cd midx-compact-base-none && + + git config maintenance.auto false && + + write_packs A B C D && + + test_line_count = 4 $midx_chain && + + cp "$midx_chain" "$midx_chain".bak && + + # Compact the two bottommost layers (A and B) into a new + # root layer with no parent. + git multi-pack-index compact --incremental \ + --base=none \ + "$(nth_line 1 "$midx_chain")" \ + "$(nth_line 2 "$midx_chain")" && + + test_line_count = 3 $midx_chain && + + # The upper layers (C and D) should be preserved + # unchanged. + nth_line 3 "$midx_chain.bak" >expect && + nth_line 4 "$midx_chain.bak" >>expect && + nth_line 2 "$midx_chain" >actual && + nth_line 3 "$midx_chain" >>actual && + + test_cmp expect actual + ) +' + +test_expect_success 'MIDX compaction with bogus --base checksum' ' + git init midx-compact-bogus-base && + ( + cd midx-compact-bogus-base && + + git config maintenance.auto false && + + write_packs A B C && + + test_must_fail git multi-pack-index compact --incremental \ + --base=deadbeef \ + "$(nth_line 2 "$midx_chain")" \ + "$(nth_line 3 "$midx_chain")" 2>err && + test_grep "could not find base MIDX" err + ) +' + test_done
diff --git a/t/t5409-colorize-remote-messages.sh b/t/t5409-colorize-remote-messages.sh index fa5de45..3010913 100755 --- a/t/t5409-colorize-remote-messages.sh +++ b/t/t5409-colorize-remote-messages.sh
@@ -98,4 +98,96 @@ grep "<BOLD;RED>error<RESET>: error" decoded ' +test_expect_success 'disallow (color) control sequences in sideband' ' + write_script .git/color-me-surprised <<-\EOF && + printf "error: Have you \\033[31mread\\033[m this?\\a\\n" >&2 + exec "$@" + EOF + test_config_global uploadPack.packObjectsHook ./color-me-surprised && + test_commit need-at-least-one-commit && + + git clone --no-local . throw-away 2>stderr && + test_decode_color <stderr >decoded && + test_grep RED decoded && + test_grep "\\^G" stderr && + tr -dc "\\007" <stderr >actual && + test_must_be_empty actual && + + rm -rf throw-away && + git -c sideband.allowControlCharacters=false \ + clone --no-local . throw-away 2>stderr && + test_decode_color <stderr >decoded && + test_grep ! RED decoded && + test_grep "\\^G" stderr && + + rm -rf throw-away && + git -c sideband.allowControlCharacters clone --no-local . throw-away 2>stderr && + test_decode_color <stderr >decoded && + test_grep RED decoded && + tr -dc "\\007" <stderr >actual && + test_file_not_empty actual +' + +test_decode_csi() { + awk '{ + while (match($0, /\033/) != 0) { + printf "%sCSI ", substr($0, 1, RSTART-1); + $0 = substr($0, RSTART + RLENGTH, length($0) - RSTART - RLENGTH + 1); + } + print + }' +} + +test_expect_success 'control sequences in sideband allowed by default' ' + write_script .git/color-me-surprised <<-\EOF && + printf "error: \\033[31mcolor\\033[m\\033[Goverwrite\\033[Gerase\\033[K\\033?25l\\n" >&2 + exec "$@" + EOF + test_config_global uploadPack.packObjectsHook ./color-me-surprised && + test_commit need-at-least-one-commit-at-least && + + rm -rf throw-away && + git clone --no-local . throw-away 2>stderr && + test_decode_color <stderr >color-decoded && + test_decode_csi <color-decoded >decoded && + test_grep ! "CSI \\[K" decoded && + test_grep ! "CSI \\[G" decoded && + test_grep "\\^\\[?25l" decoded && + + rm -rf throw-away && + git -c sideband.allowControlCharacters=erase,cursor,color \ + clone --no-local . throw-away 2>stderr && + test_decode_color <stderr >color-decoded && + test_decode_csi <color-decoded >decoded && + test_grep "RED" decoded && + test_grep "CSI \\[K" decoded && + test_grep "CSI \\[G" decoded && + test_grep ! "\\^\\[\\[K" decoded && + test_grep ! "\\^\\[\\[G" decoded +' + +test_expect_success 'allow all control sequences for a specific URL' ' + write_script .git/eraser <<-\EOF && + printf "error: Ohai!\\r\\033[K" >&2 + exec "$@" + EOF + test_config_global uploadPack.packObjectsHook ./eraser && + test_commit one-more-please && + + rm -rf throw-away && + git clone --no-local . throw-away 2>stderr && + test_decode_color <stderr >color-decoded && + test_decode_csi <color-decoded >decoded && + test_grep ! "CSI \\[K" decoded && + test_grep "\\^\\[\\[K" decoded && + + rm -rf throw-away && + git -c "sideband.file://.allowControlCharacters=true" \ + clone --no-local "file://$PWD" throw-away 2>stderr && + test_decode_color <stderr >color-decoded && + test_decode_csi <color-decoded >decoded && + test_grep "CSI \\[K" decoded && + test_grep ! "\\^\\[\\[K" decoded +' + test_done
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index 6fe21e2..eca9a97 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh
@@ -1465,6 +1465,197 @@ test_cmp fatal-expect fatal-actual ' +test_expect_success '--negotiation-tip ignores missing refs and invalid hashes' ' + setup_negotiation_tip server server 0 && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \ + --negotiation-tip=alpha_1 --negotiation-tip=beta_1 \ + --negotiation-tip=no-such-ref \ + --negotiation-tip=invalid-hash \ + origin alpha_s beta_s && + check_negotiation_tip +' + +test_expect_success '--negotiation-restrict limits "have" lines sent' ' + setup_negotiation_tip server server 0 && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \ + --negotiation-restrict=alpha_1 --negotiation-restrict=beta_1 \ + origin alpha_s beta_s && + check_negotiation_tip +' + +test_expect_success '--negotiation-restrict understands globs' ' + setup_negotiation_tip server server 0 && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \ + --negotiation-restrict=*_1 \ + origin alpha_s beta_s && + check_negotiation_tip +' + +test_expect_success '--negotiation-restrict and --negotiation-tip can be mixed' ' + setup_negotiation_tip server server 0 && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \ + --negotiation-restrict=alpha_1 \ + --negotiation-tip=beta_1 \ + origin alpha_s beta_s && + check_negotiation_tip +' + +test_expect_success 'remote.<name>.negotiationRestrict used as default' ' + setup_negotiation_tip server server 0 && + + # test the reset of the list on an empty value + git -C client config --add remote.origin.negotiationRestrict alpha_2 && + git -C client config --add remote.origin.negotiationRestrict "" && + git -C client config --add remote.origin.negotiationRestrict alpha_1 && + git -C client config --add remote.origin.negotiationRestrict beta_1 && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \ + origin alpha_s beta_s && + check_negotiation_tip +' + +test_expect_success 'CLI --negotiation-restrict overrides remote config' ' + setup_negotiation_tip server server 0 && + git -C client config --add remote.origin.negotiationRestrict alpha_1 && + git -C client config --add remote.origin.negotiationRestrict beta_1 && + ALPHA_1=$(git -C client rev-parse alpha_1) && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \ + --negotiation-restrict=alpha_1 \ + origin alpha_s beta_s && + test_grep "fetch> have $ALPHA_1" trace && + BETA_1=$(git -C client rev-parse beta_1) && + test_grep ! "fetch> have $BETA_1" trace +' + +test_expect_success '--negotiation-include includes configured refs as haves' ' + test_when_finished rm -f trace && + setup_negotiation_tip server server 0 && + + GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \ + --negotiation-restrict=alpha_1 \ + --negotiation-include=refs/tags/beta_1 \ + origin alpha_s beta_s && + + ALPHA_1=$(git -C client rev-parse alpha_1) && + test_grep "fetch> have $ALPHA_1" trace && + BETA_1=$(git -C client rev-parse beta_1) && + test_grep "fetch> have $BETA_1" trace +' + +test_expect_success '--negotiation-include works with glob patterns' ' + test_when_finished rm -f trace && + setup_negotiation_tip server server 0 && + + GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \ + --negotiation-restrict=alpha_1 \ + --negotiation-include="refs/tags/beta_*" \ + origin alpha_s beta_s && + + BETA_1=$(git -C client rev-parse beta_1) && + test_grep "fetch> have $BETA_1" trace && + BETA_2=$(git -C client rev-parse beta_2) && + test_grep "fetch> have $BETA_2" trace +' + +test_expect_success '--negotiation-include is additive with negotiation' ' + test_when_finished rm -f trace && + setup_negotiation_tip server server 0 && + + GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \ + --negotiation-include=refs/tags/beta_1 \ + origin alpha_s beta_s && + + BETA_1=$(git -C client rev-parse beta_1) && + test_grep "fetch> have $BETA_1" trace +' + +test_expect_success '--negotiation-include ignores non-existent refs silently' ' + setup_negotiation_tip server server 0 && + + git -C client fetch --quiet \ + --negotiation-restrict=alpha_1 \ + --negotiation-include=refs/tags/nonexistent \ + origin alpha_s beta_s 2>err && + test_must_be_empty err +' + +test_expect_success '--negotiation-include avoids duplicates with negotiator' ' + test_when_finished rm -f trace && + setup_negotiation_tip server server 0 && + + ALPHA_1=$(git -C client rev-parse alpha_1) && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \ + --negotiation-restrict=alpha_1 \ + --negotiation-include=refs/tags/alpha_1 \ + origin alpha_s beta_s && + + test_grep "fetch> have $ALPHA_1" trace >matches && + test_line_count = 1 matches +' + +test_expect_success 'remote.<name>.negotiationInclude used as default for --negotiation-include' ' + test_when_finished rm -f trace && + setup_negotiation_tip server server 0 && + + # test the reset of the list on an empty value + git -C client config --add remote.origin.negotiationInclude refs/tags/alpha_1 && + git -C client config --add remote.origin.negotiationInclude "" && + git -C client config --add remote.origin.negotiationInclude refs/tags/beta_1 && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \ + --negotiation-restrict=beta_2 \ + origin alpha_s beta_s && + + ALPHA_1=$(git -C client rev-parse alpha_1) && + test_grep ! "fetch> have $ALPHA_1" trace && + BETA_1=$(git -C client rev-parse beta_1) && + test_grep "fetch> have $BETA_1" trace +' + +test_expect_success 'remote.<name>.negotiationInclude works with glob patterns' ' + test_when_finished rm -f trace && + setup_negotiation_tip server server 0 && + + git -C client config --add remote.origin.negotiationInclude "refs/tags/beta_*" && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \ + --negotiation-restrict=alpha_1 \ + origin alpha_s beta_s && + + BETA_1=$(git -C client rev-parse beta_1) && + test_grep "fetch> have $BETA_1" trace && + BETA_2=$(git -C client rev-parse beta_2) && + test_grep "fetch> have $BETA_2" trace +' + +test_expect_success 'CLI --negotiation-include overrides remote.<name>.negotiationInclude' ' + test_when_finished rm -f trace && + setup_negotiation_tip server server 0 && + + git -C client config --add remote.origin.negotiationInclude refs/tags/beta_2 && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \ + --negotiation-restrict=alpha_1 \ + --negotiation-include=refs/tags/beta_1 \ + origin alpha_s beta_s && + + BETA_1=$(git -C client rev-parse beta_1) && + test_grep "fetch> have $BETA_1" trace && + BETA_2=$(git -C client rev-parse beta_2) && + test_grep ! "fetch> have $BETA_2" trace +' + +test_expect_success '--negotiation-include avoids duplicates with v0' ' + test_when_finished rm -f trace && + setup_negotiation_tip server server 0 && + + ALPHA_1=$(git -C client rev-parse alpha_1) && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client \ + -c protocol.version=0 fetch \ + --negotiation-restrict=alpha_1 \ + --negotiation-include=refs/tags/alpha_1 \ + origin alpha_s beta_s && + + test_grep "fetch> have $ALPHA_1" trace >matches && + test_line_count = 1 matches +' + test_expect_success SYMLINKS 'clone does not get confused by a D/F conflict' ' git init df-conflict && (
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 117cfa0..1b98634 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh
@@ -254,6 +254,36 @@ ! grep "Fetching submodule" err ' +test_expect_success 'push with negotiation and remote.<name>.negotiationInclude' ' + test_when_finished rm -rf negotiation_include && + mk_empty negotiation_include && + git push negotiation_include $the_first_commit:refs/remotes/origin/first_commit && + test_commit -C negotiation_include unrelated_commit && + git -C negotiation_include config receive.hideRefs refs/remotes/origin/first_commit && + test_when_finished "rm event" && + GIT_TRACE2_EVENT="$(pwd)/event" \ + git -c protocol.version=2 -c push.negotiate=1 \ + -c remote.negotiation_include.negotiationInclude=refs/heads/main \ + push negotiation_include refs/heads/main:refs/remotes/origin/main && + test_grep \"key\":\"total_rounds\" event && + grep_wrote 2 event # 1 commit, 1 tree +' + +test_expect_success 'push with negotiation and remote.<name>.negotiationRestrict' ' + test_when_finished rm -rf negotiation_restrict && + mk_empty negotiation_restrict && + git push negotiation_restrict $the_first_commit:refs/remotes/origin/first_commit && + test_commit -C negotiation_restrict unrelated_commit && + git -C negotiation_restrict config receive.hideRefs refs/remotes/origin/first_commit && + test_when_finished "rm event" && + GIT_TRACE2_EVENT="$(pwd)/event" \ + git -c protocol.version=2 -c push.negotiate=1 \ + -c remote.negotiation_restrict.negotiationRestrict=refs/heads/main \ + push negotiation_restrict refs/heads/main:refs/remotes/origin/main && + test_grep \"key\":\"total_rounds\" event && + grep_wrote 2 event # 1 commit, 1 tree +' + test_expect_success 'push without wildcard' ' mk_empty testrepo && @@ -1349,7 +1379,7 @@ git for-each-ref >tmp1 && sed -n "p; s|refs/heads/main$|refs/remotes/origin/main|p" tmp1 | sed -n "p; s|refs/heads/main$|refs/remotes/origin/HEAD|p" | - sort -k 4 >../expect + sort -k 3 >../expect ) && test_when_finished "rm -rf dst" && git init dst && @@ -1791,6 +1821,17 @@ ) ' +test_expect_success 'denyCurrentBranch and core.worktree' ' + test_when_finished "rm -fr cloned cloned.git" && + git clone --separate-git-dir cloned.git . cloned && + git --git-dir cloned.git config receive.denyCurrentBranch updateInstead && + git --git-dir cloned.git config core.worktree "$PWD/cloned" && + test_commit raspberry && + git push cloned.git HEAD:main && + test_path_exists cloned/raspberry.t && + test_must_fail git push --delete cloned.git main +' + test_expect_success 'denyCurrentBranch and worktrees' ' test_when_finished "rm -fr cloned && git worktree remove --force new-wt" && git worktree add new-wt &&
diff --git a/t/t5537-fetch-shallow.sh b/t/t5537-fetch-shallow.sh index 6588ce6..9982dd2 100755 --- a/t/t5537-fetch-shallow.sh +++ b/t/t5537-fetch-shallow.sh
@@ -251,6 +251,16 @@ origin "+refs/heads/*:refs/remotes/origin/*" ' +test_expect_success 'fetch --deepen does not truncate' ' + git clone --no-local .git full-clone && + git -C full-clone rev-parse --is-shallow-repository >expect && + git -C full-clone log --oneline >>expect && + git -C full-clone fetch --deepen=1 && + git -C full-clone rev-parse --is-shallow-repository >actual && + git -C full-clone log --oneline >>actual && + test_cmp expect actual +' + . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd
diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh index 9d0a7f5..b0080bf 100755 --- a/t/t5550-http-fetch-dumb.sh +++ b/t/t5550-http-fetch-dumb.sh
@@ -555,4 +555,24 @@ git -C idx-v1 fsck ' +test_expect_success 'absolute-path alternate when url has no path' ' + src=$HTTPD_DOCUMENT_ROOT_PATH/repo.git && + alt=absolute-alt.git && + git clone --bare --shared "$src" "$alt" && + + # Our repo has an alternate pointing to the absolute filesystem path, + # but that will not make any sense to an http client. So we will + # manually give it the equivalent path that the http server will + # understand. + echo "/dumb/repo.git/objects" >"$alt/objects/info/http-alternates" && + + # Now make our alt repository available at the root of the http + # server without any path (i.e., just http://localhost:1234). + git -C "$alt" update-server-info && + mv absolute-alt.git/* "$HTTPD_DOCUMENT_ROOT_PATH" && + + git -c http.followRedirects=true clone "$HTTPD_URL" alt-clone.git 2>err && + test_grep "adding alternate object store: $HTTPD_URL/dumb/repo.git" err +' + test_done
diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh index a26b6c2..e236e52 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh
@@ -481,6 +481,7 @@ ' test_expect_success EXPENSIVE 'http can handle enormous ref negotiation' ' + test_when_finished "rm -f tags" && ( cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && create_tags 2001 50000
diff --git a/t/t5563-simple-http-auth.sh b/t/t5563-simple-http-auth.sh index 0063581..a7d475d 100755 --- a/t/t5563-simple-http-auth.sh +++ b/t/t5563-simple-http-auth.sh
@@ -719,4 +719,78 @@ EOF ' +test_lazy_prereq SPNEGO 'curl --version | grep -qi "SPNEGO\|GSS-API\|Kerberos\|negotiate"' + +test_expect_success SPNEGO 'http.emptyAuth=auto attempts Negotiate before credential_fill' ' + test_when_finished "per_test_cleanup" && + + set_credential_reply get <<-EOF && + username=alice + password=secret-passwd + EOF + + # Basic base64(alice:secret-passwd) + cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && + id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== + EOF + + cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF && + id=1 status=200 + id=default response=WWW-Authenticate: Negotiate + id=default response=WWW-Authenticate: Basic realm="example.com" + EOF + + test_config_global credential.helper test-helper && + GIT_TRACE_CURL="$TRASH_DIRECTORY/trace-auto" \ + git -c http.emptyAuth=auto \ + ls-remote "$HTTPD_URL/custom_auth/repo.git" && + + # In auto mode with a Negotiate+Basic server, there should be + # three 401 responses: (1) initial no-auth request, (2) empty-auth + # retry where Negotiate fails (no Kerberos ticket), (3) libcurl + # internal Negotiate retry. The fourth attempt uses Basic + # credentials from credential_fill and succeeds. + grep "HTTP/[0-9.]* 401" "$TRASH_DIRECTORY/trace-auto" >actual_401s && + test_line_count = 3 actual_401s && + + expect_credential_query get <<-EOF + capability[]=authtype + capability[]=state + protocol=http + host=$HTTPD_DEST + wwwauth[]=Negotiate + wwwauth[]=Basic realm="example.com" + EOF +' + +test_expect_success SPNEGO 'http.emptyAuth=false skips Negotiate' ' + test_when_finished "per_test_cleanup" && + + set_credential_reply get <<-EOF && + username=alice + password=secret-passwd + EOF + + # Basic base64(alice:secret-passwd) + cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && + id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== + EOF + + cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF && + id=1 status=200 + id=default response=WWW-Authenticate: Negotiate + id=default response=WWW-Authenticate: Basic realm="example.com" + EOF + + test_config_global credential.helper test-helper && + GIT_TRACE_CURL="$TRASH_DIRECTORY/trace-false" \ + git -c http.emptyAuth=false \ + ls-remote "$HTTPD_URL/custom_auth/repo.git" && + + # With emptyAuth=false, Negotiate is stripped immediately and + # credential_fill is called right away. Only one 401 response. + grep "HTTP/[0-9.]* 401" "$TRASH_DIRECTORY/trace-false" >actual_401s && + test_line_count = 1 actual_401s +' + test_done
diff --git a/t/t5564-http-proxy.sh b/t/t5564-http-proxy.sh index 3bcbdef..817cd1f 100755 --- a/t/t5564-http-proxy.sh +++ b/t/t5564-http-proxy.sh
@@ -50,14 +50,20 @@ # The %30 tests that the correct amount of percent-encoding is applied to the # proxy string passed to curl. +# Use a short path for the socket to avoid exceeding the 108-character +# Unix domain socket limit when the trash directory path is long. +SOCKS_TMPDIR=$(mktemp -d) +SOCKS_SOCK="$SOCKS_TMPDIR/%30.sock" + test_lazy_prereq SOCKS_PROXY ' test_have_prereq PERL && - start_socks "$TRASH_DIRECTORY/%30.sock" + start_socks "$SOCKS_SOCK" ' test_atexit ' test ! -e "$TRASH_DIRECTORY/socks.pid" || kill "$(cat "$TRASH_DIRECTORY/socks.pid")" + rm -rf "$SOCKS_TMPDIR" ' # The below tests morally ought to be gated on a prerequisite that Git is @@ -70,7 +76,8 @@ test_expect_success SOCKS_PROXY 'clone via Unix socket' ' test_when_finished "rm -rf clone" && - test_config_global http.proxy "socks4://localhost$PWD/%2530.sock" && { + socks_proxy_url="socks4://localhost$(echo "$SOCKS_SOCK" | sed "s/%/%25/g")" && + test_config_global http.proxy "$socks_proxy_url" && { { GIT_TRACE_CURL=$PWD/trace \ GIT_TRACE_CURL_COMPONENTS=socks \ @@ -95,4 +102,10 @@ } EOT +test_expect_success 'unknown proxy scheme is rejected' ' + test_must_fail git clone -c http.proxy=htpp://127.0.0.1 \ + https://example.com/repo.git 2>err && + test_grep "unsupported proxy scheme '\''htpp'\''" err +' + test_done
diff --git a/t/t5566-push-group.sh b/t/t5566-push-group.sh new file mode 100755 index 0000000..a7d5935 --- /dev/null +++ b/t/t5566-push-group.sh
@@ -0,0 +1,160 @@ +#!/bin/sh + +test_description='push to remote group' + +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=default +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + +. ./test-lib.sh + +test_expect_success 'setup' ' + for i in 1 2 3 + do + git init --bare dest-$i.git && + git -C dest-$i.git symbolic-ref HEAD refs/heads/not-a-branch || + return 1 + done && + test_tick && + git commit --allow-empty -m "initial" && + git config set remote.remote-1.url "file://$(pwd)/dest-1.git" && + git config set remote.remote-1.fetch "+refs/heads/*:refs/remotes/remote-1/*" && + git config set remote.remote-2.url "file://$(pwd)/dest-2.git" && + git config set remote.remote-2.fetch "+refs/heads/*:refs/remotes/remote-2/*" && + git config set remote.remote-3.url "file://$(pwd)/dest-3.git" && + git config set remote.remote-3.fetch "+refs/heads/*:refs/remotes/remote-3/*" && + git config set remotes.all-remotes "remote-1 remote-2 remote-3" +' + +test_expect_success 'push to remote group updates all members correctly' ' + git push all-remotes HEAD:refs/heads/main && + git rev-parse HEAD >expect && + for i in 1 2 3 + do + git -C dest-$i.git rev-parse refs/heads/main >actual || + return 1 + test_cmp expect actual || return 1 + done +' + +test_expect_success 'push second commit to group updates all members' ' + test_tick && + git commit --allow-empty -m "second" && + git push all-remotes HEAD:refs/heads/main && + git rev-parse HEAD >expect && + for i in 1 2 3 + do + git -C dest-$i.git rev-parse refs/heads/main >actual || + return 1 + test_cmp expect actual || return 1 + done +' + +test_expect_success 'push to single remote in group does not affect others' ' + test_tick && + git commit --allow-empty -m "third" && + git push remote-1 HEAD:refs/heads/main && + git -C dest-1.git rev-parse refs/heads/main >hash-after-1 && + git -C dest-2.git rev-parse refs/heads/main >hash-after-2 && + ! test_cmp hash-after-1 hash-after-2 +' + +test_expect_success 'mirror remote in group with refspec fails' ' + git config set remote.remote-1.mirror true && + test_must_fail git push all-remotes HEAD:refs/heads/main 2>err && + test_grep "mirror" err && + git config unset remote.remote-1.mirror +' + +test_expect_success 'push.default=current works with group push' ' + git config set push.default current && + test_tick && + git commit --allow-empty -m "fifth" && + git push all-remotes && + git config unset push.default +' + +test_expect_success '--atomic is rejected for group push' ' + test_must_fail git push --atomic all-remotes HEAD:refs/heads/main 2>err && + test_grep "atomic" err +' + +test_expect_success 'push continues past rejection to remaining remotes' ' + for i in c1 c2 c3 + do + git init --bare dest-$i.git || return 1 + done && + git config set remote.c1.url "file://$(pwd)/dest-c1.git" && + git config set remote.c2.url "file://$(pwd)/dest-c2.git" && + git config set remote.c3.url "file://$(pwd)/dest-c3.git" && + git config set remotes.continue-group "c1 c2 c3" && + + test_tick && + git commit --allow-empty -m "base for continue test" && + + # initial sync + git push continue-group HEAD:refs/heads/main && + + # advance c2 independently + git clone dest-c2.git tmp-c2 && + ( + cd tmp-c2 && + git checkout -b main origin/main && + test_commit c2_independent && + git push origin HEAD:refs/heads/main + ) && + rm -rf tmp-c2 && + + test_tick && + git commit --allow-empty -m "local diverging commit" && + + # push: c2 rejects, others succeed + test_must_fail git push continue-group HEAD:refs/heads/main && + + git rev-parse HEAD >expect && + git -C dest-c1.git rev-parse refs/heads/main >actual-c1 && + git -C dest-c3.git rev-parse refs/heads/main >actual-c3 && + test_cmp expect actual-c1 && + test_cmp expect actual-c3 && + + # c2 should not have the new commit + git -C dest-c2.git rev-parse refs/heads/main >actual-c2 && + ! test_cmp expect actual-c2 +' + +test_expect_success 'fatal connection error does not stop remaining remotes' ' + for i in f1 f2 f3 + do + git init --bare dest-$i.git || return 1 + done && + git config set remote.f1.url "file://$(pwd)/dest-f1.git" && + git config set remote.f2.url "file://$(pwd)/dest-f2.git" && + git config set remote.f3.url "file://$(pwd)/dest-f3.git" && + git config set remotes.fatal-group "f1 f2 f3" && + + test_tick && + git commit --allow-empty -m "base for fatal test" && + + # initial sync + git push fatal-group HEAD:refs/heads/main && + + # break f2 + git config set remote.f2.url "file:///tmp/does-not-exist-$$" && + + test_tick && + git commit --allow-empty -m "after fatal setup" && + + # overall exit code is non-zero because f2 failed + test_must_fail git push fatal-group HEAD:refs/heads/main && + + git rev-parse HEAD >expect && + + # f1 and f3 should both have the new commit — subprocesses are independent + git -C dest-f1.git rev-parse refs/heads/main >actual-f1 && + test_cmp expect actual-f1 && + git -C dest-f3.git rev-parse refs/heads/main >actual-f3 && + test_cmp expect actual-f3 && + + git config set remote.f2.url "file://$(pwd)/dest-f2.git" +' + +test_done
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index d743d98..3dd229c 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh
@@ -813,7 +813,9 @@ test_when_finished "rm -rf repo \"$HTTPD_DOCUMENT_ROOT_PATH/repo.git\"" && git clone --bare --no-local src "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && - test_when_finished "rm \"$HOME\"/.gitconfig" && + test_when_finished "cp \"$HOME\"/.gitconfig.bak \ + \"$HOME\"/.gitconfig 2>/dev/null || rm -f \"$HOME\"/.gitconfig" && + cp "$HOME"/.gitconfig "$HOME"/.gitconfig.bak 2>/dev/null && cat >"$HOME"/.gitconfig <<-EOF && [includeIf "onbranch:something"] path = /does/not/exist.inc
diff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh index 470bfb6..c232ab8 100755 --- a/t/t5604-clone-reference.sh +++ b/t/t5604-clone-reference.sh
@@ -360,4 +360,27 @@ grep "is a symlink, refusing to clone with --local" err ' +test_expect_success 'dissociate from repo with commit graph' ' + git init orig && + # We are trying to make sure the dissociated repo can + # find the tree of the tip commit, so the test could still + # serve its purpose with an empty tree. But having actual + # content future-proofs us against any kind of internal + # empty-tree optimizations. + echo content >orig/file && + git -C orig add . && + git -C orig commit -m foo && + + # We will use graph.git as our "local" source to dissociate + # from. + git clone --bare orig graph.git && + git -C graph.git commit-graph write --reachable && + + # And then finally clone orig, using graph.git to get our objects. This + # must be non-bare so that we perform the checkout step, which will + # need to access the tree of HEAD, which we will have originally loaded + # via the commit graph. + git clone --no-local --reference graph.git --dissociate orig clone +' + test_done
diff --git a/t/t5620-backfill.sh b/t/t5620-backfill.sh index f3b5e39..d2ea68e 100755 --- a/t/t5620-backfill.sh +++ b/t/t5620-backfill.sh
@@ -15,6 +15,14 @@ test_grep "unrecognized argument: --unexpected-arg" err ' +test_expect_success 'backfill rejects incompatible filter options' ' + test_must_fail git backfill --objects --filter=tree:1 2>err && + test_grep "cannot backfill with these filter options" err && + + test_must_fail git backfill --objects --filter=blob:limit=10m 2>err && + test_grep "cannot backfill with blob size limits" err +' + # We create objects in the 'src' repo. test_expect_success 'setup repo for object creation' ' echo "{print \$1}" >print_1.awk && @@ -257,11 +265,12 @@ git -C backfill-revs rev-list --quiet --objects --missing=print HEAD >missing && test_line_count = 48 missing && - git -C backfill-revs backfill HEAD~2..HEAD && + GIT_TRACE2_EVENT="$(pwd)/backfill-trace" git -C backfill-revs backfill HEAD~2..HEAD && - # 30 objects downloaded. + # 36 objects downloaded, 12 still missing + test_trace2_data promisor fetch_count 36 <backfill-trace && git -C backfill-revs rev-list --quiet --objects --missing=print HEAD >missing && - test_line_count = 18 missing + test_line_count = 12 missing ' test_expect_success 'backfill with revisions over stdin' ' @@ -279,11 +288,12 @@ ^HEAD~2 EOF - git -C backfill-revs backfill --stdin <in && + GIT_TRACE2_EVENT="$(pwd)/backfill-trace" git -C backfill-revs backfill --stdin <in && - # 30 objects downloaded. + # 36 objects downloaded, 12 still missing + test_trace2_data promisor fetch_count 36 <backfill-trace && git -C backfill-revs rev-list --quiet --objects --missing=print HEAD >missing && - test_line_count = 18 missing + test_line_count = 12 missing ' test_expect_success 'backfill with prefix pathspec' ' @@ -398,6 +408,102 @@ test_line_count = 6 missing ' +test_expect_success 'backfill range with include-edges enables fetch-free git-log' ' + git clone --no-checkout --filter=blob:none \ + --single-branch --branch=main \ + "file://$(pwd)/srv.bare" backfill-log && + + # Backfill the range with default include edges. + git -C backfill-log backfill HEAD~2..HEAD && + + # git log -p needs edge blobs for the "before" side of + # diffs. With edge inclusion, all needed blobs are local. + GIT_TRACE2_EVENT="$(pwd)/log-trace" git \ + -C backfill-log log -p HEAD~2..HEAD >log-output && + + # No promisor fetches should have been needed. + ! grep "fetch_count" log-trace +' + +test_expect_success 'backfill range without include edges causes on-demand fetches in git-log' ' + git clone --no-checkout --filter=blob:none \ + --single-branch --branch=main \ + "file://$(pwd)/srv.bare" backfill-log-no-bdy && + + # Backfill WITHOUT include edges -- file.3 v1 blobs are missing. + git -C backfill-log-no-bdy backfill --no-include-edges HEAD~2..HEAD && + + # git log -p HEAD~2..HEAD computes diff of commit 7 against + # commit 6. It needs file.3 v1 (the "before" side), which was + # not backfilled. This triggers on-demand promisor fetches. + GIT_TRACE2_EVENT="$(pwd)/log-no-bdy-trace" git \ + -C backfill-log-no-bdy log -p HEAD~2..HEAD >log-output && + + grep "fetch_count" log-no-bdy-trace +' + +test_expect_success 'backfill range enables fetch-free replay' ' + # Create a repo with a branch to replay. + git init replay-src && + ( + cd replay-src && + git config uploadpack.allowfilter 1 && + git config uploadpack.allowanysha1inwant 1 && + test_commit base && + git checkout -b topic && + test_commit topic-change && + git checkout main && + test_commit main-change + ) && + git clone --bare --filter=blob:none \ + "file://$(pwd)/replay-src" replay-dest.git && + + # Backfill the replay range: --onto main, replaying topic~1..topic. + # For replay, we need TARGET^! plus the range. + main_oid=$(git -C replay-dest.git rev-parse main) && + topic_oid=$(git -C replay-dest.git rev-parse topic) && + base_oid=$(git -C replay-dest.git rev-parse topic~1) && + git -C replay-dest.git backfill \ + "$main_oid^!" "$base_oid..$topic_oid" && + + # Now replay should complete without any promisor fetches. + GIT_TRACE2_EVENT="$(pwd)/replay-trace" git -C replay-dest.git \ + replay --onto main topic~1..topic >replay-out && + + ! grep "fetch_count" replay-trace +' + +test_expect_success 'backfill enables fetch-free merge' ' + # Create a repo with two branches to merge. + git init merge-src && + ( + cd merge-src && + git config uploadpack.allowfilter 1 && + git config uploadpack.allowanysha1inwant 1 && + test_commit merge-base && + git checkout -b side && + test_commit side-change && + git checkout main && + test_commit main-side-change + ) && + git clone --filter=blob:none \ + "file://$(pwd)/merge-src" merge-dest && + + # The clone checked out main, fetching its blobs. + # Backfill the three endpoint commits needed for merge. + main_oid=$(git -C merge-dest rev-parse origin/main) && + side_oid=$(git -C merge-dest rev-parse origin/side) && + mbase=$(git -C merge-dest merge-base origin/main origin/side) && + git -C merge-dest backfill --no-include-edges \ + "$main_oid^!" "$side_oid^!" "$mbase^!" && + + # Merge should complete without promisor fetches. + GIT_TRACE2_EVENT="$(pwd)/merge-trace" git -C merge-dest \ + merge origin/side -m "test merge" && + + ! grep "fetch_count" merge-trace +' + . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd
diff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh index f826ac4..9f6cf41 100755 --- a/t/t5702-protocol-v2.sh +++ b/t/t5702-protocol-v2.sh
@@ -869,14 +869,14 @@ test_commit -C client three } -test_expect_success 'usage: --negotiate-only without --negotiation-tip' ' +test_expect_success 'usage: --negotiate-only without --negotiation-restrict' ' SERVER="server" && URI="file://$(pwd)/server" && setup_negotiate_only "$SERVER" "$URI" && cat >err.expect <<-\EOF && - fatal: --negotiate-only needs one or more --negotiation-tip=* + fatal: --negotiate-only needs one or more --negotiation-restrict=* EOF test_must_fail git -c protocol.version=2 -C client fetch \
diff --git a/t/t5710-promisor-remote-capability.sh b/t/t5710-promisor-remote-capability.sh index 357822c..b404ad9 100755 --- a/t/t5710-promisor-remote-capability.sh +++ b/t/t5710-promisor-remote-capability.sh
@@ -76,6 +76,31 @@ cp "$path" "$path2" } +# On Windows, `pwd` returns a path like 'D:/foo/bar'. Prepend '/' to turn +# it into '/D:/foo/bar', which is what git expects in file:// URLs on Windows. +# On Unix, the path already starts with '/', so this is a no-op. +pwd_path=$(pwd) +case "$pwd_path" in +[a-zA-Z]:*) pwd_path="/$pwd_path" ;; +esac + +# Allowed characters: alphanumeric, standard path/URI (_ . ~ / : -), +# and those percent-encoded below (% space = , ;) +rest=$(printf "%s" "$pwd_path" | tr -d 'a-zA-Z0-9_.~/:% =,;-') +if test -n "$rest" +then + skip_all="PWD contains unsupported special characters" + test_done +fi + +TRASH_DIRECTORY_URL="file://$pwd_path" + +encoded_path=$(printf "%s" "$pwd_path" | + sed -e 's/%/%25/g' -e 's/ /%20/g' -e 's/=/%3D/g' \ + -e 's/;/%3B/g' -e 's/,/%2C/g') + +ENCODED_TRASH_DIRECTORY_URL="file://$encoded_path" + test_expect_success "setup for testing promisor remote advertisement" ' # Create another bare repo called "lop" (for Large Object Promisor) git init --bare lop && @@ -88,7 +113,7 @@ initialize_server 1 "$oid" && # Configure lop as promisor remote for server - git -C server remote add lop "file://$(pwd)/lop" && + git -C server remote add lop "$TRASH_DIRECTORY_URL/lop" && git -C server config remote.lop.promisor true && git -C lop config uploadpack.allowFilter true && @@ -104,7 +129,7 @@ # Clone from server to create a client GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \ -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \ - -c remote.lop.url="file://$(pwd)/lop" \ + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \ -c promisor.acceptfromserver=All \ --no-local --filter="blob:limit=5k" server client && @@ -119,7 +144,7 @@ # Clone from server to create a client GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \ -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \ - -c remote.lop.url="file://$(pwd)/lop" \ + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \ -c promisor.acceptfromserver=All \ --no-local --filter="blob:limit=5k" server client && @@ -137,7 +162,7 @@ # Clone from server to create a client GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \ -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \ - -c remote.lop.url="file://$(pwd)/lop" \ + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \ -c promisor.acceptfromserver=None \ --no-local --filter="blob:limit=5k" server client && @@ -156,8 +181,8 @@ git -C client init && git -C client config remote.lop.promisor true && git -C client config remote.lop.fetch "+refs/heads/*:refs/remotes/lop/*" && - git -C client config remote.lop.url "file://$(pwd)/lop" && - git -C client config remote.server.url "file://$(pwd)/server" && + git -C client config remote.lop.url "$TRASH_DIRECTORY_URL/lop" && + git -C client config remote.server.url "$TRASH_DIRECTORY_URL/server" && git -C client config remote.server.fetch "+refs/heads/*:refs/remotes/server/*" && git -C client config promisor.acceptfromserver All && GIT_NO_LAZY_FETCH=0 git -C client fetch --filter="blob:limit=5k" server && @@ -166,6 +191,75 @@ check_missing_objects server 1 "$oid" ' +test_expect_success "clone with two promisors but only one advertised" ' + git -C server config promisor.advertise true && + test_when_finished "rm -rf client unused_lop" && + + # Create a promisor that will be configured but not be used + git init --bare unused_lop && + + # Clone from server to create a client + GIT_TRACE="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git clone \ + -c remote.unused_lop.promisor=true \ + -c remote.unused_lop.fetch="+refs/heads/*:refs/remotes/unused_lop/*" \ + -c remote.unused_lop.url="$TRASH_DIRECTORY_URL/unused_lop" \ + -c remote.lop.promisor=true \ + -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \ + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \ + -c promisor.acceptfromserver=All \ + --no-local --filter="blob:limit=5k" server client && + + # Check that "unused_lop" appears before "lop" in the config + printf "remote.%s.promisor true\n" "unused_lop" "lop" "origin" >expect && + git -C client config get --all --show-names --regexp "^remote\..*\.promisor$" >actual && + test_cmp expect actual && + + # Check that "lop" was tried + test_grep " fetch lop " trace && + # Check that "unused_lop" was not contacted + # This means "lop", the accepted promisor, was tried first + test_grep ! " fetch unused_lop " trace && + + # Check that the largest object is still missing on the server + check_missing_objects server 1 "$oid" +' + +test_expect_success "init + fetch two promisors but only one advertised" ' + git -C server config promisor.advertise true && + test_when_finished "rm -rf client unused_lop" && + + # Create a promisor that will be configured but not be used + git init --bare unused_lop && + + mkdir client && + git -C client init && + git -C client config remote.unused_lop.promisor true && + git -C client config remote.unused_lop.fetch "+refs/heads/*:refs/remotes/unused_lop/*" && + git -C client config remote.unused_lop.url "$TRASH_DIRECTORY_URL/unused_lop" && + git -C client config remote.lop.promisor true && + git -C client config remote.lop.fetch "+refs/heads/*:refs/remotes/lop/*" && + git -C client config remote.lop.url "$TRASH_DIRECTORY_URL/lop" && + git -C client config remote.server.url "$TRASH_DIRECTORY_URL/server" && + git -C client config remote.server.fetch "+refs/heads/*:refs/remotes/server/*" && + git -C client config promisor.acceptfromserver All && + + # Check that "unused_lop" appears before "lop" in the config + printf "remote.%s.promisor true\n" "unused_lop" "lop" >expect && + git -C client config get --all --show-names --regexp "^remote\..*\.promisor$" >actual && + test_cmp expect actual && + + GIT_TRACE="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git -C client fetch --filter="blob:limit=5k" server && + + # Check that "lop" was tried + test_grep " fetch lop " trace && + # Check that "unused_lop" was not contacted + # This means "lop", the accepted promisor, was tried first + test_grep ! " fetch unused_lop " trace && + + # Check that the largest object is still missing on the server + check_missing_objects server 1 "$oid" +' + test_expect_success "clone with promisor.acceptfromserver set to 'KnownName'" ' git -C server config promisor.advertise true && test_when_finished "rm -rf client" && @@ -173,7 +267,7 @@ # Clone from server to create a client GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \ -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \ - -c remote.lop.url="file://$(pwd)/lop" \ + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \ -c promisor.acceptfromserver=KnownName \ --no-local --filter="blob:limit=5k" server client && @@ -188,7 +282,7 @@ # Clone from server to create a client GIT_NO_LAZY_FETCH=0 git clone -c remote.serverTwo.promisor=true \ -c remote.serverTwo.fetch="+refs/heads/*:refs/remotes/lop/*" \ - -c remote.serverTwo.url="file://$(pwd)/lop" \ + -c remote.serverTwo.url="$TRASH_DIRECTORY_URL/lop" \ -c promisor.acceptfromserver=KnownName \ --no-local --filter="blob:limit=5k" server client && @@ -225,7 +319,7 @@ # Clone from server to create a client GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \ -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \ - -c remote.lop.url="file://$(pwd)/lop" \ + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \ -c promisor.acceptfromserver=KnownUrl \ --no-local --filter="blob:limit=5k" server client && @@ -242,7 +336,7 @@ # Clone from server to create a client GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \ -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \ - -c remote.lop.url="file://$(pwd)/serverTwo" \ + -c remote.lop.url="$TRASH_DIRECTORY_URL/serverTwo" \ -c promisor.acceptfromserver=KnownUrl \ --no-local --filter="blob:limit=5k" server client && @@ -257,7 +351,7 @@ git -C server config promisor.advertise true && test_when_finished "rm -rf client" && - test_when_finished "git -C server config set remote.lop.url \"file://$(pwd)/lop\"" && + test_when_finished "git -C server config set remote.lop.url \"$TRASH_DIRECTORY_URL/lop\"" && git -C server config unset remote.lop.url && # Clone from server to create a client @@ -266,7 +360,7 @@ # missing, so the remote name will be used instead which will fail. test_must_fail env GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \ -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \ - -c remote.lop.url="file://$(pwd)/lop" \ + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \ -c promisor.acceptfromserver=KnownUrl \ --no-local --filter="blob:limit=5k" server client && @@ -278,7 +372,7 @@ git -C server config promisor.advertise true && test_when_finished "rm -rf client" && - test_when_finished "git -C server config set remote.lop.url \"file://$(pwd)/lop\"" && + test_when_finished "git -C server config set remote.lop.url \"$TRASH_DIRECTORY_URL/lop\"" && git -C server config set remote.lop.url "" && # Clone from server to create a client @@ -287,7 +381,7 @@ # so the remote name will be used instead which will fail. test_must_fail env GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \ -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \ - -c remote.lop.url="file://$(pwd)/lop" \ + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \ -c promisor.acceptfromserver=KnownUrl \ --no-local --filter="blob:limit=5k" server client && @@ -311,13 +405,12 @@ GIT_TRACE_PACKET="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git clone \ -c remote.lop.promisor=true \ -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \ - -c remote.lop.url="file://$(pwd)/lop" \ + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \ -c promisor.acceptfromserver=All \ --no-local --filter="blob:limit=5k" server client && # Check that fields are properly transmitted - ENCODED_URL=$(echo "file://$(pwd)/lop" | sed -e "s/ /%20/g") && - PR1="name=lop,url=$ENCODED_URL,partialCloneFilter=blob:none" && + PR1="name=lop,url=$ENCODED_TRASH_DIRECTORY_URL/lop,partialCloneFilter=blob:none" && PR2="name=otherLop,url=https://invalid.invalid,partialCloneFilter=blob:limit=10k,token=fooBar" && test_grep "clone< promisor-remote=$PR1;$PR2" trace && test_grep "clone> promisor-remote=lop;otherLop" trace && @@ -342,15 +435,14 @@ GIT_TRACE_PACKET="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git clone \ -c remote.lop.promisor=true \ -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \ - -c remote.lop.url="file://$(pwd)/lop" \ + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \ -c remote.lop.partialCloneFilter="blob:none" \ -c promisor.acceptfromserver=All \ -c promisor.checkFields=partialcloneFilter \ --no-local --filter="blob:limit=5k" server client && # Check that fields are properly transmitted - ENCODED_URL=$(echo "file://$(pwd)/lop" | sed -e "s/ /%20/g") && - PR1="name=lop,url=$ENCODED_URL,partialCloneFilter=blob:none" && + PR1="name=lop,url=$ENCODED_TRASH_DIRECTORY_URL/lop,partialCloneFilter=blob:none" && PR2="name=otherLop,url=https://invalid.invalid,partialCloneFilter=blob:limit=10k,token=fooBar" && test_grep "clone< promisor-remote=$PR1;$PR2" trace && test_grep "clone> promisor-remote=lop" trace && @@ -380,7 +472,7 @@ GIT_TRACE_PACKET="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git clone \ -c remote.lop.promisor=true \ -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \ - -c remote.lop.url="file://$(pwd)/lop" \ + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \ -c remote.lop.token="fooYYY" \ -c remote.lop.partialCloneFilter="blob:none" \ -c promisor.acceptfromserver=All \ @@ -432,7 +524,7 @@ GIT_TRACE_PACKET="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git clone \ -c remote.lop.promisor=true \ - -c remote.lop.url="file://$(pwd)/lop" \ + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \ -c promisor.acceptfromserver=All \ --no-local --filter=auto server client 2>err && @@ -489,7 +581,7 @@ # Clone from server to create a client GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \ -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \ - -c remote.lop.url="file://$(pwd)/lop" \ + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \ -c promisor.acceptfromserver=All \ --no-local --filter="blob:limit=5k" server client &&
diff --git a/t/t6002-rev-list-bisect.sh b/t/t6002-rev-list-bisect.sh index daa009c..f2de40b 100755 --- a/t/t6002-rev-list-bisect.sh +++ b/t/t6002-rev-list-bisect.sh
@@ -27,13 +27,16 @@ # Test if bisection size is close to half of list size within # tolerance. # - _bisect_err=$(expr $_list_size - $_bisection_size \* 2) - test "$_bisect_err" -lt 0 && _bisect_err=$(expr 0 - $_bisect_err) - _bisect_err=$(expr $_bisect_err / 2) ; # floor + _bisect_err=$(($_list_size - $_bisection_size * 2)) + if test "$_bisect_err" -lt 0 + then + _bisect_err=$((0 - $_bisect_err)) + fi + _bisect_err=$(($_bisect_err / 2)) ; # floor - test_expect_success \ - "bisection diff $_bisect_option $_head $* <= $_max_diff" \ - 'test $_bisect_err -le $_max_diff' + test_expect_success "bisection diff $_bisect_option $_head $* <= $_max_diff" ' + test $_bisect_err -le $_max_diff + ' } date >path0
diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 1ba9ca2..0811162 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh
@@ -258,7 +258,7 @@ git bisect start $HASH4 $HASH1 && git bisect skip && git bisect bad > my_bisect_log.txt && - grep "$HASH2 is the first bad commit" my_bisect_log.txt + grep "$HASH2 is the first '\''bad'\'' commit" my_bisect_log.txt ' # $HASH1 is good, $HASH4 is bad, we skip $HASH3 and $HASH2 @@ -269,7 +269,7 @@ git bisect start $HASH4 $HASH1 && git bisect skip && test_expect_code 2 git bisect skip >my_bisect_log.txt && - grep "first bad commit could be any of" my_bisect_log.txt && + grep "first '\''bad'\'' commit could be any of" my_bisect_log.txt && ! grep $HASH1 my_bisect_log.txt && grep $HASH2 my_bisect_log.txt && grep $HASH3 my_bisect_log.txt && @@ -285,7 +285,7 @@ git bisect start $HASH4 $HASH1 && git bisect skip && test_expect_code 2 git bisect good >my_bisect_log.txt && - grep "first bad commit could be any of" my_bisect_log.txt && + grep "first '\''bad'\'' commit could be any of" my_bisect_log.txt && ! grep $HASH1 my_bisect_log.txt && ! grep $HASH2 my_bisect_log.txt && grep $HASH3 my_bisect_log.txt && @@ -304,7 +304,7 @@ git bisect good $HASH1 && git bisect skip && test_expect_code 2 git bisect good >my_bisect_log.txt && - grep "first bad commit could be any of" my_bisect_log.txt && + grep "first '\''bad'\'' commit could be any of" my_bisect_log.txt && ! grep $HASH1 my_bisect_log.txt && ! grep $HASH2 my_bisect_log.txt && grep $HASH3 my_bisect_log.txt && @@ -348,8 +348,8 @@ test_bisect_run_args <<-'EOF_ARGS' 6<<-EOF_OUT 7<<-'EOF_ERR' EOF_ARGS running './run.sh' - $HASH4 is the first bad commit - bisect found first bad commit + $HASH4 is the first 'bad' commit + bisect found first 'bad' commit EOF_OUT EOF_ERR " @@ -359,8 +359,8 @@ <--> EOF_ARGS running './run.sh' '--' - $HASH4 is the first bad commit - bisect found first bad commit + $HASH4 is the first 'bad' commit + bisect found first 'bad' commit EOF_OUT EOF_ERR " @@ -373,8 +373,8 @@ <bar> EOF_ARGS running './run.sh' '--log' 'foo' '--no-log' 'bar' - $HASH4 is the first bad commit - bisect found first bad commit + $HASH4 is the first 'bad' commit + bisect found first 'bad' commit EOF_OUT EOF_ERR " @@ -384,8 +384,8 @@ <--bisect-start> EOF_ARGS running './run.sh' '--bisect-start' - $HASH4 is the first bad commit - bisect found first bad commit + $HASH4 is the first 'bad' commit + bisect found first 'bad' commit EOF_OUT EOF_ERR " @@ -418,7 +418,7 @@ fi EOF cat <<-'EOF' >expect && - unable to verify './fail.sh' on good revision + unable to verify './fail.sh' on 'good' revision EOF test_when_finished 'git bisect reset' && git bisect start && @@ -439,7 +439,7 @@ git bisect good $HASH1 && git bisect bad $HASH4 && git bisect run ./test_script.sh >my_bisect_log.txt && - grep "$HASH3 is the first bad commit" my_bisect_log.txt && + grep "$HASH3 is the first '\''bad'\'' commit" my_bisect_log.txt && git bisect reset ' @@ -461,7 +461,7 @@ EOF git bisect start $HASH4 $HASH1 && git bisect run ./test_script.sh >my_bisect_log.txt && - grep "$HASH4 is the first bad commit" my_bisect_log.txt && + grep "$HASH4 is the first '\''bad'\'' commit" my_bisect_log.txt && git bisect reset ' @@ -474,7 +474,7 @@ git bisect good $HASH1 && git bisect bad $HASH4 && git bisect run ./test_script.sh >my_bisect_log.txt && - grep "$HASH3 is the first bad commit" my_bisect_log.txt + grep "$HASH3 is the first '\''bad'\'' commit" my_bisect_log.txt ' test_expect_success POSIXPERM 'bisect run fails with non-executable test script' ' @@ -485,7 +485,7 @@ git bisect good $HASH1 && git bisect bad $HASH4 && test_must_fail git bisect run ./not-executable.sh >my_bisect_log.txt && - ! grep "is the first bad commit" my_bisect_log.txt + ! grep "is the first '\''bad'\'' commit" my_bisect_log.txt ' test_expect_success 'bisect run accepts exit code 127 as bad' ' @@ -497,7 +497,7 @@ git bisect good $HASH1 && git bisect bad $HASH4 && git bisect run ./test_script.sh >my_bisect_log.txt && - grep "$HASH3 is the first bad commit" my_bisect_log.txt + grep "$HASH3 is the first '\''bad'\'' commit" my_bisect_log.txt ' test_expect_success 'bisect run fails with missing test script' ' @@ -507,7 +507,7 @@ git bisect good $HASH1 && git bisect bad $HASH4 && test_must_fail git bisect run ./does-not-exist.sh >my_bisect_log.txt && - ! grep "is the first bad commit" my_bisect_log.txt + ! grep "is the first '\''bad'\'' commit" my_bisect_log.txt ' # $HASH1 is good, $HASH5 is bad, we skip $HASH3 @@ -520,14 +520,14 @@ git bisect start $HASH5 $HASH1 && git bisect skip && git bisect good > my_bisect_log.txt && - grep "$HASH5 is the first bad commit" my_bisect_log.txt && + grep "$HASH5 is the first '\''bad'\'' commit" my_bisect_log.txt && git bisect log > log_to_replay.txt && git bisect reset ' test_expect_success 'bisect skip and bisect replay' ' git bisect replay log_to_replay.txt > my_bisect_log.txt && - grep "$HASH5 is the first bad commit" my_bisect_log.txt && + grep "$HASH5 is the first '\''bad'\'' commit" my_bisect_log.txt && git bisect reset ' @@ -541,7 +541,7 @@ EOF git bisect start $HASH6 $HASH1 && test_expect_code 2 git bisect run ./test_script.sh >my_bisect_log.txt && - grep "first bad commit could be any of" my_bisect_log.txt && + grep "first '\''bad'\'' commit could be any of" my_bisect_log.txt && ! grep $HASH3 my_bisect_log.txt && ! grep $HASH6 my_bisect_log.txt && grep $HASH4 my_bisect_log.txt && @@ -560,7 +560,7 @@ EOF git bisect start $HASH7 $HASH1 && git bisect run ./test_script.sh >my_bisect_log.txt && - grep "$HASH6 is the first bad commit" my_bisect_log.txt + grep "$HASH6 is the first '\''bad'\'' commit" my_bisect_log.txt ' test_expect_success 'bisect skip only one range' ' @@ -569,7 +569,7 @@ git bisect skip $HASH1..$HASH5 && test "$HASH6" = "$(git rev-parse --verify HEAD)" && test_must_fail git bisect bad > my_bisect_log.txt && - grep "first bad commit could be any of" my_bisect_log.txt + grep "first '\''bad'\'' commit could be any of" my_bisect_log.txt ' test_expect_success 'bisect skip many ranges' ' @@ -578,7 +578,7 @@ git bisect skip $HASH2 $HASH2.. ..$HASH5 && test "$HASH6" = "$(git rev-parse --verify HEAD)" && test_must_fail git bisect bad > my_bisect_log.txt && - grep "first bad commit could be any of" my_bisect_log.txt + grep "first '\''bad'\'' commit could be any of" my_bisect_log.txt ' test_expect_success 'bisect starting with a detached HEAD' ' @@ -594,7 +594,7 @@ test_expect_success 'bisect errors out if bad and good are mistaken' ' git bisect reset && test_must_fail git bisect start $HASH2 $HASH4 2> rev_list_error && - test_grep "mistook good and bad" rev_list_error && + test_grep "mistook '\''good'\'' and '\''bad'\''" rev_list_error && git bisect reset ' @@ -610,7 +610,7 @@ rev_hash6=$(git rev-parse --verify HEAD) && test "$rev_hash6" = "$HASH6" && git bisect good > my_bisect_log.txt && - grep "$HASH7 is the first bad commit" my_bisect_log.txt && + grep "$HASH7 is the first '\''bad'\'' commit" my_bisect_log.txt && git bisect reset && rev_hash6=$(git rev-parse --verify bisect) && test "$rev_hash6" = "$HASH6" && @@ -703,7 +703,7 @@ git bisect good $HASH4 && git bisect bad $B_HASH && git bisect run ./test_script.sh >my_bisect_log.txt && - grep "$B_HASH is the first bad commit" my_bisect_log.txt && + grep "$B_HASH is the first '\''bad'\'' commit" my_bisect_log.txt && git bisect reset && test_path_is_missing .git/BISECT_FIRST_PARENT ' @@ -777,7 +777,7 @@ para1=$(git rev-parse --verify HEAD) && test "$para1" = "$PARA_HASH1" && git bisect bad > my_bisect_log.txt && - grep "$PARA_HASH1 is the first bad commit" my_bisect_log.txt + grep "$PARA_HASH1 is the first '\''bad'\'' commit" my_bisect_log.txt ' test_expect_success 'restricting bisection on one dir and a file' ' @@ -795,7 +795,7 @@ para1=$(git rev-parse --verify HEAD) && test "$para1" = "$PARA_HASH1" && git bisect good > my_bisect_log.txt && - grep "$PARA_HASH4 is the first bad commit" my_bisect_log.txt + grep "$PARA_HASH4 is the first '\''bad'\'' commit" my_bisect_log.txt ' test_expect_success 'skipping away from skipped commit' ' @@ -826,7 +826,7 @@ "test \$(git rev-list BISECT_HEAD ^$HASH2 --max-count=1 | wc -l) = 0" \ >../nocheckout.log ) && - grep "$HASH3 is the first bad commit" nocheckout.log + grep "$HASH3 is the first '\''bad'\'' commit" nocheckout.log ' @@ -841,7 +841,7 @@ "test \$(git rev-list BISECT_HEAD ^$HASH2 --max-count=1 | wc -l) = 0" \ >../defaulted.log ) && - grep "$HASH3 is the first bad commit" defaulted.log + grep "$HASH3 is the first '\''bad'\'' commit" defaulted.log ' # @@ -969,7 +969,7 @@ git bisect start '$HASH4' '$HASH2' # good: [$HASH3] Add <3: Another new day for git> into <hello>. git bisect good $HASH3 -# first bad commit: [$HASH4] Add <4: Ciao for now> into <hello>. +# first 'bad' commit: [$HASH4] Add <4: Ciao for now> into <hello>. EOF test_expect_success 'bisect log: successful result' ' @@ -988,8 +988,8 @@ # skip: [$HASH3] Add <3: Another new day for git> into <hello>. git bisect skip $HASH3 # only skipped commits left to test -# possible first bad commit: [$HASH4] Add <4: Ciao for now> into <hello>. -# possible first bad commit: [$HASH3] Add <3: Another new day for git> into <hello>. +# possible first 'bad' commit: [$HASH4] Add <4: Ciao for now> into <hello>. +# possible first 'bad' commit: [$HASH3] Add <3: Another new day for git> into <hello>. EOF test_expect_success 'bisect log: only skip commits left' ' @@ -1031,21 +1031,21 @@ git bisect new $HASH4 && git bisect new && git bisect new >bisect_result && - grep "$HASH2 is the first new commit" bisect_result && + grep "$HASH2 is the first '\''new'\'' commit" bisect_result && git bisect log >log_to_replay.txt && git bisect reset ' test_expect_success 'bisect replay with old and new' ' git bisect replay log_to_replay.txt >bisect_result && - grep "$HASH2 is the first new commit" bisect_result && + grep "$HASH2 is the first '\''new'\'' commit" bisect_result && git bisect reset ' test_expect_success 'bisect replay with CRLF log' ' append_cr <log_to_replay.txt >log_to_replay_crlf.txt && git bisect replay log_to_replay_crlf.txt >bisect_result_crlf && - grep "$HASH2 is the first new commit" bisect_result_crlf && + grep "$HASH2 is the first '\''new'\'' commit" bisect_result_crlf && git bisect reset ' @@ -1077,12 +1077,14 @@ test_expect_success 'bisect start with one term1 and term2' ' git bisect reset && - git bisect start --term-old term2 --term-new term1 && - git bisect term2 $HASH1 && + git bisect start --term-old term2 --term-new term1 >bisect_result && + test_grep "status: waiting for both '\''term2'\'' and '\''term1'\'' commits" bisect_result && + git bisect term2 $HASH1 >bisect_result && + test_grep "status: waiting for '\''term1'\'' commit, 1 '\''term2'\'' commit known" bisect_result && git bisect term1 $HASH4 && git bisect term1 && git bisect term1 >bisect_result && - grep "$HASH2 is the first term1 commit" bisect_result && + test_grep "$HASH2 is the first '\''term1'\'' commit" bisect_result && git bisect log >log_to_replay.txt && git bisect reset ' @@ -1099,7 +1101,17 @@ test_expect_success 'bisect replay with term1 and term2' ' git bisect replay log_to_replay.txt >bisect_result && - grep "$HASH2 is the first term1 commit" bisect_result && + grep "$HASH2 is the first '\''term1'\'' commit" bisect_result && + git bisect reset +' + +test_expect_success 'bisect run term1 term2' ' + git bisect reset && + git bisect start --term-new term1 --term-old term2 $HASH4 $HASH1 && + git bisect term1 && + git bisect run false >bisect_result && + test_grep "bisect found first '\''term1'\'' commit" bisect_result && + git bisect log >log_to_replay.txt && git bisect reset ' @@ -1108,7 +1120,7 @@ git bisect start --term-new term1 --term-old term2 $HASH4 $HASH1 && git bisect term1 && git bisect term1 >bisect_result && - grep "$HASH2 is the first term1 commit" bisect_result && + grep "$HASH2 is the first '\''term1'\'' commit" bisect_result && git bisect log >log_to_replay.txt && git bisect reset ' @@ -1142,8 +1154,8 @@ git bisect start --term-bad=one --term-good=two && git bisect terms >actual && cat <<-EOF >expected && - Your current terms are two for the old state - and one for the new state. + Your current terms are '\''two'\'' for the old state + and '\''one'\'' for the new state. EOF test_cmp expected actual && git bisect terms --term-bad >actual && @@ -1200,7 +1212,7 @@ git bisect good tag-one && git bisect bad tag-two >output && bad=$(git rev-parse --verify tag-two^{commit}) && - grep "$bad is the first bad commit" output + grep "$bad is the first '\''bad'\'' commit" output ' test_expect_success 'bisect run fails with exit code equals or greater than 128' ' @@ -1224,29 +1236,29 @@ test_expect_success 'bisect state output with multiple good commits' ' git bisect reset && git bisect start >output && - grep "waiting for both good and bad commits" output && + grep "waiting for both '\''good'\'' and '\''bad'\'' commits" output && git bisect log >output && - grep "waiting for both good and bad commits" output && + grep "waiting for both '\''good'\'' and '\''bad'\'' commits" output && git bisect good "$HASH1" >output && - grep "waiting for bad commit, 1 good commit known" output && + grep "waiting for '\''bad'\'' commit, 1 '\''good'\'' commit known" output && git bisect log >output && - grep "waiting for bad commit, 1 good commit known" output && + grep "waiting for '\''bad'\'' commit, 1 '\''good'\'' commit known" output && git bisect good "$HASH2" >output && - grep "waiting for bad commit, 2 good commits known" output && + grep "waiting for '\''bad'\'' commit, 2 '\''good'\'' commits known" output && git bisect log >output && - grep "waiting for bad commit, 2 good commits known" output + grep "waiting for '\''bad'\'' commit, 2 '\''good'\'' commits known" output ' test_expect_success 'bisect state output with bad commit' ' git bisect reset && git bisect start >output && - grep "waiting for both good and bad commits" output && + grep "waiting for both '\''good'\'' and '\''bad'\'' commits" output && git bisect log >output && - grep "waiting for both good and bad commits" output && + grep "waiting for both '\''good'\'' and '\''bad'\'' commits" output && git bisect bad "$HASH4" >output && - grep -F "waiting for good commit(s), bad commit known" output && + grep -F "waiting for '\''good'\'' commit(s), '\''bad'\'' commit known" output && git bisect log >output && - grep -F "waiting for good commit(s), bad commit known" output + grep -F "waiting for '\''good'\'' commit(s), '\''bad'\'' commit known" output ' test_expect_success 'verify correct error message' '
diff --git a/t/t6112-rev-list-filters-objects.sh b/t/t6112-rev-list-filters-objects.sh index 39211ef..e0a825b 100755 --- a/t/t6112-rev-list-filters-objects.sh +++ b/t/t6112-rev-list-filters-objects.sh
@@ -623,9 +623,9 @@ omitted_2=$(echo a | git hash-object --stdin) && omitted_3=$(echo abcde | git hash-object --stdin) && - grep ~$omitted_1 actual && - grep ~$omitted_2 actual && - grep ~$omitted_3 actual && + grep "~$omitted_1" actual && + grep "~$omitted_2" actual && + grep "~$omitted_3" actual && test_line_count = 3 actual '
diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index 2c70cc5..4d72033 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh
@@ -298,6 +298,20 @@ test_cmp expect actual ' +test_expect_success 'name-rev --annotate-stdin --name-only' ' + >expect.unsorted && + for rev in $(git rev-list --all) + do + name=$(git name-rev --name-only $rev) && + echo "$name" >>expect.unsorted || return 1 + done && + sort <expect.unsorted >expect && + git name-rev --annotate-stdin --name-only \ + <list >actual.unsorted && + sort <actual.unsorted >actual && + test_cmp expect actual +' + test_expect_success 'name-rev --stdin deprecated' ' git rev-list --all >list && if ! test_have_prereq WITH_BREAKING_CHANGES @@ -345,6 +359,28 @@ test_cmp expect actual ' +test_expect_success 'describe --contains --all --match no matching commit' ' + echo "tags/A^0" >expect && + tagged_commit=$(git rev-parse "refs/tags/A^0") && + test_must_fail git describe --contains --all --match="B" $tagged_commit +' + +check_describe "tags/A^0" --contains --all --match="A" $(git rev-parse "refs/tags/A^0") + +check_describe "branch_A" --contains --all --match="branch*" $(git rev-parse "refs/tags/A^0") + +check_describe "branch_C~1" --contains --all --match="branch*" --exclude="branch_A" $(git rev-parse "refs/tags/A^0") + +check_describe "branch_A" --contains --all \ + --exclude="A" --exclude="c" --exclude="test*" --exclude="origin/remote_branch_A" \ + $(git rev-parse "refs/tags/A^0") + +check_describe "remotes/origin/remote_branch_A" --contains --all --match="origin/remote*" $(git rev-parse "refs/tags/A^0") + +check_describe "remotes/origin/remote_branch_C~1" --contains --all \ + --match="origin/remote*" --exclude="origin/remote_branch_A" \ + $(git rev-parse "refs/tags/A^0") + test_expect_success 'setup and absorb a submodule' ' test_create_repo sub1 && test_commit -C sub1 initial && @@ -787,4 +823,198 @@ test_must_fail git cat-file -t "refs/tags/super-invalid/./../...../ ~^:/?*[////\\\\\\&}/busted.lock-42-g"$(cat out) ' +test_expect_success 'setup: format-rev' ' + mkdir repo-format && + git -C repo-format init && + test_commit -C repo-format first && + test_commit -C repo-format second && + test_commit -C repo-format third && + test_commit -C repo-format fourth && + test_commit -C repo-format fifth && + test_commit -C repo-format sixth && + test_commit -C repo-format seventh && + test_commit -C repo-format eighth +' + +test_expect_success 'format-rev --stdin-mode=revs' ' + cat >expect <<-\EOF && + eighth + seventh + fifth + EOF + git -C repo-format format-rev --stdin-mode=revs \ + --format=%s >actual <<-\EOF && + HEAD + HEAD~ + HEAD~3 + EOF + test_cmp expect actual +' + +test_expect_success 'format-rev --stdin-mode=text from rev-list same as log' ' + git -C repo-format log --format=reference >expect && + test_file_not_empty expect && + git -C repo-format rev-list HEAD >list && + git -C repo-format format-rev --stdin-mode=text \ + --format=reference <list >actual && + test_cmp expect actual +' + +test_expect_success 'format-rev --stdin-mode=text with running text and tree oid' ' + cmit_oid=$(git -C repo-format rev-parse fifth) && + reference=$(git -C repo-format log -n1 --format=reference fifth) && + tree=$(git -C repo-format rev-parse HEAD^{tree}) && + cat >expect <<-EOF && + We thought we fixed this in ${reference}. + But look at this tree: ${tree}. + EOF + git -C repo-format format-rev --stdin-mode=text --format=reference \ + >actual <<-EOF && + We thought we fixed this in ${cmit_oid}. + But look at this tree: ${tree}. + EOF + test_cmp expect actual +' + +test_expect_success 'format-rev with %N (note)' ' + test_when_finished "git -C repo-format notes remove" && + git -C repo-format notes add -m"Make a note" && + printf "Make a note\n\n\n" >expect && + git -C repo-format format-rev --stdin-mode=revs \ + --format="tformat:%N" \ + >actual <<-\EOF && + HEAD + HEAD~ + EOF + test_cmp expect actual +' + +test_expect_success 'format-rev --notes<ref> (custom notes ref)' ' + # One custom notes ref + test_when_finished "git -C repo-format notes remove" && + test_when_finished "git -C repo-format notes --ref=word remove" && + git -C repo-format notes add -m"default" && + git -C repo-format notes --ref=word add -m"custom" && + printf "custom\n\n" >expect && + git -C repo-format format-rev --stdin-mode=revs \ + --format="tformat:%N" \ + --notes=word \ + >actual <<-\EOF && + HEAD + EOF + test_cmp expect actual && + # Glob all + printf "default\ncustom\n\n" >expect && + git -C repo-format format-rev --stdin-mode=revs \ + --format="tformat:%N" \ + --notes=* >actual <<-\EOF && + HEAD + EOF + test_cmp expect actual +' + +test_expect_success 'format-rev --stdin-mode=revs on annotated tag peels to commit' ' + test_when_finished "git -C repo-format tag -d version" && + git -C repo-format tag -a -m"new version" version && + cat >expect <<-\EOF && + eighth + EOF + git -C repo-format format-rev --stdin-mode=revs \ + --format=%s \ + >actual <<-\EOF && + version + EOF + test_cmp expect actual +' + +test_expect_success 'format-rev --stdin-mode=revs lookup failures' ' + test_when_finished "git -C repo-format tag -d tag-to-tree" && + invalid_syntax=not-valid && + non_existing_oid=${EMPTY_BLOB} && + tree=$(git -C repo-format rev-parse eighth^{tree}) && + git -C repo-format tag -a -mmessage tag-to-tree "$tree" && + tag_to_tree=$(git -C repo-format rev-parse tag-to-tree) && + cat >expect <<-EOF && + Could not get object name for ${invalid_syntax}. Skipping. + Could not get object for ${non_existing_oid}. Skipping. + Could not get commit for ${tree}. Skipping. + Could not get commit for ${tag_to_tree}. Skipping. + EOF + git -C repo-format format-rev --stdin-mode=revs \ + --format=%s \ + 2>actual >out <<-EOF && + ${invalid_syntax} + ${non_existing_oid} + ${tree} + ${tag_to_tree} + EOF + test_line_count = 0 out && + test_cmp expect actual +' + + +test_expect_success 'format-rev -z --stdin-mode=text with object name lookup failures' ' + printf "%s\0" "$(git -C repo-format rev-parse HEAD)" >input && + printf "%s\0" "$(git -C repo-format rev-parse HEAD^{tree})" >>input && + printf "%s\0" "$EMPTY_BLOB" >>input && + printf "%s\0" "$(git -C repo-format log --format=%s -1)" >expect && + printf "%s\0" "$(git -C repo-format rev-parse HEAD^{tree})" >>expect && + printf "%s\0" "$EMPTY_BLOB" >>expect && + git -C repo-format format-rev --stdin-mode=text \ + --format=%s -z <input >actual && + test_cmp expect actual +' + +test_expect_success 'setup: format-rev input and output separators' ' + git -C repo-format rev-list HEAD >input-lf && + git -C repo-format rev-list -z HEAD >input-nul && + git -C repo-format log --format=%s >output-lf && + git -C repo-format log -z --format=%s >output-nul && + echo revs >stdin-modes && + echo text >>stdin-modes +' + +while read mode +do + test_expect_success "format-rev -z --stdin-mode=$mode" ' + cat output-nul >expect && + git -C repo-format format-rev --stdin-mode="$mode" \ + --format=%s -z <input-nul >actual && + test_cmp expect actual + ' + + test_expect_success "format-rev -z --no-null-input --no-null-output --stdin-mode=$mode" ' + cat output-lf >expect && + git -C repo-format format-rev --stdin-mode="$mode" \ + --format=%s -z --no-null-input --no-null-output \ + <input-lf >actual && + test_cmp expect actual + ' + + test_expect_success "format-rev ---null-input --stdin-mode=$mode" ' + cat output-lf >expect && + git -C repo-format format-rev --stdin-mode="$mode" \ + --format=%s --null-input \ + <input-nul >actual && + test_cmp expect actual + ' + + test_expect_success "format-rev --null-output --stdin-mode=$mode" ' + cat output-nul >expect && + git -C repo-format format-rev --stdin-mode="$mode" \ + --format=%s --null-output \ + <input-lf >actual && + test_cmp expect actual + ' + + test_expect_success "format-rev -z --stdin-mode=$mode with multi-line output" ' + format="%s%n%aI" && + git -C repo-format log -z --format="$format" \ + >expect && + git -C repo-format format-rev --stdin-mode="$mode" \ + --format="$format" -z <input-nul >actual && + test_cmp expect actual + ' +done <stdin-modes + test_done
diff --git a/t/t6408-merge-up-to-date.sh b/t/t6408-merge-up-to-date.sh index 7763c1b..be0840e 100755 --- a/t/t6408-merge-up-to-date.sh +++ b/t/t6408-merge-up-to-date.sh
@@ -89,4 +89,14 @@ test "$expect" = "$current" ' +test_expect_success 'merge octopus already up to date' ' + + git reset --hard c2 && + test_tick && + git merge c0 c1 && + expect=$(git rev-parse c2) && + current=$(git rev-parse HEAD) && + test "$expect" = "$current" +' + test_done
diff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh index 15dd2d9..56ee968 100755 --- a/t/t6429-merge-sequence-rename-caching.sh +++ b/t/t6429-merge-sequence-rename-caching.sh
@@ -846,4 +846,64 @@ ) ' +# +# In the following testcase: +# Base: subdir/file_1 +# Upstream: file_1 (renamed from subdir/file) +# Topic_1: subdir/file_2 (modified subdir/file) +# Topic_2: subdir/file_2, file_2 (added another "file" with same contents) +# Topic_3: file_2 (deleted subdir/file) +# +# +# This testcase presents no problems for git traditionally, but the fact that +# subdir/file -> file +# gets cached after the first pick presents a problem for the third commit +# to be replayed, because file has contents file_2 on all three sides and +# is thus trivially resolved early. The point of renames is to allow us to +# three-way merge contents across multiple filenames, but if the target is +# already resolved, we risk throwing an assertion. Verify that the code +# correctly drops the irrelevant rename in order to avoid hitting that +# assertion. +# +test_expect_success 'cached rename does not assert on trivially clean target' ' + git init cached-rename-trivially-clean-target && + ( + cd cached-rename-trivially-clean-target && + + mkdir subdir && + printf "%s\n" 1 2 3 >subdir/file && + git add subdir/file && + git commit -m orig && + + git branch upstream && + git branch topic && + + git switch upstream && + git mv subdir/file file && + git commit -m "rename subdir/file to file" && + + git switch topic && + + echo 4 >>subdir/file && + git add subdir/file && + git commit -m "modify subdir/file" && + + cp subdir/file file && + git add file && + git commit -m "copy subdir/file to file" && + + git rm subdir/file && + git commit -m "delete subdir/file" && + + git switch upstream && + git replay --onto HEAD upstream..topic && + git checkout topic && + + git ls-files >tracked-files && + test_line_count = 1 tracked-files && + printf "%s\n" 1 2 3 4 >expect && + test_cmp expect file + ) +' + test_done
diff --git a/t/t6600-test-reach.sh b/t/t6600-test-reach.sh index dc0421e..b5b314e 100755 --- a/t/t6600-test-reach.sh +++ b/t/t6600-test-reach.sh
@@ -612,6 +612,51 @@ --format="%(refname)" --stdin ' +test_expect_success 'for-each-ref merged:duplicate, all reachable' ' + git branch dup-a commit-3-3 && + git branch dup-b commit-3-3 && + cat >input <<-\EOF && + refs/heads/commit-1-1 + refs/heads/dup-a + refs/heads/dup-b + EOF + cat >expect <<-\EOF && + refs/heads/commit-1-1 + refs/heads/dup-a + refs/heads/dup-b + EOF + run_all_modes git for-each-ref --merged=commit-5-5 \ + --format="%(refname)" --stdin +' + +test_expect_success 'for-each-ref merged:duplicate, none reachable' ' + cat >input <<-\EOF && + refs/heads/dup-a + refs/heads/dup-b + refs/heads/commit-9-9 + EOF + >expect && + run_all_modes git for-each-ref --merged=commit-2-2 \ + --format="%(refname)" --stdin +' + +test_expect_success 'for-each-ref merged:duplicate at min generation' ' + git branch dup-c commit-1-1 && + git branch dup-d commit-1-1 && + cat >input <<-\EOF && + refs/heads/dup-c + refs/heads/dup-d + refs/heads/commit-5-5 + EOF + cat >expect <<-\EOF && + refs/heads/commit-5-5 + refs/heads/dup-c + refs/heads/dup-d + EOF + run_all_modes git for-each-ref --merged=commit-5-5 \ + --format="%(refname)" --stdin +' + # For get_branch_base_for_tip, we only care about # first-parent history. Here is the test graph with # second parents removed: @@ -882,4 +927,44 @@ test_cmp expect.sorted actual.sorted ' +# The following tests verify the early-exit optimisation in +# paint_down_to_common when merge-base is invoked without --all. +# Each test checks all four commit-graph configurations. + +merge_base_all_modes () { + test_when_finished rm -rf .git/objects/info/commit-graph && + git merge-base "$@" >actual && + test_cmp expect actual && + cp commit-graph-full .git/objects/info/commit-graph && + git merge-base "$@" >actual && + test_cmp expect actual && + cp commit-graph-half .git/objects/info/commit-graph && + git merge-base "$@" >actual && + test_cmp expect actual && + cp commit-graph-no-gdat .git/objects/info/commit-graph && + git merge-base "$@" >actual && + test_cmp expect actual +} + +test_expect_success 'merge-base without --all (unique base)' ' + git rev-parse commit-5-3 >expect && + merge_base_all_modes commit-5-7 commit-8-3 +' + +test_expect_success 'merge-base without --all is one of --all results' ' + test_when_finished rm -rf .git/objects/info/commit-graph && + + cp commit-graph-full .git/objects/info/commit-graph && + git merge-base --all commit-5-7 commit-4-8 commit-6-6 commit-8-3 >all && + git merge-base commit-5-7 commit-4-8 commit-6-6 commit-8-3 >single && + test_line_count = 1 single && + grep -F -f single all && + + cp commit-graph-half .git/objects/info/commit-graph && + git merge-base --all commit-5-7 commit-4-8 commit-6-6 commit-8-3 >all && + git merge-base commit-5-7 commit-4-8 commit-6-6 commit-8-3 >single && + test_line_count = 1 single && + grep -F -f single all +' + test_done
diff --git a/t/t6601-path-walk.sh b/t/t6601-path-walk.sh index 56bd1e3..e9fcd85 100755 --- a/t/t6601-path-walk.sh +++ b/t/t6601-path-walk.sh
@@ -7,17 +7,15 @@ test_expect_success 'setup test repository' ' git checkout -b base && - # Make some objects that will only be reachable - # via non-commit tags. - mkdir child && - echo file >child/file && - git add child && - git commit -m "will abandon" && - git tag -a -m "tree" tree-tag HEAD^{tree} && - echo file2 >file2 && - git add file2 && - git commit --amend -m "will abandon" && - git tag tree-tag2 HEAD^{tree} && + # Create tree objects that are only reachable via tags, + # not from any commit in the history. + child_blob_oid=$(echo "child blob content" | git hash-object -t blob -w --stdin) && + child_tree_oid=$(printf "100644 blob %s\tfile\n" "$child_blob_oid" | git mktree) && + tree_tag_oid=$(printf "040000 tree %s\tchild\n" "$child_tree_oid" | git mktree) && + git tag -a -m "tree" tree-tag "$tree_tag_oid" && + file2_blob_oid=$(echo "tagged tree file2" | git hash-object -t blob -w --stdin) && + tree_tag2_oid=$(printf "040000 tree %s\tchild\n100644 blob %s\tfile2\n" "$child_tree_oid" "$file2_blob_oid" | git mktree) && + git tag tree-tag2 "$tree_tag2_oid" && echo blob >file && blob_oid=$(git hash-object -t blob -w --stdin <file) && @@ -26,7 +24,7 @@ blob2_oid=$(git hash-object -t blob -w --stdin <file2) && git tag blob-tag2 "$blob2_oid" && - rm -fr child file file2 && + rm -fr file file2 && mkdir left && mkdir right && @@ -34,7 +32,7 @@ echo b >left/b && echo c >right/c && git add . && - git commit --amend -m "first" && + git commit -m "first" && git tag -m "first" first HEAD && echo d >right/d && @@ -79,23 +77,23 @@ 3:tree::$(git rev-parse base^{tree}) 3:tree::$(git rev-parse base~1^{tree}) 3:tree::$(git rev-parse base~2^{tree}) - 3:tree::$(git rev-parse refs/tags/tree-tag^{}) - 3:tree::$(git rev-parse refs/tags/tree-tag2^{}) 4:blob:a:$(git rev-parse base~2:a) - 5:blob:file2:$(git rev-parse refs/tags/tree-tag2^{}:file2) - 6:tree:a/:$(git rev-parse base:a) - 7:tree:child/:$(git rev-parse refs/tags/tree-tag:child) - 8:blob:child/file:$(git rev-parse refs/tags/tree-tag:child/file) - 9:tree:left/:$(git rev-parse base:left) - 9:tree:left/:$(git rev-parse base~2:left) - 10:blob:left/b:$(git rev-parse base~2:left/b) - 10:blob:left/b:$(git rev-parse base:left/b) - 11:tree:right/:$(git rev-parse topic:right) - 11:tree:right/:$(git rev-parse base~1:right) - 11:tree:right/:$(git rev-parse base~2:right) - 12:blob:right/c:$(git rev-parse base~2:right/c) - 12:blob:right/c:$(git rev-parse topic:right/c) - 13:blob:right/d:$(git rev-parse base~1:right/d) + 5:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag^{}) + 5:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag2^{}) + 6:blob:file2:$(git rev-parse refs/tags/tree-tag2^{}:file2) + 7:tree:a/:$(git rev-parse base:a) + 8:tree:child/:$(git rev-parse refs/tags/tree-tag:child) + 9:blob:child/file:$(git rev-parse refs/tags/tree-tag:child/file) + 10:tree:left/:$(git rev-parse base:left) + 10:tree:left/:$(git rev-parse base~2:left) + 11:blob:left/b:$(git rev-parse base~2:left/b) + 11:blob:left/b:$(git rev-parse base:left/b) + 12:tree:right/:$(git rev-parse topic:right) + 12:tree:right/:$(git rev-parse base~1:right) + 12:tree:right/:$(git rev-parse base~2:right) + 13:blob:right/c:$(git rev-parse base~2:right/c) + 13:blob:right/c:$(git rev-parse topic:right/c) + 14:blob:right/d:$(git rev-parse base~1:right/d) blobs:10 commits:4 tags:7 @@ -206,6 +204,43 @@ test_cmp_sorted expect out ' +test_expect_success 'base & topic, sparse, no tree pruning' ' + cat >patterns <<-EOF && + /* + !/*/ + /left/ + EOF + + test-tool path-walk --stdin-pl --no-pl-sparse-trees \ + -- base topic <patterns >out && + + cat >expect <<-EOF && + 0:commit::$(git rev-parse topic) + 0:commit::$(git rev-parse base) + 0:commit::$(git rev-parse base~1) + 0:commit::$(git rev-parse base~2) + 1:tree::$(git rev-parse topic^{tree}) + 1:tree::$(git rev-parse base^{tree}) + 1:tree::$(git rev-parse base~1^{tree}) + 1:tree::$(git rev-parse base~2^{tree}) + 2:blob:a:$(git rev-parse base~2:a) + 3:tree:a/:$(git rev-parse base:a) + 4:tree:left/:$(git rev-parse base:left) + 4:tree:left/:$(git rev-parse base~2:left) + 5:blob:left/b:$(git rev-parse base~2:left/b) + 5:blob:left/b:$(git rev-parse base:left/b) + 6:tree:right/:$(git rev-parse topic:right) + 6:tree:right/:$(git rev-parse base~1:right) + 6:tree:right/:$(git rev-parse base~2:right) + blobs:3 + commits:4 + tags:0 + trees:10 + EOF + + test_cmp_sorted expect out +' + test_expect_success 'topic only' ' test-tool path-walk -- topic >out && @@ -415,4 +450,483 @@ test_line_count = 1 out-filtered ' +test_expect_success 'all, blob:none filter' ' + test-tool path-walk --filter=blob:none -- --all >out && + + cat >expect <<-EOF && + 0:commit::$(git rev-parse topic) + 0:commit::$(git rev-parse base) + 0:commit::$(git rev-parse base~1) + 0:commit::$(git rev-parse base~2) + 1:tag:/tags:$(git rev-parse refs/tags/first) + 1:tag:/tags:$(git rev-parse refs/tags/second.1) + 1:tag:/tags:$(git rev-parse refs/tags/second.2) + 1:tag:/tags:$(git rev-parse refs/tags/third) + 1:tag:/tags:$(git rev-parse refs/tags/fourth) + 1:tag:/tags:$(git rev-parse refs/tags/tree-tag) + 1:tag:/tags:$(git rev-parse refs/tags/blob-tag) + 2:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag^{}) + 2:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag2^{}) + 3:tree::$(git rev-parse topic^{tree}) + 3:tree::$(git rev-parse base^{tree}) + 3:tree::$(git rev-parse base~1^{tree}) + 3:tree::$(git rev-parse base~2^{tree}) + 4:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag^{}) + 4:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag2^{}) + 5:tree:a/:$(git rev-parse base:a) + 6:tree:child/:$(git rev-parse refs/tags/tree-tag:child) + 7:tree:left/:$(git rev-parse base:left) + 7:tree:left/:$(git rev-parse base~2:left) + 8:tree:right/:$(git rev-parse topic:right) + 8:tree:right/:$(git rev-parse base~1:right) + 8:tree:right/:$(git rev-parse base~2:right) + blobs:2 + commits:4 + tags:7 + trees:13 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'topic only, blob:none filter' ' + test-tool path-walk --filter=blob:none -- topic >out && + + cat >expect <<-EOF && + 0:commit::$(git rev-parse topic) + 0:commit::$(git rev-parse base~1) + 0:commit::$(git rev-parse base~2) + 1:tree::$(git rev-parse topic^{tree}) + 1:tree::$(git rev-parse base~1^{tree}) + 1:tree::$(git rev-parse base~2^{tree}) + 2:tree:left/:$(git rev-parse base~2:left) + 3:tree:right/:$(git rev-parse topic:right) + 3:tree:right/:$(git rev-parse base~1:right) + 3:tree:right/:$(git rev-parse base~2:right) + blobs:0 + commits:3 + tags:0 + trees:7 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'all, blob:limit=0 filter' ' + test-tool path-walk --filter=blob:limit=0 -- --all >out && + + cat >expect <<-EOF && + 0:commit::$(git rev-parse topic) + 0:commit::$(git rev-parse base) + 0:commit::$(git rev-parse base~1) + 0:commit::$(git rev-parse base~2) + 1:tag:/tags:$(git rev-parse refs/tags/first) + 1:tag:/tags:$(git rev-parse refs/tags/second.1) + 1:tag:/tags:$(git rev-parse refs/tags/second.2) + 1:tag:/tags:$(git rev-parse refs/tags/third) + 1:tag:/tags:$(git rev-parse refs/tags/fourth) + 1:tag:/tags:$(git rev-parse refs/tags/tree-tag) + 1:tag:/tags:$(git rev-parse refs/tags/blob-tag) + 2:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag^{}) + 2:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag2^{}) + 3:tree::$(git rev-parse topic^{tree}) + 3:tree::$(git rev-parse base^{tree}) + 3:tree::$(git rev-parse base~1^{tree}) + 3:tree::$(git rev-parse base~2^{tree}) + 4:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag^{}) + 4:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag2^{}) + 5:tree:a/:$(git rev-parse base:a) + 6:tree:child/:$(git rev-parse refs/tags/tree-tag:child) + 7:tree:left/:$(git rev-parse base:left) + 7:tree:left/:$(git rev-parse base~2:left) + 8:tree:right/:$(git rev-parse topic:right) + 8:tree:right/:$(git rev-parse base~1:right) + 8:tree:right/:$(git rev-parse base~2:right) + blobs:2 + commits:4 + tags:7 + trees:13 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'all, blob:limit=3 filter' ' + test-tool path-walk --filter=blob:limit=3 -- --all >out && + + cat >expect <<-EOF && + 0:commit::$(git rev-parse topic) + 0:commit::$(git rev-parse base) + 0:commit::$(git rev-parse base~1) + 0:commit::$(git rev-parse base~2) + 1:tag:/tags:$(git rev-parse refs/tags/first) + 1:tag:/tags:$(git rev-parse refs/tags/second.1) + 1:tag:/tags:$(git rev-parse refs/tags/second.2) + 1:tag:/tags:$(git rev-parse refs/tags/third) + 1:tag:/tags:$(git rev-parse refs/tags/fourth) + 1:tag:/tags:$(git rev-parse refs/tags/tree-tag) + 1:tag:/tags:$(git rev-parse refs/tags/blob-tag) + 2:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag^{}) + 2:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag2^{}) + 3:tree::$(git rev-parse topic^{tree}) + 3:tree::$(git rev-parse base^{tree}) + 3:tree::$(git rev-parse base~1^{tree}) + 3:tree::$(git rev-parse base~2^{tree}) + 4:blob:a:$(git rev-parse base~2:a) + 5:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag^{}) + 5:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag2^{}) + 6:tree:a/:$(git rev-parse base:a) + 7:tree:child/:$(git rev-parse refs/tags/tree-tag:child) + 8:tree:left/:$(git rev-parse base:left) + 8:tree:left/:$(git rev-parse base~2:left) + 9:blob:left/b:$(git rev-parse base~2:left/b) + 10:tree:right/:$(git rev-parse topic:right) + 10:tree:right/:$(git rev-parse base~1:right) + 10:tree:right/:$(git rev-parse base~2:right) + 11:blob:right/c:$(git rev-parse base~2:right/c) + 12:blob:right/d:$(git rev-parse base~1:right/d) + blobs:6 + commits:4 + tags:7 + trees:13 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'all, tree:0 filter' ' + test-tool path-walk --filter=tree:0 -- --all >out && + + cat >expect <<-EOF && + 0:commit::$(git rev-parse topic) + 0:commit::$(git rev-parse base) + 0:commit::$(git rev-parse base~1) + 0:commit::$(git rev-parse base~2) + 1:tag:/tags:$(git rev-parse refs/tags/first) + 1:tag:/tags:$(git rev-parse refs/tags/second.1) + 1:tag:/tags:$(git rev-parse refs/tags/second.2) + 1:tag:/tags:$(git rev-parse refs/tags/third) + 1:tag:/tags:$(git rev-parse refs/tags/fourth) + 1:tag:/tags:$(git rev-parse refs/tags/tree-tag) + 1:tag:/tags:$(git rev-parse refs/tags/blob-tag) + 2:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag^{}) + 2:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag2^{}) + 3:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag^{tree}) + 3:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag2) + blobs:2 + commits:4 + tags:7 + trees:2 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'topic only, tree:0 filter' ' + test-tool path-walk --filter=tree:0 -- topic >out && + + cat >expect <<-EOF && + 0:commit::$(git rev-parse topic) + 0:commit::$(git rev-parse base~1) + 0:commit::$(git rev-parse base~2) + blobs:0 + commits:3 + tags:0 + trees:0 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'tree:1 filter is rejected' ' + test_must_fail test-tool path-walk --filter=tree:1 -- --all 2>err && + test_grep "tree:1 filter not supported by the path-walk API" err +' + +test_expect_success 'all, object:type=commit filter' ' + test-tool path-walk --filter=object:type=commit -- --all >out && + + cat >expect <<-EOF && + 0:commit::$(git rev-parse topic) + 0:commit::$(git rev-parse base) + 0:commit::$(git rev-parse base~1) + 0:commit::$(git rev-parse base~2) + blobs:0 + commits:4 + tags:0 + trees:0 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'all, object:type=tag filter' ' + test-tool path-walk --filter=object:type=tag -- --all >out && + + cat >expect <<-EOF && + 0:tag:/tags:$(git rev-parse refs/tags/first) + 0:tag:/tags:$(git rev-parse refs/tags/second.1) + 0:tag:/tags:$(git rev-parse refs/tags/second.2) + 0:tag:/tags:$(git rev-parse refs/tags/third) + 0:tag:/tags:$(git rev-parse refs/tags/fourth) + 0:tag:/tags:$(git rev-parse refs/tags/tree-tag) + 0:tag:/tags:$(git rev-parse refs/tags/blob-tag) + blobs:0 + commits:0 + tags:7 + trees:0 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'all, object:type=tree filter' ' + test-tool path-walk --filter=object:type=tree -- --all >out && + + cat >expect <<-EOF && + 0:tree::$(git rev-parse topic^{tree}) + 0:tree::$(git rev-parse base^{tree}) + 0:tree::$(git rev-parse base~1^{tree}) + 0:tree::$(git rev-parse base~2^{tree}) + 1:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag^{}) + 1:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag2^{}) + 2:tree:a/:$(git rev-parse base:a) + 3:tree:child/:$(git rev-parse refs/tags/tree-tag:child) + 4:tree:left/:$(git rev-parse base:left) + 4:tree:left/:$(git rev-parse base~2:left) + 5:tree:right/:$(git rev-parse topic:right) + 5:tree:right/:$(git rev-parse base~1:right) + 5:tree:right/:$(git rev-parse base~2:right) + blobs:0 + commits:0 + tags:0 + trees:13 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'all, object:type=blob filter' ' + test-tool path-walk --filter=object:type=blob -- --all >out && + + cat >expect <<-EOF && + 0:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag^{}) + 0:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag2^{}) + 1:blob:a:$(git rev-parse base~2:a) + 2:blob:left/b:$(git rev-parse base:left/b) + 2:blob:left/b:$(git rev-parse base~2:left/b) + 3:blob:right/c:$(git rev-parse base~2:right/c) + 3:blob:right/c:$(git rev-parse topic:right/c) + 4:blob:right/d:$(git rev-parse base~1:right/d) + blobs:8 + commits:0 + tags:0 + trees:0 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'all, combine:blob:none+tree:0 filter' ' + test-tool path-walk \ + --filter=combine:blob:none+tree:0 -- --all >out && + + cat >expect <<-EOF && + 0:commit::$(git rev-parse topic) + 0:commit::$(git rev-parse base) + 0:commit::$(git rev-parse base~1) + 0:commit::$(git rev-parse base~2) + 1:tag:/tags:$(git rev-parse refs/tags/first) + 1:tag:/tags:$(git rev-parse refs/tags/second.1) + 1:tag:/tags:$(git rev-parse refs/tags/second.2) + 1:tag:/tags:$(git rev-parse refs/tags/third) + 1:tag:/tags:$(git rev-parse refs/tags/fourth) + 1:tag:/tags:$(git rev-parse refs/tags/tree-tag) + 1:tag:/tags:$(git rev-parse refs/tags/blob-tag) + 2:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag^{}) + 2:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag2^{}) + 3:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag^{tree}) + 3:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag2) + blobs:2 + commits:4 + tags:7 + trees:2 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'all, combine:object:type=blob+blob:limit=3 filter' ' + test-tool path-walk \ + --filter=combine:object:type=blob+blob:limit=3 \ + -- --all >out && + + cat >expect <<-EOF && + 0:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag^{}) + 0:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag2^{}) + 1:blob:a:$(git rev-parse base~2:a) + 2:blob:left/b:$(git rev-parse base~2:left/b) + 3:blob:right/c:$(git rev-parse base~2:right/c) + 4:blob:right/d:$(git rev-parse base~1:right/d) + blobs:6 + commits:0 + tags:0 + trees:0 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'all, combine of disjoint object:types is empty' ' + test-tool path-walk \ + --filter=combine:object:type=blob+object:type=tree \ + -- --all >out && + + cat >expect <<-EOF && + blobs:0 + commits:0 + tags:0 + trees:0 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'combine: rejects unsupported subfilters' ' + test_must_fail test-tool path-walk \ + --filter=combine:tree:1+blob:none -- --all 2>err && + test_grep "tree:1 filter not supported by the path-walk API" err +' + +test_expect_success 'setup sparse filter blob' ' + # Cone-mode patterns: include root, exclude all dirs, include left/ + cat >patterns <<-\EOF && + /* + !/*/ + /left/ + EOF + sparse_oid=$(git hash-object -w -t blob patterns) +' + +test_expect_success 'all, sparse:oid filter' ' + test-tool path-walk --filter=sparse:oid=$sparse_oid -- --all >out && + + cat >expect <<-EOF && + 0:commit::$(git rev-parse topic) + 0:commit::$(git rev-parse base) + 0:commit::$(git rev-parse base~1) + 0:commit::$(git rev-parse base~2) + 1:tag:/tags:$(git rev-parse refs/tags/first) + 1:tag:/tags:$(git rev-parse refs/tags/second.1) + 1:tag:/tags:$(git rev-parse refs/tags/second.2) + 1:tag:/tags:$(git rev-parse refs/tags/third) + 1:tag:/tags:$(git rev-parse refs/tags/fourth) + 1:tag:/tags:$(git rev-parse refs/tags/tree-tag) + 1:tag:/tags:$(git rev-parse refs/tags/blob-tag) + 2:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag^{}) + 2:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag2^{}) + 3:tree::$(git rev-parse topic^{tree}) + 3:tree::$(git rev-parse base^{tree}) + 3:tree::$(git rev-parse base~1^{tree}) + 3:tree::$(git rev-parse base~2^{tree}) + 4:blob:a:$(git rev-parse base~2:a) + 5:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag^{}) + 5:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag2^{}) + 6:blob:file2:$(git rev-parse refs/tags/tree-tag2^{}:file2) + 7:tree:a/:$(git rev-parse base:a) + 8:tree:child/:$(git rev-parse refs/tags/tree-tag:child) + 9:tree:left/:$(git rev-parse base:left) + 9:tree:left/:$(git rev-parse base~2:left) + 10:blob:left/b:$(git rev-parse base~2:left/b) + 10:blob:left/b:$(git rev-parse base:left/b) + 11:tree:right/:$(git rev-parse topic:right) + 11:tree:right/:$(git rev-parse base~1:right) + 11:tree:right/:$(git rev-parse base~2:right) + blobs:6 + commits:4 + tags:7 + trees:13 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'topic only, sparse:oid filter' ' + test-tool path-walk --filter=sparse:oid=$sparse_oid -- topic >out && + + cat >expect <<-EOF && + 0:commit::$(git rev-parse topic) + 0:commit::$(git rev-parse base~1) + 0:commit::$(git rev-parse base~2) + 1:tree::$(git rev-parse topic^{tree}) + 1:tree::$(git rev-parse base~1^{tree}) + 1:tree::$(git rev-parse base~2^{tree}) + 2:blob:a:$(git rev-parse base~2:a) + 3:tree:left/:$(git rev-parse base~2:left) + 4:blob:left/b:$(git rev-parse base~2:left/b) + 5:tree:right/:$(git rev-parse topic:right) + 5:tree:right/:$(git rev-parse base~1:right) + 5:tree:right/:$(git rev-parse base~2:right) + blobs:2 + commits:3 + tags:0 + trees:7 + EOF + + test_cmp_sorted expect out +' + +# Demonstrate the SEEN flag ordering issue: when the same tree/blob OID +# appears at two sibling paths where one is in-cone and the other is +# out-of-cone, the path-walk must still discover blobs at the in-cone +# path even when the shared tree OID was first encountered out-of-cone. +# Since sparse:oid includes all trees, the out-of-cone tree (aaa/) is +# walked first, and its blob is skipped. The path-walk then re-walks +# the same tree OID at the in-cone path (zzz/) to find the blob there. + +test_expect_success 'setup shared tree OID across cone boundary' ' + git checkout --orphan shared-tree && + git rm -rf . && + mkdir aaa zzz && + echo "shared content" >aaa/file && + echo "shared content" >zzz/file && + echo "root file" >rootfile && + git add aaa zzz rootfile && + git commit -m "aaa and zzz have same tree OID" && + + # Verify they really share a tree OID + aaa_tree=$(git rev-parse HEAD:aaa) && + zzz_tree=$(git rev-parse HEAD:zzz) && + test "$aaa_tree" = "$zzz_tree" && + + # Cone pattern: include root + zzz/ (not aaa/) + cat >shared-patterns <<-\EOF && + /* + !/*/ + /zzz/ + EOF + shared_sparse_oid=$(git hash-object -w -t blob shared-patterns) +' + +test_expect_success 'sparse:oid with shared tree OID across cone boundary' ' + test-tool path-walk \ + --filter=sparse:oid=$shared_sparse_oid \ + -- shared-tree >out && + + cat >expect <<-EOF && + 0:commit::$(git rev-parse shared-tree) + 1:tree::$(git rev-parse shared-tree^{tree}) + 2:blob:rootfile:$(git rev-parse shared-tree:rootfile) + 3:tree:aaa/:$(git rev-parse shared-tree:aaa) + 4:tree:zzz/:$(git rev-parse shared-tree:zzz) + 5:blob:zzz/file:$(git rev-parse shared-tree:zzz/file) + blobs:2 + commits:1 + tags:0 + trees:3 + EOF + + test_cmp_sorted expect out +' + test_done
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index faf7d97..d918005 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh
@@ -145,9 +145,7 @@ ' test_expect_success 'Multiple -l or --list options are equivalent to one -l option' ' - cat >expect <<-\EOF && - mytag - EOF + git tag -l >expect && git tag -l -l >actual && test_cmp expect actual && git tag --list --list >actual && @@ -157,8 +155,10 @@ ' test_expect_success 'listing all tags if one exists should output that tag' ' - test $(git tag -l) = mytag && - test $(git tag) = mytag + git tag -l >actual && + test_grep "^mytag$" actual && + git tag >actual && + test_grep "^mytag$" actual ' # pattern matching: @@ -168,11 +168,15 @@ ' test_expect_success 'listing a tag with --ignore-case' ' - test $(git tag -l --ignore-case MYTAG) = mytag + echo mytag >expect && + git tag -l --ignore-case MYTAG >actual && + test_cmp expect actual ' test_expect_success 'listing a tag using a matching pattern should output that tag' ' - test $(git tag -l mytag) = mytag + echo mytag >expect && + git tag -l mytag >actual && + test_cmp expect actual ' test_expect_success 'listing tags using a non-matching pattern should succeed' ' @@ -191,15 +195,14 @@ ' test_expect_success 'trying to create a tag with a non-valid name should fail' ' - git tag -l >actual && - test_line_count = 1 actual && + git tag -l >tags-before && test_must_fail git tag "" && test_must_fail git tag .othertag && test_must_fail git tag "other tag" && test_must_fail git tag "othertag^" && test_must_fail git tag "other~tag" && - git tag -l >actual && - test_line_count = 1 actual + git tag -l >tags-after && + test_cmp tags-before tags-after ' test_expect_success 'creating a tag using HEAD directly should succeed' ' @@ -227,12 +230,7 @@ ' test_expect_success 'trying to delete tags without params should succeed and do nothing' ' - cat >expect <<-\EOF && - myhead - mytag - EOF - git tag -l >actual && - test_cmp expect actual && + git tag -l >expect && git tag -d && git tag -l >actual && test_cmp expect actual @@ -438,8 +436,12 @@ test_expect_success 'a non-annotated tag created without parameters should point to HEAD' ' git tag non-annotated-tag && - test $(git cat-file -t non-annotated-tag) = commit && - test $(git rev-parse non-annotated-tag) = $(git rev-parse HEAD) + echo commit >expect && + git cat-file -t non-annotated-tag >actual && + test_cmp expect actual && + git rev-parse HEAD >expect && + git rev-parse non-annotated-tag >actual && + test_cmp expect actual ' test_expect_success 'trying to verify an unknown tag should fail' ' @@ -1528,11 +1530,11 @@ ' test_expect_success 'git tag -a fails if tag annotation is empty' ' - ! (GIT_EDITOR=cat git tag -a initial-comment) + test_must_fail env GIT_EDITOR=cat git tag -a initial-comment ' test_expect_success 'message in editor has initial comment' ' - ! (GIT_EDITOR=cat git tag -a initial-comment >actual) + test_must_fail env GIT_EDITOR=cat git tag -a initial-comment >actual ' test_expect_success 'message in editor has initial comment: first line' '
diff --git a/t/t7060-wtstatus.sh b/t/t7060-wtstatus.sh index 0f4344c..942ddbb 100755 --- a/t/t7060-wtstatus.sh +++ b/t/t7060-wtstatus.sh
@@ -9,6 +9,7 @@ test_expect_success setup ' git config --global advice.statusuoption false && + echo "/.gitconfig" >>.git/info/exclude && test_commit A && test_commit B oneside added && git checkout A^0 && @@ -221,7 +222,6 @@ git status --branch --porcelain >actual && cat >expected <<-EOF && ## HEAD (no branch) - ?? .gitconfig ?? actual ?? expect ?? expected @@ -237,7 +237,6 @@ git status --branch --porcelain=v1 >actual && cat >expected <<-EOF && ## HEAD (no branch) - ?? .gitconfig ?? actual ?? expect ?? expected
diff --git a/t/t7061-wtstatus-ignore.sh b/t/t7061-wtstatus-ignore.sh index 2f9bea9..14ddaba 100755 --- a/t/t7061-wtstatus-ignore.sh +++ b/t/t7061-wtstatus-ignore.sh
@@ -18,6 +18,7 @@ : >untracked/ignored && : >untracked/uncommitted && git status --porcelain --ignored >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -27,6 +28,7 @@ : >untracked/ignored && : >untracked/uncommitted && git status --porcelain --ignored >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -40,18 +42,22 @@ test_expect_success 'status untracked files --ignored with pathspec (literal match)' ' git status --porcelain --ignored -- untracked/ignored >actual && echo "!! untracked/ignored" >expected && + test_filter_gitconfig actual && test_cmp expected actual && git status --porcelain --ignored -- untracked/uncommitted >actual && echo "?? untracked/uncommitted" >expected && + test_filter_gitconfig actual && test_cmp expected actual ' test_expect_success 'status untracked files --ignored with pathspec (glob match)' ' git status --porcelain --ignored -- untracked/i\* >actual && echo "!! untracked/ignored" >expected && + test_filter_gitconfig actual && test_cmp expected actual && git status --porcelain --ignored -- untracked/u\* >actual && echo "?? untracked/uncommitted" >expected && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -65,6 +71,7 @@ test_expect_success 'status untracked directory with --ignored -u' ' git status --porcelain --ignored -u >actual && + test_filter_gitconfig actual && test_cmp expected actual ' cat >expected <<\EOF @@ -76,9 +83,11 @@ git status --porcelain --ignored >tmp && grep untracked/ tmp >actual && rm tmp && + test_filter_gitconfig actual && test_cmp expected actual && git status --porcelain --ignored untracked/ >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -89,6 +98,7 @@ test_expect_success 'status prefixed untracked sub-directory with --ignored -u' ' git status --porcelain --ignored -u untracked/ >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -104,6 +114,7 @@ mkdir ignored && : >ignored/uncommitted && git status --porcelain --ignored >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -116,6 +127,7 @@ test_expect_success 'status ignored directory with --ignore -u' ' git status --porcelain --ignored -u >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -130,6 +142,7 @@ mkdir untracked-ignored && mkdir untracked-ignored/test && git status --porcelain --ignored >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -141,6 +154,7 @@ test_expect_success 'status empty untracked directory with --ignore -u' ' git status --porcelain --ignored -u >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -155,6 +169,7 @@ : >untracked-ignored/ignored && : >untracked-ignored/test/ignored && git status --porcelain --ignored >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -168,6 +183,7 @@ test_expect_success 'status untracked directory with ignored files with --ignore -u' ' git status --porcelain --ignored -u >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -185,6 +201,7 @@ git commit -m. && echo "tracked" >.gitignore && git status --porcelain --ignored >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -196,6 +213,7 @@ test_expect_success 'status ignored tracked directory with --ignore -u' ' git status --porcelain --ignored -u >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -208,6 +226,7 @@ test_expect_success 'status ignored tracked directory and ignored file with --ignore' ' echo "committed" >>.gitignore && git status --porcelain --ignored >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -219,6 +238,7 @@ test_expect_success 'status ignored tracked directory and ignored file with --ignore -u' ' git status --porcelain --ignored -u >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -233,6 +253,7 @@ echo "tracked" >.gitignore && : >tracked/uncommitted && git status --porcelain --ignored >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -245,6 +266,7 @@ test_expect_success 'status ignored tracked directory and uncommitted file with --ignore -u' ' git status --porcelain --ignored -u >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -260,6 +282,7 @@ mkdir tracked/ignored && : >tracked/ignored/uncommitted && git status --porcelain --ignored >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -272,6 +295,7 @@ test_expect_success 'status ignored tracked directory with uncommitted file in untracked subdir with --ignore -u' ' git status --porcelain --ignored -u >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -287,6 +311,7 @@ git add -f tracked/ignored/committed && git commit -m. && git status --porcelain --ignored >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -299,6 +324,7 @@ test_expect_success 'status ignored tracked directory with uncommitted file in tracked subdir with --ignore -u' ' git status --porcelain --ignored -u >actual && + test_filter_gitconfig actual && test_cmp expected actual ' @@ -310,6 +336,7 @@ git init tracked/submodule && test_commit -C tracked/submodule initial && git status --porcelain --ignored -u tracked/submodule >actual && + test_filter_gitconfig actual && test_cmp expected actual '
diff --git a/t/t7064-wtstatus-pv2.sh b/t/t7064-wtstatus-pv2.sh index 8bbc5ce..be6c931 100755 --- a/t/t7064-wtstatus-pv2.sh +++ b/t/t7064-wtstatus-pv2.sh
@@ -231,6 +231,7 @@ EOF git status --porcelain=v2 --ignored --untracked-files=all >actual && + test_filter_gitconfig actual && test_cmp expect actual '
diff --git a/t/t7104-reset-hard.sh b/t/t7104-reset-hard.sh index 7948ec3..c23d6e3 100755 --- a/t/t7104-reset-hard.sh +++ b/t/t7104-reset-hard.sh
@@ -21,7 +21,7 @@ rm -f hello && mkdir -p hello && >hello/world && - test "$(git ls-files -o)" = hello/world + test "$(git ls-files -o --exclude-standard)" = hello/world '
diff --git a/t/t7201-co.sh b/t/t7201-co.sh index 9bcf7c0..7613b1d 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh
@@ -102,7 +102,10 @@ test "$(git symbolic-ref HEAD)" = "refs/heads/side" && - printf "M\t%s\n" one >expect.messages && + cat >expect.messages <<-\EOF && + The following paths have local changes: + M one + EOF test_cmp expect.messages messages && fill "M one" "A three" "D two" >expect.main && @@ -210,6 +213,72 @@ test_cmp expect two ' +test_expect_success 'checkout -m with mixed staged and unstaged changes' ' + git checkout -f main && + git clean -f && + + fill 0 x y z >same && + git add same && + fill 1 2 3 4 5 6 7 >one && + git checkout -m side >actual 2>&1 && + test_grep "Applied autostash" actual && + fill 0 x y z >expect && + test_cmp expect same && + fill 1 2 3 4 5 6 7 >expect && + test_cmp expect one +' + +test_expect_success 'checkout -m creates a recoverable stash on conflict' ' + git checkout -f main && + git clean -f && + + fill 1 2 3 4 5 >one && + test_must_fail git checkout side 2>stderr && + test_grep "Your local changes" stderr && + git checkout -m side >actual 2>&1 && + test_grep "resulted in conflicts" actual && + test_grep "git stash drop" actual && + test_grep "git stash pop" actual && + test_grep "The following paths have local changes" actual && + git log -p -1 --format="%gs%n%B" -g --diff-merges=1 refs/stash >actual && + sed /^index/d actual >actual.trimmed && + cat >expect <<-EOF && + autostash while switching to ${SQ}side${SQ} + On main: autostash while switching to ${SQ}side${SQ} + + diff --git a/one b/one + --- a/one + +++ b/one + @@ -3,6 +3,3 @@ + 3 + 4 + 5 + -6 + -7 + -8 + EOF + test_cmp expect actual.trimmed && + git stash drop && + git reset --hard +' + +test_expect_success 'checkout -m which would overwrite untracked file' ' + git checkout -f --detach main && + test_commit another-file && + git checkout HEAD^ && + >another-file.t && + fill 1 2 3 4 5 >one && + test_must_fail git checkout -m @{-1} 2>err && + q_to_tab >expect <<-\EOF && + error: The following untracked working tree files would be overwritten by checkout: + Qanother-file.t + Please move or remove them before you switch branches. + Aborting + Applied autostash. + EOF + test_cmp expect err +' + test_expect_success 'switch to another branch while carrying a deletion' ' git checkout -f main && git reset --hard &&
diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index 3adab12..6abb008 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh
@@ -1055,6 +1055,14 @@ ) ' +test_expect_success 'submodule update honors fetch jobs config from .gitmodules' ' + test_when_finished "rm -rf super3" && + git clone cloned super3 && + git -C super3 config -f .gitmodules submodule.fetchJobs 67 && + GIT_TRACE="$(pwd)/trace.out" git -C super3 submodule update --init && + test_grep "67 tasks" trace.out +' + test_expect_success 'git clone passes the parallel jobs config on to submodules' ' test_when_finished "rm -rf super4" && GIT_TRACE=$(pwd)/trace.out git clone --recurse-submodules --jobs 7 . super4 &&
diff --git a/t/t7422-submodule-output.sh b/t/t7422-submodule-output.sh index aea1ddf..852136f 100755 --- a/t/t7422-submodule-output.sh +++ b/t/t7422-submodule-output.sh
@@ -198,7 +198,7 @@ ( cd repo && GIT_ALLOW_PROTOCOL=file git submodule add "$(pwd)"/../submodule && - { git submodule status --recursive 2>err; echo $?>status; } | + { { ret=0 && git submodule status --recursive 2>err || ret=$?; } && echo $ret >status; } | grep -q recursive-submodule-path-1 && test_must_be_empty err && test_match_signal 13 "$(cat status)"
diff --git a/t/t7450-bad-git-dotfiles.sh b/t/t7450-bad-git-dotfiles.sh index f512eed..8cc8652 100755 --- a/t/t7450-bad-git-dotfiles.sh +++ b/t/t7450-bad-git-dotfiles.sh
@@ -220,17 +220,19 @@ ) ' - test -n "$refuse_index" && - test_expect_success "refuse to load symlinked $name into index ($type)" ' - test_must_fail \ - git -C $dir \ - -c core.protectntfs \ - -c core.protecthfs \ - read-tree $tree 2>err && - grep "invalid path.*$name" err && - git -C $dir ls-files -s >out && - test_must_be_empty out - ' + if test -n "$refuse_index" + then + test_expect_success "refuse to load symlinked $name into index ($type)" ' + test_must_fail \ + git -C $dir \ + -c core.protectntfs \ + -c core.protecthfs \ + read-tree $tree 2>err && + grep "invalid path.*$name" err && + git -C $dir ls-files -s >out && + test_must_be_empty out + ' + fi } check_dotx_symlink gitmodules vanilla .gitmodules
diff --git a/t/t7508-status.sh b/t/t7508-status.sh index a5e21bf..c2057bc 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh
@@ -263,6 +263,7 @@ !! untracked EOF git status -s --ignored >output && + test_filter_gitconfig output && test_cmp expect output && cat >expect <<\EOF && @@ -296,6 +297,7 @@ EOF git status --ignored >output && + test_filter_gitconfig output && test_cmp expect output ' @@ -328,6 +330,7 @@ !! untracked EOF git status -s --ignored >output && + test_filter_gitconfig output && test_cmp expect output && cat >expect <<\EOF && @@ -358,6 +361,7 @@ EOF git status --ignored >output && + test_filter_gitconfig output && test_cmp expect output ' @@ -773,8 +777,8 @@ ' # recover unconditionally from color tests -git config --unset color.status -git config --unset color.ui +git config --unset color.status || : +git config --unset color.ui || : test_expect_success 'status --porcelain respects -b' '
diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh index 1201c85..aa9108d 100755 --- a/t/t7510-signed-commit.sh +++ b/t/t7510-signed-commit.sh
@@ -462,4 +462,14 @@ git commit -S --allow-empty -m signed-commit ' +test_expect_success GPG 'commit verifies with non-UTF-8 commit message' ' + printf "I hate\\376\\377UTF-8\\n" >message && + echo unusual-message >file && + git add file && + test_tick && git commit -S -F message 2>err && + git verify-commit HEAD && + grep "commit message did not conform to UTF-8" err >lines && + test_line_count = 1 lines +' + test_done
diff --git a/t/t7521-ignored-mode.sh b/t/t7521-ignored-mode.sh index a88b02b..7ea0b0d 100755 --- a/t/t7521-ignored-mode.sh +++ b/t/t7521-ignored-mode.sh
@@ -30,6 +30,7 @@ dir/ignored/ignored_1.ign dir/ignored/ignored_2.ign && git status --porcelain=v2 --ignored=matching --untracked-files=all >output && + test_filter_gitconfig output && test_cmp expect output '
diff --git a/t/t7527-builtin-fsmonitor.sh b/t/t7527-builtin-fsmonitor.sh index e7b4065..d881e27 100755 --- a/t/t7527-builtin-fsmonitor.sh +++ b/t/t7527-builtin-fsmonitor.sh
@@ -10,9 +10,58 @@ test_done fi +# Verify that the filesystem delivers events to the daemon. +# On some configurations (e.g., overlayfs with older kernels), +# inotify watches succeed but events are never delivered. The +# cookie wait will time out and the daemon logs a trace message. +# +# Use "timeout" (if available) to guard each step against hangs. +maybe_timeout () { + if type timeout >/dev/null 2>&1 + then + timeout "$@" + else + shift + "$@" + fi +} + +test_lazy_prereq FSMONITOR_WORKS ' + git init test_fsmonitor_smoke || return 1 + + GIT_TRACE_FSMONITOR="$PWD/smoke.trace" && + export GIT_TRACE_FSMONITOR && + maybe_timeout 30 \ + git -C test_fsmonitor_smoke fsmonitor--daemon start \ + --start-timeout=10 + ret=$? + unset GIT_TRACE_FSMONITOR + if test $ret -ne 0 + then + rm -rf test_fsmonitor_smoke smoke.trace + return 1 + fi + + maybe_timeout 10 \ + test-tool -C test_fsmonitor_smoke fsmonitor-client query \ + --token 0 >/dev/null 2>&1 + maybe_timeout 5 \ + git -C test_fsmonitor_smoke fsmonitor--daemon stop 2>/dev/null + ! grep -q "cookie_wait timed out" "$PWD/smoke.trace" 2>/dev/null + ret=$? + rm -rf test_fsmonitor_smoke smoke.trace + return $ret +' + +if ! test_have_prereq FSMONITOR_WORKS +then + skip_all="filesystem does not deliver fsmonitor events (container/overlayfs?)" + test_done +fi + stop_daemon_delete_repo () { r=$1 && - test_might_fail git -C $r fsmonitor--daemon stop && + { maybe_timeout 30 git -C $r fsmonitor--daemon stop 2>/dev/null || :; } && rm -rf $1 } @@ -67,7 +116,7 @@ export GIT_TEST_FSMONITOR_TOKEN fi && - git $r fsmonitor--daemon start && + git $r fsmonitor--daemon start --start-timeout=10 && git $r fsmonitor--daemon status ) } @@ -523,6 +572,28 @@ retry_grep "^event: dir1$" .git/trace ' +test_expect_success 'rapid nested directory creation' ' + test_when_finished "git fsmonitor--daemon stop; rm -rf rapid" && + + start_daemon --tf "$PWD/.git/trace" && + + # Rapidly create nested directories to exercise race conditions + # where directory watches may be added concurrently during + # event processing and recursive scanning. + for i in $(test_seq 1 20) + do + mkdir -p "rapid/nested/dir$i/subdir/deep" || return 1 + done && + + # Give the daemon time to process all events + sleep 1 && + + test-tool fsmonitor-client query --token 0 && + + # Verify daemon is still running (did not crash) + git fsmonitor--daemon status +' + # The next few test cases exercise the token-resync code. When filesystem # drops events (because of filesystem velocity or because the daemon isn't # polling fast enough), we need to discard the cached data (relative to the @@ -913,7 +984,10 @@ start_git_in_background () { git "$@" & git_pid=$! - git_pgid=$(ps -o pgid= -p $git_pid) + git_pgid=$(ps -o pgid= -p $git_pid 2>/dev/null || + awk '{print $5}' /proc/$git_pid/stat 2>/dev/null) && + git_pgid="${git_pgid## }" && + git_pgid="${git_pgid%% }" nr_tries_left=10 while true do @@ -924,15 +998,16 @@ fi sleep 1 nr_tries_left=$(($nr_tries_left - 1)) - done >/dev/null 2>&1 & + done >/dev/null 2>&1 3>&- 4>&- 5>&- 6>&- 7>&- & watchdog_pid=$! wait $git_pid } stop_git () { - while kill -0 -- -$git_pgid + test -n "$git_pgid" || return 0 + while kill -0 -- -$git_pgid 2>/dev/null do - kill -- -$git_pgid + kill -- -$git_pgid 2>/dev/null sleep 1 done } @@ -947,7 +1022,7 @@ test_expect_success !MINGW "submodule implicitly starts daemon by pull" ' test_atexit "stop_watchdog" && - test_when_finished "stop_git; rm -rf cloned super sub" && + test_when_finished "set +m; stop_git; rm -rf cloned super sub" && create_super super && create_sub sub &&
diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index 9838094..f877d9a 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh
@@ -914,7 +914,8 @@ git diff >expect && test_when_finished "test_might_fail git stash drop" && git merge --autostash c3 2>err && - test_grep "Applying autostash resulted in conflicts." err && + test_grep "applying them" err && + test_grep "resulted in conflicts" err && git show HEAD:file >merge-result && test_cmp result.1-9 merge-result && git stash show -p >actual &&
diff --git a/t/t7705-repack-incremental-midx.sh b/t/t7705-repack-incremental-midx.sh new file mode 100755 index 0000000..25a8c40 --- /dev/null +++ b/t/t7705-repack-incremental-midx.sh
@@ -0,0 +1,525 @@ +#!/bin/sh + +test_description='git repack --write-midx=incremental' + +. ./test-lib.sh + +GIT_TEST_MULTI_PACK_INDEX=0 +GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 +GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL=0 + +objdir=.git/objects +packdir=$objdir/pack +midxdir=$packdir/multi-pack-index.d +midx_chain=$midxdir/multi-pack-index-chain + +# incrementally_repack N +# +# Make "N" new commits, each stored in their own pack, and then repacked +# with the --write-midx=incremental strategy. +incrementally_repack () { + for i in $(test_seq 1 "$1") + do + test_commit "$i" && + + git repack --geometric=2 -d --write-midx=incremental \ + --write-bitmap-index && + git multi-pack-index verify || return 1 + done +} + +# Create packs with geometrically increasing sizes so that they +# satisfy the geometric progression and survive a --geometric=2 +# repack without being rolled up. Creates 3 packs containing 1, +# 2, and 6 commits (3, 6, and 18 objects) respectively. +create_geometric_packs () { + test_commit "small" && + git repack -d && + + test_commit_bulk --message="medium" 2 && + test_commit_bulk --message="large" 6 && + + git repack --geometric=2 -d --write-midx=incremental \ + --write-bitmap-index +} + +# create_layer <test_commit_bulk args> +# +# Creates a new MIDX layer with the contents of "test_commit_bulk $@". +create_layer () { + test_commit_bulk "$@" && + + git multi-pack-index write --incremental --bitmap +} + +# create_layers +# +# Reads lines of "<message> <nr>" from stdin and creates a new MIDX +# layer for each line. See create_layer above for more. +create_layers () { + while read msg nr + do + create_layer --message="$msg" "$nr" || return 1 + done +} + +test_expect_success '--write-midx=incremental without --geometric' ' + git init incremental-without-geometric && + ( + cd incremental-without-geometric && + + git config maintenance.auto false && + + test_commit first && + git repack -d && + + test_commit second && + git repack --write-midx=incremental && + + git multi-pack-index verify && + test_line_count = 1 $midx_chain && + cp $midx_chain $midx_chain.before && + + # A second repack appends a new layer without + # disturbing the existing one. + test_commit third && + git repack --write-midx=incremental && + + git multi-pack-index verify && + test_line_count = 2 $midx_chain && + head -n 1 $midx_chain.before >expect && + head -n 1 $midx_chain >actual && + test_cmp expect actual && + + git fsck + ) +' + +test_expect_success 'below layer threshold, tip packs excluded' ' + git init below-layer-threshold-tip-packs-excluded && + ( + cd below-layer-threshold-tip-packs-excluded && + + git config maintenance.auto false && + git config repack.midxnewlayerthreshold 4 && + git config repack.midxsplitfactor 2 && + + # Create 3 packs forming a geometric progression by + # object count such that they are unmodified by the + # initial repack. The MIDX chain thusly contains a + # single layer with three packs. + create_geometric_packs && + ls $packdir/pack-*.idx | sort >packs.before && + test_line_count = 1 $midx_chain && + cp $midx_chain $midx_chain.before && + + # Repack a new commit. Since the layer threshold is + # unmet, a new MIDX layer is added on top of the + # existing one. + test_commit extra && + git repack --geometric=2 -d --write-midx=incremental \ + --write-bitmap-index && + git multi-pack-index verify && + + ls $packdir/pack-*.idx | sort >packs.after && + comm -13 packs.before packs.after >packs.new && + test_line_count = 1 packs.new && + + test_line_count = 2 "$midx_chain" && + head -n 1 "$midx_chain.before" >expect && + head -n 1 "$midx_chain" >actual && + test_cmp expect actual + ) +' + +test_expect_success 'above layer threshold, tip packs repacked' ' + git init above-layer-threshold-tip-packs-repacked && + ( + cd above-layer-threshold-tip-packs-repacked && + + git config maintenance.auto false && + git config repack.midxnewlayerthreshold 2 && + git config repack.midxsplitfactor 2 && + + # Same setup, but with the layer threshold set to 2. + # Since the tip MIDX layer meets that threshold, its + # packs are considered repack candidates. + create_geometric_packs && + cp $midx_chain $midx_chain.before && + + # Perturb the existing progression such that it is + # rolled up into a single new pack, invalidating the + # existing MIDX layer and replacing it with a new one. + test_commit extra && + git repack -d && + git repack --geometric=2 -d --write-midx=incremental \ + --write-bitmap-index && + + ! test_cmp $midx_chain.before $midx_chain && + test_line_count = 1 $midx_chain && + + git multi-pack-index verify + ) +' + +test_expect_success 'above layer threshold, tip layer preserved' ' + git init above-layer-threshold-tip-layer-preserved && + ( + cd above-layer-threshold-tip-layer-preserved && + + git config maintenance.auto false && + git config repack.midxnewlayerthreshold 2 && + git config repack.midxsplitfactor 2 && + + test_commit_bulk --message="medium" 2 && + test_commit_bulk --message="large" 6 && + + git repack --geometric=2 -d --write-midx=incremental \ + --write-bitmap-index && + + test_line_count = 1 "$midx_chain" && + ls $packdir/pack-*.idx | sort >packs.before && + cp $midx_chain $midx_chain.before && + + # Create objects to form a pack satisfying the geometric + # progression (thus preserving the tip layer), but not + # so large that it meets the layer merging condition. + test_commit_bulk --message="small" 1 && + git repack --geometric=2 -d --write-midx=incremental \ + --write-bitmap-index && + + ls $packdir/pack-*.idx | sort >packs.after && + comm -13 packs.before packs.after >packs.new && + + test_line_count = 1 packs.new && + test_line_count = 3 packs.after && + test_line_count = 2 "$midx_chain" && + head -n 1 "$midx_chain.before" >expect && + head -n 1 "$midx_chain" >actual && + test_cmp expect actual && + + git multi-pack-index verify + ) +' + +test_expect_success 'above layer threshold, tip packs preserved' ' + git init above-layer-threshold-tip-packs-preserved && + ( + cd above-layer-threshold-tip-packs-preserved && + + git config maintenance.auto false && + git config repack.midxnewlayerthreshold 2 && + git config repack.midxsplitfactor 2 && + + create_geometric_packs && + ls $packdir/pack-*.idx | sort >packs.before && + cp $midx_chain $midx_chain.before && + + # Same setup as above, but this time the new objects do + # not satisfy the new layer merging condition, resulting + # in a new tip layer. + test_commit_bulk --message="huge" 18 && + git repack --geometric=2 -d --write-midx=incremental \ + --write-bitmap-index && + + ls $packdir/pack-*.idx | sort >packs.after && + comm -13 packs.before packs.after >packs.new && + + ! test_cmp $midx_chain.before $midx_chain && + test_line_count = 1 $midx_chain && + test_line_count = 1 packs.new && + + git multi-pack-index verify + ) +' + +test_expect_success 'new tip absorbs multiple layers' ' + git init new-tip-absorbs-multiple-layers && + ( + cd new-tip-absorbs-multiple-layers && + + git config maintenance.auto false && + git config repack.midxnewlayerthreshold 1 && + git config repack.midxsplitfactor 2 && + + # Build a 4-layer chain where each layer is too small to + # absorb the one below it. The sizes must satisfy L(n) < + # L(n-1)/2 for each adjacent pair: + # + # L0 (oldest): 75 obj (25 commits) + # L1: 21 obj (7 commits, 21 < 75/2) + # L2: 9 obj (3 commits, 9 < 21/2) + # L3 (tip): 3 obj (1 commit, 3 < 9/2) + create_layers <<-\EOF && + L0 25 + L1 7 + L2 3 + L3 1 + EOF + + test_line_count = 4 "$midx_chain" && + cp $midx_chain $midx_chain.before && + + # Now add a new commit. The merging condition is + # satisfied between L3-L1, but violated at L0, which is + # too large relative to the accumulated size. + # + # As a result, the chain shrinks from 4 to 2 layers. + test_commit new && + git repack --geometric=2 -d --write-midx=incremental \ + --write-bitmap-index && + + ! test_cmp $midx_chain.before $midx_chain && + test_line_count = 2 "$midx_chain" && + git multi-pack-index verify + ) +' + +test_expect_success 'compaction of older layers' ' + git init compaction-of-older-layers && + ( + cd compaction-of-older-layers && + + git config maintenance.auto false && + git config repack.midxnewlayerthreshold 1 && + git config repack.midxsplitfactor 2 && + + # Build a chain with two small layers at the bottom + # and a larger barrier layer on top, producing a + # chain that violates the compaction invariant, since + # the two small layers would normally have been merged. + create_layers <<-\EOF && + one 2 + two 4 + barrier 54 + EOF + + cp $midx_chain $midx_chain.before && + + # Running an incremental repack compacts the two + # small layers at the bottom of the chain as a + # separate step in the compaction plan. + test_commit another && + git repack --geometric=2 -d --write-midx=incremental \ + --write-bitmap-index && + + test_line_count = 2 "$midx_chain" && + git multi-pack-index verify + ) +' + +test_expect_success 'geometric rollup with surviving tip packs' ' + git init geometric-rollup-with-surviving-tip-packs && + ( + cd geometric-rollup-with-surviving-tip-packs && + + git config maintenance.auto false && + git config repack.midxnewlayerthreshold 1 && + git config repack.midxsplitfactor 2 && + + # Create a pack large enough to anchor the geometric + # progression when small packs are added alongside it. + create_layer --message="big" 5 && + + test_line_count = 1 "$midx_chain" && + cp $midx_chain $midx_chain.before && + + # Repack a small number of objects such that the + # progression is unbothered. Note that the existing pack + # is considered a repack candidate as the new layer + # threshold is set to 1. + test_commit small-1 && + git repack -d && + git repack --geometric=2 -d --write-midx=incremental \ + --write-bitmap-index && + + ! test_cmp $midx_chain.before $midx_chain && + cp $midx_chain $midx_chain.before + ) +' + +test_expect_success 'kept packs are excluded from repack' ' + git init kept-packs-excluded-from-repack && + ( + cd kept-packs-excluded-from-repack && + + git config maintenance.auto false && + git config repack.midxnewlayerthreshold 1 && + git config repack.midxsplitfactor 2 && + + # Create two equal-sized packs, marking one as kept. + for i in A B + do + test_commit "$i" && git repack -d || return 1 + done && + + keep=$(ls $packdir/pack-*.idx | head -n 1) && + touch "${keep%.idx}.keep" && + + # The kept pack is excluded as a repacking candidate + # entirely, so no rollup occurs as there is only one + # non-kept pack. A new MIDX layer is written containing + # that pack. + git repack --geometric=2 -d --write-midx=incremental && + + test-tool read-midx $objdir >actual && + grep "^pack-.*\.idx$" actual >actual.packs && + test_line_count = 1 actual.packs && + test_grep ! "$keep" actual.packs && + + git multi-pack-index verify && + + # All objects (from both kept and non-kept packs) + # must still be accessible. + git fsck + ) +' + +test_expect_success 'incremental MIDX with --max-pack-size' ' + git init incremental-midx-with--max-pack-size && + ( + cd incremental-midx-with--max-pack-size && + + git config maintenance.auto false && + git config repack.midxnewlayerthreshold 1 && + git config repack.midxsplitfactor 2 && + + create_layer --message="base" 1 && + + # Now add enough data that a small --max-pack-size will + # cause pack-objects to split its output. Create objects + # large enough to fill multiple packs. + test-tool genrandom foo 1M >big1 && + test-tool genrandom bar 1M >big2 && + git add big1 big2 && + test_tick && + git commit -a -m "big blobs" && + git repack -d && + + git repack --geometric=2 -d --write-midx=incremental \ + --write-bitmap-index --max-pack-size=1M && + + test_line_count = 1 "$midx_chain" && + test-tool read-midx $objdir >actual && + grep "^pack-.*\.idx$" actual >actual.packs && + test_line_count -gt 1 actual.packs && + + git multi-pack-index verify + ) +' + +test_expect_success 'noop repack preserves valid MIDX chain' ' + git init noop-repack-preserves-valid-midx-chain && + ( + cd noop-repack-preserves-valid-midx-chain && + + git config maintenance.auto false && + git config repack.midxnewlayerthreshold 1 && + git config repack.midxsplitfactor 2 && + + create_layer --message="base" 1 && + + git multi-pack-index verify && + cp $midx_chain $midx_chain.before && + + # Running again with no new objects should not break + # the MIDX chain. It produces "Nothing new to pack." + git repack --geometric=2 -d --write-midx=incremental \ + --write-bitmap-index && + + test_cmp $midx_chain.before $midx_chain && + + git multi-pack-index verify && + git fsck + ) +' + +test_expect_success 'repack -ad removes stale incremental chain' ' + git init repack--ad-removes-stale-incremental-chain && + ( + cd repack--ad-removes-stale-incremental-chain && + + git config maintenance.auto false && + git config repack.midxnewlayerthreshold 1 && + git config repack.midxsplitfactor 2 && + + create_layers <<-\EOF && + one 1 + two 1 + EOF + + test_path_is_file $midx_chain && + test_line_count = 2 $midx_chain && + + git repack -ad && + + test_path_is_missing $packdir/multi-pack-index && + test_dir_is_empty $midxdir + ) +' + +test_expect_success 'repack -ad --write-midx=incremental is safe' ' + git init ad-incremental-midx && + ( + cd ad-incremental-midx && + + git config maintenance.auto false && + + # Build a MIDX chain with multiple layers referencing + # distinct packs. + test_commit first && + git repack -d && + + test_commit second && + git repack -d --write-midx=incremental && + + git multi-pack-index verify && + test_line_count = 1 $midx_chain && + + # Now do a full -ad repack. The new pack contains all + # objects, but any retained MIDX layers still reference + # the now-deleted packs. + test_commit third && + git repack -ad --write-midx=incremental && + + git multi-pack-index verify && + git fsck && + git rev-list --all --objects >/dev/null + ) +' + +test_expect_success 'repack rejects invalid midxSplitFactor' ' + test_when_finished "rm -fr bad-split-factor" && + git init bad-split-factor && + ( + cd bad-split-factor && + test_commit base && + + for v in 0 1 -1 + do + test_must_fail git -c repack.midxSplitFactor=$v \ + repack -d --geometric=2 --write-midx=incremental 2>err && + test_grep "invalid value for --midx-split-factor" err || + return 1 + done + ) +' + +test_expect_success 'repack rejects invalid midxNewLayerThreshold' ' + test_when_finished "rm -fr bad-layer-threshold" && + git init bad-layer-threshold && + ( + cd bad-layer-threshold && + test_commit base && + + for v in 0 -1 + do + test_must_fail git -c repack.midxNewLayerThreshold=$v \ + repack -d --geometric=2 --write-midx=incremental 2>err && + test_grep "invalid value for --midx-new-layer-threshold" err || + return 1 + done + ) +' + +test_done
diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh index 64ac4f0..d61c4a4 100755 --- a/t/t7810-grep.sh +++ b/t/t7810-grep.sh
@@ -18,8 +18,9 @@ ' } -LC_ALL=en_US.UTF-8 test-tool regex '^.$' '¿' && - test_set_prereq MB_REGEX +test_lazy_prereq MB_REGEX ' + LC_ALL=en_US.UTF-8 test-tool regex "^.$" "¿" +' cat >hello.c <<EOF #include <assert.h> @@ -322,11 +323,11 @@ ${HC}file:1:5:mmap ${HC}file:2:5:mmap ${HC}file:3:5:mmap - ${HC}file:3:13:mmap + ${HC}file:3:14:mmap ${HC}file:4:5:mmap - ${HC}file:4:13:mmap + ${HC}file:4:14:mmap ${HC}file:5:5:mmap - ${HC}file:5:13:mmap + ${HC}file:5:14:mmap EOF git grep --column -n -o -e mmap $H >actual && test_cmp expected actual @@ -1929,4 +1930,62 @@ test_cmp expected actual ' +test_expect_success 'grep of revision in partial clone batches prefetch and honors pathspec' ' + test_when_finished "rm -rf grep-partial-src grep-partial" && + + git init grep-partial-src && + ( + cd grep-partial-src && + git config uploadpack.allowfilter 1 && + git config uploadpack.allowanysha1inwant 1 && + mkdir a b && + echo "needle in haystack" >a/matches.txt && + echo "nothing to see here" >a/nomatch.txt && + echo "needle again" >b/matches.md && + git add . && + git commit -m "initial" + ) && + + git clone --no-checkout --filter=blob:none \ + "file://$(pwd)/grep-partial-src" grep-partial && + + # All three blobs are missing immediately after a blobless clone. + git -C grep-partial rev-list --quiet --objects \ + --missing=print HEAD >missing && + test_line_count = 3 missing && + + # A pathspec-limited grep should prefetch only the two blobs + # in a/. It should fetch both blobs in one batched request. + GIT_TRACE2_EVENT="$(pwd)/grep-trace-pathspec" \ + git -C grep-partial grep -c "needle" HEAD -- "a/*.txt" >result && + + # Only a/matches.txt contains "needle" among the matched paths. + test_line_count = 1 result && + + # Exactly the two a/*.txt blobs should have been requested, and + # the server packed those two objects in the response. + test_trace2_data promisor fetch_count 2 <grep-trace-pathspec && + test_trace2_data pack-objects written 2 <grep-trace-pathspec && + + # b/matches.md should still be missing locally. + git -C grep-partial rev-list --quiet --objects \ + --missing=print HEAD >missing && + test_line_count = 1 missing && + + # A second grep without a pathspec must recurse into both + # subdirectories, but should request only the still-missing blob + # from the promisor. + GIT_TRACE2_EVENT="$(pwd)/grep-trace-all" \ + git -C grep-partial grep -c "needle" HEAD >result && + + test_line_count = 2 result && + test_trace2_data promisor fetch_count 1 <grep-trace-all && + test_trace2_data pack-objects written 1 <grep-trace-all && + + # Everything is local now. + git -C grep-partial rev-list --quiet --objects \ + --missing=print HEAD >missing && + test_line_count = 0 missing +' + test_done
diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh index 4700bea..d7f82e1 100755 --- a/t/t7900-maintenance.sh +++ b/t/t7900-maintenance.sh
@@ -73,6 +73,31 @@ test_subcommand ! git maintenance run --auto --quiet --detach <false ' +test_expect_success 'gc.auto config option' ' + GIT_TRACE2_EVENT="$(pwd)/default" git commit --quiet --allow-empty -m 1 && + test_subcommand git maintenance run --auto --quiet --detach <default && + GIT_TRACE2_EVENT="$(pwd)/true" \ + git -c gc.auto=1 commit --quiet --allow-empty -m 2 && + test_subcommand git maintenance run --auto --quiet --detach <true && + GIT_TRACE2_EVENT="$(pwd)/false" \ + git -c gc.auto=0 commit --quiet --allow-empty -m 3 && + test_subcommand ! git maintenance run --auto --quiet --detach <false +' + +test_expect_success 'maintenance.auto overrides gc.auto' ' + test_when_finished "rm -f trace" && + + test_config maintenance.auto false && + test_config gc.auto 1 && + GIT_TRACE2_EVENT="$(pwd)/trace" git commit --quiet --allow-empty -m 1 && + test_subcommand ! git maintenance run --auto --quiet --detach <trace && + + test_config maintenance.auto true && + test_config gc.auto 0 && + GIT_TRACE2_EVENT="$(pwd)/trace" git commit --quiet --allow-empty -m 1 && + test_subcommand git maintenance run --auto --quiet --detach <trace +' + for cfg in maintenance.autoDetach gc.autoDetach do test_expect_success "$cfg=true config option" ' @@ -101,8 +126,12 @@ test_expect_success 'register uses XDG_CONFIG_HOME config if it exists' ' test_when_finished rm -r .config/git/config && ( + # Override HOME so that .gitconfig (which test-lib.sh may + # have created, e.g. to set safe.bareRepository) does not + # take precedence over the XDG location. + HOME=$PWD/must-not-exist && XDG_CONFIG_HOME=.config && - export XDG_CONFIG_HOME && + export HOME XDG_CONFIG_HOME && mkdir -p $XDG_CONFIG_HOME/git && >$XDG_CONFIG_HOME/git/config && git maintenance register && @@ -124,8 +153,12 @@ test_expect_success 'unregister uses XDG_CONFIG_HOME config if it exists' ' test_when_finished rm -r .config/git/config && ( + # Override HOME so that .gitconfig (which test-lib.sh may + # have created, e.g. to set safe.bareRepository) does not + # take precedence over the XDG location. + HOME=$PWD/must-not-exist && XDG_CONFIG_HOME=.config && - export XDG_CONFIG_HOME && + export HOME XDG_CONFIG_HOME && mkdir -p $XDG_CONFIG_HOME/git && >$XDG_CONFIG_HOME/git/config && git maintenance register && @@ -532,7 +565,16 @@ # And verify that there are no loose objects anymore. git count-objects -v >count && - test_grep '^count: 0$' count + test_grep '^count: 0$' count && + + # Verify that no orphaned .idx files were left behind. On + # Windows, a missing odb_to_close causes the parent to hold + # mmap handles on .idx files, silently preventing their + # deletion by the child git-repack process. + ls .git/objects/pack/pack-*.idx .git/objects/pack/pack-*.pack | + sed "s/\.pack$/.idx/" | + sort | uniq -u >orphaned-idx && + test_must_be_empty orphaned-idx } test_expect_success 'geometric repacking task' ' @@ -580,8 +622,19 @@ # And these two small packs should now be merged via the # geometric repack. The large packfile should remain intact. + cp -R .git/objects .git/objects.save && run_and_verify_geometric_pack 2 && + # On Windows, verify the same with legacy delete semantics + # that reject deletion of mmap-held .idx files. + if test_have_prereq MINGW + then + rm -rf .git/objects && + mv .git/objects.save .git/objects && + test_env GIT_TEST_LEGACY_DELETE=1 \ + run_and_verify_geometric_pack 2 + fi && + # If we now add two more objects and repack twice we should # then see another all-into-one repack. This time around # though, as we have unreachable objects, we should also see a @@ -1438,6 +1491,64 @@ ) ' +test_expect_success PIPE '--detach holds maintenance lock until daemonized child exits' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + + git config maintenance.auto false && + git config core.lockfilepid true && + + git remote add origin /does/not/exist && + git config set remote.origin.uploadpack "cat fifo-uploadpack" && + + mkfifo fifo-uploadpack fifo-maint && + + # Open the maintenance FIFO, as otherwise spawning + # git-maintenance(1) would block. Note that we need to open it + # as read-write, as otherwise we would block here already. + exec 9<>fifo-maint && + + { git maintenance run --task=prefetch --detach 7>&9 & } && + parent="$!" && + + # Reap the parent process so that the exec call below will not + # get SIGCHLD. + wait "$parent" && + + # Open the git-upload-pack(1) FIFO for writing, which will + # block until the upload-pack script opens it for reading. Once + # exec returns, we know that the daemonized child is alive and + # pinned. + exec 8>fifo-uploadpack && + + test_path_is_file .git/objects/maintenance.lock && + test_path_is_file .git/objects/"maintenance~pid.lock" && + + # Verify that the maintenance.lock still exists, and + # that it was created by the parent process, not the + # child. + echo "pid $parent" >expect && + test_cmp expect .git/objects/"maintenance~pid.lock" && + + # Reopen the maintenance FIFO as read-only so that + # git-maintenance(1) is the only writer. This will cause it to + # close the FIFO once the process exits. + exec 9<&- && + exec 9<fifo-maint && + + # Close the FIFO used by git-upload-pack(1) to unblock it and + # then wait until the maintenance FIFO is closed by + # git-maintenance(1), indicating that it has exited. + exec 8>&- && + cat <&9 && + + test_path_is_missing .git/objects/maintenance.lock && + test_path_is_missing .git/objects/"maintenance~pid.lock" + ) +' + test_expect_success '--detach causes maintenance to run in background' ' test_when_finished "rm -rf repo" && git init repo &&
diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index af28b01..1ab98b9 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh
@@ -232,7 +232,7 @@ test_cmp expected.$(test_oid algo) a ' -test_expect_success 'exit if remote refs are ambigious' ' +test_expect_success 'exit if remote refs are ambiguous' ' git config --add svn-remote.svn.fetch \ bar:refs/remotes/git-svn && test_must_fail git svn migrate
diff --git a/t/t9138-git-svn-authors-prog.sh b/t/t9138-git-svn-authors-prog.sh index 784ec7f..5bb38cb 100755 --- a/t/t9138-git-svn-authors-prog.sh +++ b/t/t9138-git-svn-authors-prog.sh
@@ -68,8 +68,8 @@ ) ' -git --git-dir=x/.git config --unset svn.authorsfile -git --git-dir=x/.git config --unset svn.authorsprog +git --git-dir=x/.git config --unset svn.authorsfile || : +git --git-dir=x/.git config --unset svn.authorsprog || : test_expect_success 'authors-prog imported user without email' ' svn mkdir -m gg --username gg-hermit "$svnrepo"/gg &&
diff --git a/t/t9150/svk-merge.dump b/t/t9150/svk-merge.dump index 42f70db..3c46afc 100644 --- a/t/t9150/svk-merge.dump +++ b/t/t9150/svk-merge.dump
@@ -71,13 +71,13 @@ Node-action: add Prop-content-length: 10 Text-content-length: 2401 -Text-content-md5: bfd8ff778d1492dc6758567373176a89 +Text-content-md5: d6a3917748b0c09ad85c2783f1d4dac1 Content-length: 2411 PROPS-END # -DCOLLISION_CHECK if you believe that SHA1's # 1461501637330902918203684832716283019655932542976 hashes do not give you -# enough guarantees about no collisions between objects ever hapenning. +# enough guarantees about no collisions between objects ever happening. # # -DNSEC if you want git to care about sub-second file mtimes and ctimes. # Note that you need some new glibc (at least >2.2.4) for this, and it will @@ -201,12 +201,12 @@ Node-kind: file Node-action: change Text-content-length: 2465 -Text-content-md5: 16e38d9753b061731650561ce01b1195 +Text-content-md5: 3f413450a7a26596d9e512ee385a9b19 Content-length: 2465 # -DCOLLISION_CHECK if you believe that SHA1's # 1461501637330902918203684832716283019655932542976 hashes do not give you -# enough guarantees about no collisions between objects ever hapenning. +# enough guarantees about no collisions between objects ever happening. # # -DNSEC if you want git to care about sub-second file mtimes and ctimes. # Note that you need some new glibc (at least >2.2.4) for this, and it will @@ -305,12 +305,12 @@ Node-kind: file Node-action: change Text-content-length: 2521 -Text-content-md5: 0668418a621333f4aa8b6632cd63e2a0 +Text-content-md5: 89788781014278d76ff23648b8b08b2d Content-length: 2521 # -DCOLLISION_CHECK if you believe that SHA1's # 1461501637330902918203684832716283019655932542976 hashes do not give you -# enough guarantees about no collisions between objects ever hapenning. +# enough guarantees about no collisions between objects ever happening. # # -DNSEC if you want git to care about sub-second file mtimes and ctimes. # Note that you need some new glibc (at least >2.2.4) for this, and it will @@ -412,12 +412,12 @@ Node-kind: file Node-action: change Text-content-length: 2593 -Text-content-md5: 5ccff689fb290e00b85fe18ee50c54ba +Text-content-md5: 706d73919e6f319a0e624aa50c8b8b38 Content-length: 2593 # -DCOLLISION_CHECK if you believe that SHA1's # 1461501637330902918203684832716283019655932542976 hashes do not give you -# enough guarantees about no collisions between objects ever hapenning. +# enough guarantees about no collisions between objects ever happening. # # -DNSEC if you want git to care about sub-second file mtimes and ctimes. # Note that you need some new glibc (at least >2.2.4) for this, and it will @@ -529,12 +529,12 @@ Node-kind: file Node-action: change Text-content-length: 2713 -Text-content-md5: 0afbe34f244cd662b1f97d708c687f90 +Text-content-md5: 1c05266da99e8f01a5ccf816be47a484 Content-length: 2713 # -DCOLLISION_CHECK if you believe that SHA1's # 1461501637330902918203684832716283019655932542976 hashes do not give you -# enough guarantees about no collisions between objects ever hapenning. +# enough guarantees about no collisions between objects ever happening. # # -DNSEC if you want git to care about sub-second file mtimes and ctimes. # Note that you need some new glibc (at least >2.2.4) for this, and it will
diff --git a/t/t9151/svn-mergeinfo.dump b/t/t9151/svn-mergeinfo.dump index 47cafcf..ad74140 100644 --- a/t/t9151/svn-mergeinfo.dump +++ b/t/t9151/svn-mergeinfo.dump
@@ -80,14 +80,14 @@ Node-action: add Prop-content-length: 10 Text-content-length: 2401 -Text-content-md5: bfd8ff778d1492dc6758567373176a89 -Text-content-sha1: 103205ce331f7d64086dba497574734f78439590 +Text-content-md5: d6a3917748b0c09ad85c2783f1d4dac1 +Text-content-sha1: 9ffe895eb95d4a7c2ee2712dcf7a13637edee6a9 Content-length: 2411 PROPS-END # -DCOLLISION_CHECK if you believe that SHA1's # 1461501637330902918203684832716283019655932542976 hashes do not give you -# enough guarantees about no collisions between objects ever hapenning. +# enough guarantees about no collisions between objects ever happening. # # -DNSEC if you want git to care about sub-second file mtimes and ctimes. # Note that you need some new glibc (at least >2.2.4) for this, and it will @@ -194,8 +194,8 @@ Node-action: add Node-copyfrom-rev: 2 Node-copyfrom-path: trunk/Makefile -Text-copy-source-md5: bfd8ff778d1492dc6758567373176a89 -Text-copy-source-sha1: 103205ce331f7d64086dba497574734f78439590 +Text-copy-source-md5: d6a3917748b0c09ad85c2783f1d4dac1 +Text-copy-source-sha1: 9ffe895eb95d4a7c2ee2712dcf7a13637edee6a9 Revision-number: 4 @@ -228,8 +228,8 @@ Node-action: add Node-copyfrom-rev: 2 Node-copyfrom-path: trunk/Makefile -Text-copy-source-md5: bfd8ff778d1492dc6758567373176a89 -Text-copy-source-sha1: 103205ce331f7d64086dba497574734f78439590 +Text-copy-source-md5: d6a3917748b0c09ad85c2783f1d4dac1 +Text-copy-source-sha1: 9ffe895eb95d4a7c2ee2712dcf7a13637edee6a9 Revision-number: 5 @@ -254,13 +254,13 @@ Node-kind: file Node-action: change Text-content-length: 2465 -Text-content-md5: 16e38d9753b061731650561ce01b1195 -Text-content-sha1: 36da4b84ea9b64218ab48171dfc5c48ae025f38b +Text-content-md5: 3f413450a7a26596d9e512ee385a9b19 +Text-content-sha1: b3cd389d63c5e3af4fe22b7464cf97968662ad1a Content-length: 2465 # -DCOLLISION_CHECK if you believe that SHA1's # 1461501637330902918203684832716283019655932542976 hashes do not give you -# enough guarantees about no collisions between objects ever hapenning. +# enough guarantees about no collisions between objects ever happening. # # -DNSEC if you want git to care about sub-second file mtimes and ctimes. # Note that you need some new glibc (at least >2.2.4) for this, and it will @@ -359,13 +359,13 @@ Node-kind: file Node-action: change Text-content-length: 2521 -Text-content-md5: 0668418a621333f4aa8b6632cd63e2a0 -Text-content-sha1: 4f29afd038e52f45acb5ef8c41acfc70062a741a +Text-content-md5: 89788781014278d76ff23648b8b08b2d +Text-content-sha1: f52afb2d6230e5a418416b77c3c9ad610edfd202 Content-length: 2521 # -DCOLLISION_CHECK if you believe that SHA1's # 1461501637330902918203684832716283019655932542976 hashes do not give you -# enough guarantees about no collisions between objects ever hapenning. +# enough guarantees about no collisions between objects ever happening. # # -DNSEC if you want git to care about sub-second file mtimes and ctimes. # Note that you need some new glibc (at least >2.2.4) for this, and it will @@ -467,13 +467,13 @@ Node-kind: file Node-action: change Text-content-length: 2529 -Text-content-md5: f6b197cc3f2e89a83e545d4bb003de73 -Text-content-sha1: 2f656677cfec0bceec85e53036ffb63e25126f8e +Text-content-md5: abcac8d04eb061b0a3053e359e44a2a0 +Text-content-sha1: 866caf95e04809a5ed897aea41075b24833612ea Content-length: 2529 # -DCOLLISION_CHECK if you believe that SHA1's # 1461501637330902918203684832716283019655932542976 hashes do not give you -# enough guarantees about no collisions between objects ever hapenning. +# enough guarantees about no collisions between objects ever happening. # # -DNSEC if you want git to care about sub-second file mtimes and ctimes. # Note that you need some new glibc (at least >2.2.4) for this, and it will @@ -572,13 +572,13 @@ Node-kind: file Node-action: change Text-content-length: 2593 -Text-content-md5: 5ccff689fb290e00b85fe18ee50c54ba -Text-content-sha1: a13de8e23f1483efca3e57b2b64b0ae6f740ce10 +Text-content-md5: 706d73919e6f319a0e624aa50c8b8b38 +Text-content-sha1: 9992d5a9aea960c7856ef6a9364aedd5b710ef53 Content-length: 2593 # -DCOLLISION_CHECK if you believe that SHA1's # 1461501637330902918203684832716283019655932542976 hashes do not give you -# enough guarantees about no collisions between objects ever hapenning. +# enough guarantees about no collisions between objects ever happening. # # -DNSEC if you want git to care about sub-second file mtimes and ctimes. # Note that you need some new glibc (at least >2.2.4) for this, and it will @@ -689,8 +689,8 @@ Node-action: add Node-copyfrom-rev: 8 Node-copyfrom-path: branches/left/Makefile -Text-copy-source-md5: 5ccff689fb290e00b85fe18ee50c54ba -Text-copy-source-sha1: a13de8e23f1483efca3e57b2b64b0ae6f740ce10 +Text-copy-source-md5: 706d73919e6f319a0e624aa50c8b8b38 +Text-copy-source-sha1: 9992d5a9aea960c7856ef6a9364aedd5b710ef53 @@ -761,13 +761,13 @@ Node-kind: file Node-action: change Text-content-length: 2593 -Text-content-md5: 5ccff689fb290e00b85fe18ee50c54ba -Text-content-sha1: a13de8e23f1483efca3e57b2b64b0ae6f740ce10 +Text-content-md5: 706d73919e6f319a0e624aa50c8b8b38 +Text-content-sha1: 9992d5a9aea960c7856ef6a9364aedd5b710ef53 Content-length: 2593 # -DCOLLISION_CHECK if you believe that SHA1's # 1461501637330902918203684832716283019655932542976 hashes do not give you -# enough guarantees about no collisions between objects ever hapenning. +# enough guarantees about no collisions between objects ever happening. # # -DNSEC if you want git to care about sub-second file mtimes and ctimes. # Note that you need some new glibc (at least >2.2.4) for this, and it will @@ -942,13 +942,13 @@ Node-kind: file Node-action: change Text-content-length: 2713 -Text-content-md5: 0afbe34f244cd662b1f97d708c687f90 -Text-content-sha1: 46d9377d783e67a9b581da110352e799517c8a14 +Text-content-md5: 1c05266da99e8f01a5ccf816be47a484 +Text-content-sha1: 0cba212974e2b288389d73317f3220be11158e00 Content-length: 2713 # -DCOLLISION_CHECK if you believe that SHA1's # 1461501637330902918203684832716283019655932542976 hashes do not give you -# enough guarantees about no collisions between objects ever hapenning. +# enough guarantees about no collisions between objects ever happening. # # -DNSEC if you want git to care about sub-second file mtimes and ctimes. # Note that you need some new glibc (at least >2.2.4) for this, and it will @@ -1166,13 +1166,13 @@ Node-kind: file Node-action: change Text-content-length: 2713 -Text-content-md5: 0afbe34f244cd662b1f97d708c687f90 -Text-content-sha1: 46d9377d783e67a9b581da110352e799517c8a14 +Text-content-md5: 1c05266da99e8f01a5ccf816be47a484 +Text-content-sha1: 0cba212974e2b288389d73317f3220be11158e00 Content-length: 2713 # -DCOLLISION_CHECK if you believe that SHA1's # 1461501637330902918203684832716283019655932542976 hashes do not give you -# enough guarantees about no collisions between objects ever hapenning. +# enough guarantees about no collisions between objects ever happening. # # -DNSEC if you want git to care about sub-second file mtimes and ctimes. # Note that you need some new glibc (at least >2.2.4) for this, and it will @@ -1408,13 +1408,13 @@ Node-kind: file Node-action: change Text-content-length: 2713 -Text-content-md5: 0afbe34f244cd662b1f97d708c687f90 -Text-content-sha1: 46d9377d783e67a9b581da110352e799517c8a14 +Text-content-md5: 1c05266da99e8f01a5ccf816be47a484 +Text-content-sha1: 0cba212974e2b288389d73317f3220be11158e00 Content-length: 2713 # -DCOLLISION_CHECK if you believe that SHA1's # 1461501637330902918203684832716283019655932542976 hashes do not give you -# enough guarantees about no collisions between objects ever hapenning. +# enough guarantees about no collisions between objects ever happening. # # -DNSEC if you want git to care about sub-second file mtimes and ctimes. # Note that you need some new glibc (at least >2.2.4) for this, and it will
diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh index 14cbe96..581cf3d 100755 --- a/t/t9200-git-cvsexportcommit.sh +++ b/t/t9200-git-cvsexportcommit.sh
@@ -11,8 +11,7 @@ test_done fi -cvs >/dev/null 2>&1 -if test $? -ne 1 +if ! cvs version >/dev/null 2>&1 then skip_all='skipping git cvsexportcommit tests, cvs not found' test_done
diff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh index 009437a..f2a6df7 100755 --- a/t/t9210-scalar.sh +++ b/t/t9210-scalar.sh
@@ -152,6 +152,10 @@ ' test_expect_success 'scalar clone' ' + # index.skipHash (Scalar default) and GIT_TEST_SPLIT_INDEX are + # incompatible: the shared index gets a null OID and fails to + # load on re-read. + sane_unset GIT_TEST_SPLIT_INDEX && second=$(git rev-parse --verify second:second.t) && scalar clone "file://$(pwd)" cloned --single-branch && ( @@ -182,6 +186,7 @@ ' test_expect_success 'scalar clone --no-... opts' ' + sane_unset GIT_TEST_SPLIT_INDEX && # Note: redirect stderr always to avoid having a verbose test # run result in a difference in the --[no-]progress option. GIT_TRACE2_EVENT="$(pwd)/no-opt-trace" scalar clone \ @@ -307,6 +312,7 @@ SQ="'" test_expect_success UNZIP 'scalar diagnose' ' + sane_unset GIT_TEST_SPLIT_INDEX && scalar clone "file://$(pwd)" cloned --single-branch && git repack && echo "$(pwd)/.git/objects/" >>cloned/src/.git/objects/info/alternates &&
diff --git a/t/t9211-scalar-clone.sh b/t/t9211-scalar-clone.sh index bfbf22a..2043f48 100755 --- a/t/t9211-scalar-clone.sh +++ b/t/t9211-scalar-clone.sh
@@ -8,6 +8,11 @@ GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab cron.txt,launchctl:true,schtasks:true" export GIT_TEST_MAINT_SCHEDULER +# index.skipHash (Scalar default) and GIT_TEST_SPLIT_INDEX are +# incompatible: the shared index gets a null OID and fails to +# load on re-read. Every test here uses scalar clone. +sane_unset GIT_TEST_SPLIT_INDEX + test_expect_success 'set up repository to clone' ' rm -rf .git && git init to-clone &&
diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh index e499c7f..4b45398 100755 --- a/t/t9400-git-cvsserver-server.sh +++ b/t/t9400-git-cvsserver-server.sh
@@ -17,12 +17,13 @@ skip_all='skipping git cvsserver tests, perl not available' test_done fi -cvs >/dev/null 2>&1 -if test $? -ne 1 + +if ! cvs version >/dev/null 2>&1 then skip_all='skipping git-cvsserver tests, cvs not found' test_done fi + perl -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || { skip_all='skipping git-cvsserver tests, Perl SQLite interface unavailable' test_done
diff --git a/t/t9401-git-cvsserver-crlf.sh b/t/t9401-git-cvsserver-crlf.sh index a34805a..6b4cbb1 100755 --- a/t/t9401-git-cvsserver-crlf.sh +++ b/t/t9401-git-cvsserver-crlf.sh
@@ -60,12 +60,12 @@ return $stat } -cvs >/dev/null 2>&1 -if test $? -ne 1 +if ! cvs version >/dev/null 2>&1 then skip_all='skipping git-cvsserver tests, cvs not found' test_done fi + if ! test_have_prereq PERL then skip_all='skipping git-cvsserver tests, perl not available'
diff --git a/t/t9402-git-cvsserver-refs.sh b/t/t9402-git-cvsserver-refs.sh index 2ee41f9..65f2cee 100755 --- a/t/t9402-git-cvsserver-refs.sh +++ b/t/t9402-git-cvsserver-refs.sh
@@ -68,12 +68,12 @@ ######### -cvs >/dev/null 2>&1 -if test $? -ne 1 +if ! cvs version >/dev/null 2>&1 then skip_all='skipping git-cvsserver tests, cvs not found' test_done fi + if ! test_have_prereq PERL then skip_all='skipping git-cvsserver tests, perl not available'
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index 2f9a597..55dc9ea 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh
@@ -590,12 +590,10 @@ __gitcomp "$invalid_variable_name" ' -read -r -d "" refs <<-\EOF -main +refs='main maint next -seen -EOF +seen' test_expect_success '__gitcomp_nl - trailing space' ' test_gitcomp_nl "m" "$refs" <<-EOF @@ -2446,7 +2444,7 @@ >repeated-quoted/2-file && >repeated-quoted/3\"file && # ... and here, too. - # Still, we shold only list the directory name only once. + # Still, we should list the directory name only once. test_path_completion repeated repeated-quoted '
diff --git a/t/t9904-url-parse.sh b/t/t9904-url-parse.sh new file mode 100755 index 0000000..8a369d2 --- /dev/null +++ b/t/t9904-url-parse.sh
@@ -0,0 +1,319 @@ +#!/bin/sh +# +# Copyright (c) 2024 Matheus Afonso Martins Moreira +# + +test_description='git url-parse tests' + +. ./test-lib.sh + +test_expect_success 'git url-parse -- ssh syntax' ' + git url-parse "ssh://user@example.com:1234/repository/path" && + git url-parse "ssh://user@example.com/repository/path" && + git url-parse "ssh://example.com:1234/repository/path" && + git url-parse "ssh://example.com/repository/path" +' + +test_expect_success 'git url-parse -- git syntax' ' + git url-parse "git://example.com:1234/repository/path" && + git url-parse "git://example.com/repository/path" +' + +test_expect_success 'git url-parse -- http syntax' ' + git url-parse "https://example.com:1234/repository/path" && + git url-parse "https://example.com/repository/path" && + git url-parse "http://example.com:1234/repository/path" && + git url-parse "http://example.com/repository/path" +' + +test_expect_success 'git url-parse -- scp syntax' ' + git url-parse "user@example.com:/repository/path" && + git url-parse "example.com:/repository/path" +' + +test_expect_success 'git url-parse -- username expansion - ssh syntax' ' + git url-parse "ssh://user@example.com:1234/~user/repository" && + git url-parse "ssh://user@example.com/~user/repository" && + git url-parse "ssh://example.com:1234/~user/repository" && + git url-parse "ssh://example.com/~user/repository" +' + +test_expect_success 'git url-parse -- username expansion - git syntax' ' + git url-parse "git://example.com:1234/~user/repository" && + git url-parse "git://example.com/~user/repository" +' + +test_expect_success 'git url-parse -- username expansion - scp syntax' ' + git url-parse "user@example.com:~user/repository" && + git url-parse "example.com:~user/repository" +' + +test_expect_success 'git url-parse -- file urls' ' + git url-parse "file:///repository/path" && + git url-parse "file://" +' + +test_expect_success 'git url-parse -c scheme -- ssh syntax' ' + test ssh = "$(git url-parse -c scheme "ssh://user@example.com:1234/repository/path")" && + test ssh = "$(git url-parse -c scheme "ssh://user@example.com/repository/path")" && + test ssh = "$(git url-parse -c scheme "ssh://example.com:1234/repository/path")" && + test ssh = "$(git url-parse -c scheme "ssh://example.com/repository/path")" +' + +test_expect_success 'git url-parse -c scheme -- git syntax' ' + test git = "$(git url-parse -c scheme "git://example.com:1234/repository/path")" && + test git = "$(git url-parse -c scheme "git://example.com/repository/path")" +' + +test_expect_success 'git url-parse -c scheme -- http syntax' ' + test https = "$(git url-parse -c scheme "https://example.com:1234/repository/path")" && + test https = "$(git url-parse -c scheme "https://example.com/repository/path")" && + test http = "$(git url-parse -c scheme "http://example.com:1234/repository/path")" && + test http = "$(git url-parse -c scheme "http://example.com/repository/path")" +' + +test_expect_success 'git url-parse -c scheme -- scp syntax' ' + test ssh = "$(git url-parse -c scheme "user@example.com:/repository/path")" && + test ssh = "$(git url-parse -c scheme "example.com:/repository/path")" +' + +test_expect_success 'git url-parse -c user -- ssh syntax' ' + test user = "$(git url-parse -c user "ssh://user@example.com:1234/repository/path")" && + test user = "$(git url-parse -c user "ssh://user@example.com/repository/path")" && + test "" = "$(git url-parse -c user "ssh://example.com:1234/repository/path")" && + test "" = "$(git url-parse -c user "ssh://example.com/repository/path")" +' + +test_expect_success 'git url-parse -c user -- git syntax' ' + test "" = "$(git url-parse -c user "git://example.com:1234/repository/path")" && + test "" = "$(git url-parse -c user "git://example.com/repository/path")" +' + +test_expect_success 'git url-parse -c user -- http syntax' ' + test "" = "$(git url-parse -c user "https://example.com:1234/repository/path")" && + test "" = "$(git url-parse -c user "https://example.com/repository/path")" && + test "" = "$(git url-parse -c user "http://example.com:1234/repository/path")" && + test "" = "$(git url-parse -c user "http://example.com/repository/path")" +' + +test_expect_success 'git url-parse -c user -- scp syntax' ' + test user = "$(git url-parse -c user "user@example.com:/repository/path")" && + test "" = "$(git url-parse -c user "example.com:/repository/path")" +' + +test_expect_success 'git url-parse -c password -- http syntax' ' + test secret = "$(git url-parse -c password "https://user:secret@example.com:1234/repository/path")" && + test secret = "$(git url-parse -c password "http://user:secret@example.com/repository/path")" && + test "" = "$(git url-parse -c password "https://user@example.com/repository/path")" && + test "" = "$(git url-parse -c password "https://example.com/repository/path")" +' + +test_expect_success 'git url-parse -c host -- ssh syntax' ' + test example.com = "$(git url-parse -c host "ssh://user@example.com:1234/repository/path")" && + test example.com = "$(git url-parse -c host "ssh://user@example.com/repository/path")" && + test example.com = "$(git url-parse -c host "ssh://example.com:1234/repository/path")" && + test example.com = "$(git url-parse -c host "ssh://example.com/repository/path")" +' + +test_expect_success 'git url-parse -c host -- git syntax' ' + test example.com = "$(git url-parse -c host "git://example.com:1234/repository/path")" && + test example.com = "$(git url-parse -c host "git://example.com/repository/path")" +' + +test_expect_success 'git url-parse -c host -- http syntax' ' + test example.com = "$(git url-parse -c host "https://example.com:1234/repository/path")" && + test example.com = "$(git url-parse -c host "https://example.com/repository/path")" && + test example.com = "$(git url-parse -c host "http://example.com:1234/repository/path")" && + test example.com = "$(git url-parse -c host "http://example.com/repository/path")" +' + +test_expect_success 'git url-parse -c host -- scp syntax' ' + test example.com = "$(git url-parse -c host "user@example.com:/repository/path")" && + test example.com = "$(git url-parse -c host "example.com:/repository/path")" +' + +test_expect_success 'git url-parse -c port -- ssh syntax' ' + test 1234 = "$(git url-parse -c port "ssh://user@example.com:1234/repository/path")" && + test "" = "$(git url-parse -c port "ssh://user@example.com/repository/path")" && + test 1234 = "$(git url-parse -c port "ssh://example.com:1234/repository/path")" && + test "" = "$(git url-parse -c port "ssh://example.com/repository/path")" +' + +test_expect_success 'git url-parse -c port -- git syntax' ' + test 1234 = "$(git url-parse -c port "git://example.com:1234/repository/path")" && + test "" = "$(git url-parse -c port "git://example.com/repository/path")" +' + +test_expect_success 'git url-parse -c port -- http syntax' ' + test 1234 = "$(git url-parse -c port "https://example.com:1234/repository/path")" && + test "" = "$(git url-parse -c port "https://example.com/repository/path")" && + test 1234 = "$(git url-parse -c port "http://example.com:1234/repository/path")" && + test "" = "$(git url-parse -c port "http://example.com/repository/path")" +' + +test_expect_success 'git url-parse -c port -- scp syntax' ' + test "" = "$(git url-parse -c port "user@example.com:/repository/path")" && + test "" = "$(git url-parse -c port "example.com:/repository/path")" +' + +test_expect_success 'git url-parse -c path -- ssh syntax' ' + test "/repository/path" = "$(git url-parse -c path "ssh://user@example.com:1234/repository/path")" && + test "/repository/path" = "$(git url-parse -c path "ssh://user@example.com/repository/path")" && + test "/repository/path" = "$(git url-parse -c path "ssh://example.com:1234/repository/path")" && + test "/repository/path" = "$(git url-parse -c path "ssh://example.com/repository/path")" +' + +test_expect_success 'git url-parse -c path -- git syntax' ' + test "/repository/path" = "$(git url-parse -c path "git://example.com:1234/repository/path")" && + test "/repository/path" = "$(git url-parse -c path "git://example.com/repository/path")" +' + +test_expect_success 'git url-parse -c path -- http syntax' ' + test "/repository/path" = "$(git url-parse -c path "https://example.com:1234/repository/path")" && + test "/repository/path" = "$(git url-parse -c path "https://example.com/repository/path")" && + test "/repository/path" = "$(git url-parse -c path "http://example.com:1234/repository/path")" && + test "/repository/path" = "$(git url-parse -c path "http://example.com/repository/path")" +' + +test_expect_success 'git url-parse -c path -- scp syntax' ' + test "/repository/path" = "$(git url-parse -c path "user@example.com:/repository/path")" && + test "/repository/path" = "$(git url-parse -c path "example.com:/repository/path")" +' + +test_expect_success 'git url-parse -c path -- username expansion - ssh syntax' ' + test "~user/repository" = "$(git url-parse -c path "ssh://user@example.com:1234/~user/repository")" && + test "~user/repository" = "$(git url-parse -c path "ssh://user@example.com/~user/repository")" && + test "~user/repository" = "$(git url-parse -c path "ssh://example.com:1234/~user/repository")" && + test "~user/repository" = "$(git url-parse -c path "ssh://example.com/~user/repository")" +' + +test_expect_success 'git url-parse -c path -- username expansion - git syntax' ' + test "~user/repository" = "$(git url-parse -c path "git://example.com:1234/~user/repository")" && + test "~user/repository" = "$(git url-parse -c path "git://example.com/~user/repository")" +' + +test_expect_success 'git url-parse -c path -- username expansion - scp syntax' ' + test "~user/repository" = "$(git url-parse -c path "user@example.com:~user/repository")" && + test "~user/repository" = "$(git url-parse -c path "example.com:~user/repository")" +' + +test_expect_success 'git url-parse -c path -- username expansion strips query and fragment' ' + test "~user/repository" = "$(git url-parse -c path "ssh://example.com/~user/repository?query")" && + test "~user/repository" = "$(git url-parse -c path "ssh://example.com/~user/repository#fragment")" && + test "~user/repository" = "$(git url-parse -c path "git://example.com/~user/repository?query")" && + test "~user/repository" = "$(git url-parse -c path "user@example.com:~user/repository?query")" +' + +test_expect_success 'git url-parse -- ssh syntax with IPv6' ' + git url-parse "ssh://user@[::1]:1234/repository/path" && + git url-parse "ssh://user@[::1]/repository/path" && + git url-parse "ssh://[::1]:1234/repository/path" && + git url-parse "ssh://[::1]/repository/path" && + git url-parse "ssh://[2001:db8::1]/repository/path" +' + +test_expect_success 'git url-parse -- git syntax with IPv6' ' + git url-parse "git://[::1]:9418/repository/path" && + git url-parse "git://[::1]/repository/path" +' + +test_expect_success 'git url-parse -- http syntax with IPv6' ' + git url-parse "https://[::1]:1234/repository/path" && + git url-parse "https://[::1]/repository/path" && + git url-parse "http://[2001:db8::1]/repository/path" +' + +test_expect_success 'git url-parse -c host -- IPv6 in URL form' ' + test "[::1]" = "$(git url-parse -c host "ssh://user@[::1]:1234/repository/path")" && + test "[::1]" = "$(git url-parse -c host "ssh://[::1]/repository/path")" && + test "[2001:db8::1]" = "$(git url-parse -c host "ssh://[2001:db8::1]/repository/path")" && + test "[::1]" = "$(git url-parse -c host "git://[::1]/repository/path")" && + test "[2001:db8::1]" = "$(git url-parse -c host "https://[2001:db8::1]/repository/path")" +' + +test_expect_success 'git url-parse -c port -- IPv6 in URL form' ' + test 1234 = "$(git url-parse -c port "ssh://user@[::1]:1234/repository/path")" && + test "" = "$(git url-parse -c port "ssh://[::1]/repository/path")" && + test 9418 = "$(git url-parse -c port "git://[::1]:9418/repository/path")" +' + +test_expect_success 'git url-parse -- scp syntax with IPv6' ' + git url-parse "[::1]:repository/path" && + git url-parse "user@[::1]:repository/path" && + git url-parse "[2001:db8::1]:repo" +' + +test_expect_success 'git url-parse -- scp syntax with bracketed hostname' ' + git url-parse "[myhost]:src" && + git url-parse "user@[myhost]:src" +' + +test_expect_success 'git url-parse -- scp syntax with bracketed host:port' ' + git url-parse "[myhost:123]:src" && + git url-parse "user@[myhost:123]:src" +' + +test_expect_success 'git url-parse -c host -- scp+IPv6' ' + test "[::1]" = "$(git url-parse -c host "[::1]:repository/path")" && + test "[::1]" = "$(git url-parse -c host "user@[::1]:repository/path")" && + test "[2001:db8::1]" = "$(git url-parse -c host "[2001:db8::1]:repo")" +' + +test_expect_success 'git url-parse -c path -- scp+IPv6' ' + test "/repository/path" = "$(git url-parse -c path "[::1]:/repository/path")" && + test "/repository/path" = "$(git url-parse -c path "[::1]:repository/path")" && + test "/repo" = "$(git url-parse -c path "[2001:db8::1]:repo")" +' + +test_expect_success 'git url-parse -c host,port,path -- scp [host:port]:src' ' + test myhost = "$(git url-parse -c host "[myhost:123]:src")" && + test 123 = "$(git url-parse -c port "[myhost:123]:src")" && + test "/src" = "$(git url-parse -c path "[myhost:123]:src")" +' + +test_expect_success 'git url-parse -c host,path -- scp [host]:src' ' + test myhost = "$(git url-parse -c host "[myhost]:src")" && + test "/src" = "$(git url-parse -c path "[myhost]:src")" +' + +test_expect_success 'git url-parse -c user -- scp with user@ and brackets' ' + test user = "$(git url-parse -c user "user@[::1]:repo")" && + test user = "$(git url-parse -c user "user@[myhost:123]:src")" && + test user = "$(git url-parse -c user "user@[myhost]:src")" +' + +test_expect_success 'git url-parse -- scp+IPv6 with username expansion' ' + test "~user/repo" = "$(git url-parse -c path "[::1]:~user/repo")" && + test "~user/repo" = "$(git url-parse -c path "user@[::1]:~user/repo")" +' + +test_expect_success 'git url-parse fails on invalid URL' ' + test_must_fail git url-parse "not a url" +' + +test_expect_success 'git url-parse helpful error for absolute local path' ' + test_must_fail git url-parse "/abs/path" 2>err && + test_grep "is not a URL" err && + test_grep "file:///" err +' + +test_expect_success 'git url-parse helpful error for relative local path' ' + test_must_fail git url-parse "./rel" 2>err && + test_grep "is not a URL" err && + test_grep "absolute path" err +' + +test_expect_success 'git url-parse fails on unknown -c component name' ' + test_must_fail git url-parse -c bogus "https://example.com/repo" +' + +test_expect_success 'git url-parse fails on URL missing host' ' + test_must_fail git url-parse "https://" +' + +test_expect_success 'git url-parse with no URL prints usage' ' + test_must_fail git url-parse 2>err && + test_grep "usage:" err +' + +test_done
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index f3af10f..809c662 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh
@@ -1195,8 +1195,9 @@ echo >&7 "test_must_fail: only 'git' is allowed: $*" return 1 fi - "$@" 2>&7 - exit_code=$? + + exit_code=0; "$@" 2>&7 || exit_code=$? + if test $exit_code -eq 0 && ! list_contains "$_test_ok" success then echo >&4 "test_must_fail: command succeeded: $*" @@ -1247,8 +1248,7 @@ test_expect_code () { want_code=$1 shift - "$@" 2>&7 - exit_code=$? + exit_code=0; "$@" 2>&7 || exit_code=$? if test $exit_code = $want_code then return 0 @@ -1512,7 +1512,7 @@ test "${BASH_SUBSHELL-0}" = 0 || BUG "test_when_finished does nothing in a subshell" test_cleanup="{ $* - } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup" + } || eval_ret=\$?; $test_cleanup" } # This function can be used to schedule some commands to be run @@ -1540,7 +1540,7 @@ test "${BASH_SUBSHELL-0}" = 0 || BUG "test_atexit does nothing in a subshell" test_atexit_cleanup="{ $* - } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_atexit_cleanup" + } || eval_ret=\$?; $test_atexit_cleanup" } # Deprecated wrapper for "git init", use "git init" directly instead @@ -2069,3 +2069,11 @@ test_redact_non_printables () { tr -d "\n\r" | tr "[\001-\040][\177-\377]" "." } + +# Remove .gitconfig entries from a file in place. test-lib.sh may +# create $HOME/.gitconfig (e.g. to set safe.bareRepository) which +# can appear in ls-files or status output. +test_filter_gitconfig () { + sed "/\\.gitconfig/d" "$1" >"$1.filtered" && + mv "$1.filtered" "$1" +}
diff --git a/t/test-lib-github-workflow-markup.sh b/t/test-lib-github-workflow-markup.sh index 33405c9..fa29a62 100644 --- a/t/test-lib-github-workflow-markup.sh +++ b/t/test-lib-github-workflow-markup.sh
@@ -18,7 +18,7 @@ # # The idea is for `test-lib.sh` to source this file when run in GitHub # workflows; these functions will then override (empty) functions -# that are are called at the appropriate times during the test runs. +# that are called at the appropriate times during the test runs. test_skip_test_preamble=t
diff --git a/t/test-lib-junit.sh b/t/test-lib-junit.sh index 76cbbd3..f4994dd 100644 --- a/t/test-lib-junit.sh +++ b/t/test-lib-junit.sh
@@ -19,7 +19,7 @@ # # The idea is for `test-lib.sh` to source this file when the user asks # for JUnit XML; these functions will then override (empty) functions -# that are are called at the appropriate times during the test runs. +# that are called at the appropriate times during the test runs. start_test_output () { junit_xml_dir="$TEST_OUTPUT_DIRECTORY/out"
diff --git a/t/test-lib.sh b/t/test-lib.sh index 70fd3e9..ceefb99 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh
@@ -15,6 +15,31 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see https://www.gnu.org/licenses/ . +# Enable the use of errexit so that any unexpected failures will cause us to +# abort tests, even when outside of a specific test case. +# +# Note that we only enable this on Bash 5 and newer, or when explicitly +# requested by the user via `GIT_TEST_USE_SET_E=true`. This ib secause `set -e` +# has wildly different behaviour across shells. The list of default-enabled +# shells may be extended going forward. +if test -z "$GIT_TEST_USE_SET_E" && test "${BASH_VERSINFO:=0}" -ge 5 +then + GIT_TEST_USE_SET_E=true +fi + +# We cannot use `test-tool env-helper` here, as it's not yet available. +case "${GIT_TEST_USE_SET_E:-false}" in +1|on|true|yes) + set -e + ;; +0|off|false|no) + ;; +*) + echo "GIT_TEST_USE_SET_E requires a boolean" >&2 + exit 1 + ;; +esac + # Test the binaries we have just built. The tests are kept in # t/ subdirectory and are run in 'trash directory' subdirectory. if test -z "$TEST_DIRECTORY" @@ -143,8 +168,8 @@ ################################################################ # It appears that people try to run tests without building... GIT_BINARY="${GIT_TEST_INSTALLED:-$GIT_BUILD_DIR}/git$X" -"$GIT_BINARY" >/dev/null -if test $? != 1 + +if ! "$GIT_BINARY" version >/dev/null then if test -n "$GIT_TEST_INSTALLED" then @@ -454,8 +479,10 @@ # from any previous runs. >"$GIT_TEST_TEE_OUTPUT_FILE" - (GIT_TEST_TEE_STARTED=done ${TEST_SHELL_PATH} "$0" "$@" 2>&1; - echo $? >"$TEST_RESULTS_BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE" + ( + ret=0 && GIT_TEST_TEE_STARTED=done ${TEST_SHELL_PATH} "$0" "$@" 2>&1 || ret=$? + echo "$ret" >"$TEST_RESULTS_BASE.exit" + ) | tee -a "$GIT_TEST_TEE_OUTPUT_FILE" test "$(cat "$TEST_RESULTS_BASE.exit")" = 0 exit fi @@ -1272,10 +1299,10 @@ error "Tests passed but trash directory already removed before test cleanup; aborting" cd "$TRASH_DIRECTORY/.." && - rm -fr "$TRASH_DIRECTORY" || { + rm -fr "$TRASH_DIRECTORY" 2>/dev/null || { # try again in a bit sleep 5; - rm -fr "$TRASH_DIRECTORY" + rm -fr "$TRASH_DIRECTORY" 2>/dev/null } || error "Tests passed but test cleanup failed; aborting" fi @@ -1505,6 +1532,12 @@ BAIL_OUT 'You need to build test-tool; Run "make t/helper/test-tool" in the source (toplevel) directory' fi +if test -n "$HARNESS_ACTIVE" +then + say "TAP version 13" + say "pragma +strict" +fi + # Are we running this test at all? remove_trash= this_test=${0##*/} @@ -1597,6 +1630,19 @@ TRASH_DIRECTORY=$(pwd) HOME="$TRASH_DIRECTORY" +if test -n "$WITH_BREAKING_CHANGES" +then + git config --global safe.bareRepository all && + # Only write to .git/info/exclude when the directory exists + # (i.e. when git init created the repo). If we mkdir -p it + # ourselves, tests that expect to create .git/info/ themselves + # (e.g. t0008) would fail. + if test -d .git/info + then + echo "/.gitconfig" >>.git/info/exclude + fi +fi + start_test_output "$0" # Convenience
diff --git a/t/unit-tests/clar/README.md b/t/unit-tests/clar/README.md index 4159598..a45b9c8 100644 --- a/t/unit-tests/clar/README.md +++ b/t/unit-tests/clar/README.md
@@ -138,7 +138,7 @@ __Caution:__ If you use assertions inside of `test_suitename__initialize`, make sure that you do not rely on `__initialize` being completely run inside your `test_suitename__cleanup` function. Otherwise you might -encounter ressource cleanup twice. +encounter resource cleanup twice. ## How does Clar work?
diff --git a/t/unit-tests/u-odb-inmemory.c b/t/unit-tests/u-odb-inmemory.c new file mode 100644 index 0000000..482502e --- /dev/null +++ b/t/unit-tests/u-odb-inmemory.c
@@ -0,0 +1,313 @@ +#include "unit-test.h" +#include "hex.h" +#include "odb/source-inmemory.h" +#include "odb/streaming.h" +#include "oidset.h" +#include "repository.h" +#include "strbuf.h" + +#define RANDOM_OID "da39a3ee5e6b4b0d3255bfef95601890afd80709" +#define FOOBAR_OID "f6ea0495187600e7b2288c8ac19c5886383a4632" + +static struct repository repo = { + .hash_algo = &hash_algos[GIT_HASH_SHA1], +}; +static struct object_database *odb; + +static void cl_assert_object_info(struct odb_source_inmemory *source, + const struct object_id *oid, + enum object_type expected_type, + const char *expected_content) +{ + enum object_type actual_type; + unsigned long actual_size; + void *actual_content; + struct object_info oi = { + .typep = &actual_type, + .sizep = &actual_size, + .contentp = &actual_content, + }; + + cl_must_pass(odb_source_read_object_info(&source->base, oid, &oi, 0)); + cl_assert_equal_u(actual_size, strlen(expected_content)); + cl_assert_equal_u(actual_type, expected_type); + cl_assert_equal_s((char *) actual_content, expected_content); + + free(actual_content); +} + +void test_odb_inmemory__initialize(void) +{ + odb = odb_new(&repo, "", ""); +} + +void test_odb_inmemory__cleanup(void) +{ + odb_free(odb); +} + +void test_odb_inmemory__new(void) +{ + struct odb_source_inmemory *source = odb_source_inmemory_new(odb); + cl_assert_equal_i(source->base.type, ODB_SOURCE_INMEMORY); + odb_source_free(&source->base); +} + +void test_odb_inmemory__read_missing_object(void) +{ + struct odb_source_inmemory *source = odb_source_inmemory_new(odb); + struct object_id oid; + const char *end; + + cl_must_pass(parse_oid_hex_algop(RANDOM_OID, &oid, &end, repo.hash_algo)); + cl_must_fail(odb_source_read_object_info(&source->base, &oid, NULL, 0)); + + odb_source_free(&source->base); +} + +void test_odb_inmemory__read_empty_tree(void) +{ + struct odb_source_inmemory *source = odb_source_inmemory_new(odb); + cl_assert_object_info(source, repo.hash_algo->empty_tree, OBJ_TREE, ""); + odb_source_free(&source->base); +} + +void test_odb_inmemory__read_written_object(void) +{ + struct odb_source_inmemory *source = odb_source_inmemory_new(odb); + const char data[] = "foobar"; + struct object_id written_oid; + + cl_must_pass(odb_source_write_object(&source->base, data, strlen(data), + OBJ_BLOB, &written_oid, NULL, 0)); + cl_assert_equal_s(oid_to_hex(&written_oid), FOOBAR_OID); + cl_assert_object_info(source, &written_oid, OBJ_BLOB, "foobar"); + + odb_source_free(&source->base); +} + +void test_odb_inmemory__read_stream_object(void) +{ + struct odb_source_inmemory *source = odb_source_inmemory_new(odb); + struct odb_read_stream *stream; + struct object_id written_oid; + const char data[] = "foobar"; + char buf[3] = { 0 }; + + cl_must_pass(odb_source_write_object(&source->base, data, strlen(data), + OBJ_BLOB, &written_oid, NULL, 0)); + + cl_must_pass(odb_source_read_object_stream(&stream, &source->base, + &written_oid)); + cl_assert_equal_i(stream->type, OBJ_BLOB); + cl_assert_equal_u(stream->size, 6); + + cl_assert_equal_i(odb_read_stream_read(stream, buf, 2), 2); + cl_assert_equal_s(buf, "fo"); + cl_assert_equal_i(odb_read_stream_read(stream, buf, 2), 2); + cl_assert_equal_s(buf, "ob"); + cl_assert_equal_i(odb_read_stream_read(stream, buf, 2), 2); + cl_assert_equal_s(buf, "ar"); + cl_assert_equal_i(odb_read_stream_read(stream, buf, 2), 0); + + odb_read_stream_close(stream); + odb_source_free(&source->base); +} + +static int add_one_object(const struct object_id *oid, + struct object_info *oi UNUSED, + void *payload) +{ + struct oidset *actual_oids = payload; + cl_must_pass(oidset_insert(actual_oids, oid)); + return 0; +} + +void test_odb_inmemory__for_each_object(void) +{ + struct odb_source_inmemory *source = odb_source_inmemory_new(odb); + struct odb_for_each_object_options opts = { 0 }; + struct oidset expected_oids = OIDSET_INIT; + struct oidset actual_oids = OIDSET_INIT; + struct strbuf buf = STRBUF_INIT; + + cl_must_pass(odb_source_for_each_object(&source->base, NULL, + add_one_object, &actual_oids, &opts)); + cl_assert_equal_u(oidset_size(&actual_oids), 0); + + for (int i = 0; i < 10; i++) { + struct object_id written_oid; + + strbuf_reset(&buf); + strbuf_addf(&buf, "%d", i); + + cl_must_pass(odb_source_write_object(&source->base, buf.buf, buf.len, + OBJ_BLOB, &written_oid, NULL, 0)); + cl_must_pass(oidset_insert(&expected_oids, &written_oid)); + } + + cl_must_pass(odb_source_for_each_object(&source->base, NULL, + add_one_object, &actual_oids, &opts)); + cl_assert_equal_b(oidset_equal(&expected_oids, &actual_oids), true); + + odb_source_free(&source->base); + oidset_clear(&expected_oids); + oidset_clear(&actual_oids); + strbuf_release(&buf); +} + +static int abort_after_two_objects(const struct object_id *oid UNUSED, + struct object_info *oi UNUSED, + void *payload) +{ + unsigned *counter = payload; + (*counter)++; + if (*counter == 2) + return 123; + return 0; +} + +void test_odb_inmemory__for_each_object_can_abort_iteration(void) +{ + struct odb_source_inmemory *source = odb_source_inmemory_new(odb); + struct odb_for_each_object_options opts = { 0 }; + struct object_id written_oid; + unsigned counter = 0; + + cl_must_pass(odb_source_write_object(&source->base, "1", 1, + OBJ_BLOB, &written_oid, NULL, 0)); + cl_must_pass(odb_source_write_object(&source->base, "2", 1, + OBJ_BLOB, &written_oid, NULL, 0)); + cl_must_pass(odb_source_write_object(&source->base, "3", 1, + OBJ_BLOB, &written_oid, NULL, 0)); + + cl_assert_equal_i(odb_source_for_each_object(&source->base, NULL, + abort_after_two_objects, + &counter, &opts), + 123); + cl_assert_equal_u(counter, 2); + + odb_source_free(&source->base); +} + +void test_odb_inmemory__count_objects(void) +{ + struct odb_source_inmemory *source = odb_source_inmemory_new(odb); + struct object_id written_oid; + unsigned long count; + + cl_must_pass(odb_source_count_objects(&source->base, 0, &count)); + cl_assert_equal_u(count, 0); + + cl_must_pass(odb_source_write_object(&source->base, "1", 1, + OBJ_BLOB, &written_oid, NULL, 0)); + cl_must_pass(odb_source_write_object(&source->base, "2", 1, + OBJ_BLOB, &written_oid, NULL, 0)); + cl_must_pass(odb_source_write_object(&source->base, "3", 1, + OBJ_BLOB, &written_oid, NULL, 0)); + + cl_must_pass(odb_source_count_objects(&source->base, 0, &count)); + cl_assert_equal_u(count, 3); + + odb_source_free(&source->base); +} + +void test_odb_inmemory__find_abbrev_len(void) +{ + struct odb_source_inmemory *source = odb_source_inmemory_new(odb); + struct object_id oid1, oid2; + unsigned abbrev_len; + + /* + * The two blobs we're about to write share the first 10 hex characters + * of their object IDs ("a09f43dc45"), so at least 11 characters are + * needed to tell them apart: + * + * "368317" -> a09f43dc4562d45115583f5094640ae237df55f7 + * "514796" -> a09f43dc45fef837235eb7e6b1a6ca5e169a3981 + * + * With only one blob written we expect a length of 4. + */ + cl_must_pass(odb_source_write_object(&source->base, "368317", strlen("368317"), + OBJ_BLOB, &oid1, NULL, 0)); + cl_must_pass(odb_source_find_abbrev_len(&source->base, &oid1, 4, + &abbrev_len)); + cl_assert_equal_u(abbrev_len, 4); + + /* + * With both objects present, the shared 10-character prefix means we + * need at least 11 characters to uniquely identify either object. + */ + cl_must_pass(odb_source_write_object(&source->base, "514796", strlen("514796"), + OBJ_BLOB, &oid2, NULL, 0)); + cl_must_pass(odb_source_find_abbrev_len(&source->base, &oid1, 4, + &abbrev_len)); + cl_assert_equal_u(abbrev_len, 11); + + odb_source_free(&source->base); +} + +void test_odb_inmemory__freshen_object(void) +{ + struct odb_source_inmemory *source = odb_source_inmemory_new(odb); + struct object_id written_oid; + struct object_id oid; + const char *end; + + cl_must_pass(parse_oid_hex_algop(RANDOM_OID, &oid, &end, repo.hash_algo)); + cl_assert_equal_i(odb_source_freshen_object(&source->base, &oid), 0); + + cl_must_pass(odb_source_write_object(&source->base, "foobar", + strlen("foobar"), OBJ_BLOB, + &written_oid, NULL, 0)); + cl_assert_equal_i(odb_source_freshen_object(&source->base, + &written_oid), 1); + + odb_source_free(&source->base); +} + +struct membuf_write_stream { + struct odb_write_stream base; + const char *buf; + size_t offset; + size_t size; +}; + +static ssize_t membuf_write_stream_read(struct odb_write_stream *stream, + unsigned char *buf, size_t len) +{ + struct membuf_write_stream *s = container_of(stream, struct membuf_write_stream, base); + size_t chunk_size = 2; + + if (chunk_size > len) + chunk_size = len; + if (chunk_size > s->size - s->offset) + chunk_size = s->size - s->offset; + + memcpy(buf, s->buf + s->offset, chunk_size); + + s->offset += chunk_size; + if (s->offset == s->size) + s->base.is_finished = 1; + + return chunk_size; +} + +void test_odb_inmemory__write_object_stream(void) +{ + struct odb_source_inmemory *source = odb_source_inmemory_new(odb); + const char data[] = "foobar"; + struct membuf_write_stream stream = { + .base.read = membuf_write_stream_read, + .buf = data, + .size = strlen(data), + }; + struct object_id written_oid; + + cl_must_pass(odb_source_write_object_stream(&source->base, &stream.base, + strlen(data), &written_oid)); + cl_assert_equal_s(oid_to_hex(&written_oid), FOOBAR_OID); + cl_assert_object_info(source, &written_oid, OBJ_BLOB, "foobar"); + + odb_source_free(&source->base); +}
diff --git a/t/unit-tests/u-oidtree.c b/t/unit-tests/u-oidtree.c index d4d05c7..f0d5ebb 100644 --- a/t/unit-tests/u-oidtree.c +++ b/t/unit-tests/u-oidtree.c
@@ -19,7 +19,7 @@ static int fill_tree_loc(struct oidtree *ot, const char *hexes[], size_t n) for (size_t i = 0; i < n; i++) { struct object_id oid; cl_parse_any_oid(hexes[i], &oid); - oidtree_insert(ot, &oid); + oidtree_insert(ot, &oid, NULL); } return 0; } @@ -38,9 +38,9 @@ struct expected_hex_iter { const char *query; }; -static int check_each_cb(const struct object_id *oid, void *data) +static int check_each_cb(const struct object_id *oid, void *node_data UNUSED, void *cb_data) { - struct expected_hex_iter *hex_iter = data; + struct expected_hex_iter *hex_iter = cb_data; struct object_id expected; cl_assert(hex_iter->i < hex_iter->expected_hexes.nr); @@ -105,3 +105,23 @@ void test_oidtree__each(void) check_each(&ot, "32100", "321", NULL); check_each(&ot, "32", "320", "321", NULL); } + +void test_oidtree__insert_overwrites_data(void) +{ + struct object_id oid; + struct oidtree ot; + int a, b; + + cl_parse_any_oid("1", &oid); + + oidtree_init(&ot); + + oidtree_insert(&ot, &oid, NULL); + cl_assert_equal_p(oidtree_get(&ot, &oid), NULL); + oidtree_insert(&ot, &oid, &a); + cl_assert_equal_p(oidtree_get(&ot, &oid), &a); + oidtree_insert(&ot, &oid, &b); + cl_assert_equal_p(oidtree_get(&ot, &oid), &b); + + oidtree_clear(&ot); +}
diff --git a/t/unit-tests/u-urlmatch-normalization.c b/t/unit-tests/u-urlmatch-normalization.c index 39f6e1b..3595d89 100644 --- a/t/unit-tests/u-urlmatch-normalization.c +++ b/t/unit-tests/u-urlmatch-normalization.c
@@ -245,3 +245,48 @@ void test_urlmatch_normalization__equivalents(void) compare_normalized_urls("https://@x.y/^/../abc", "httpS://@x.y:0443/abc", 1); compare_normalized_urls("https://@x.y/^/..", "httpS://@x.y:0443/", 1); } + +static void check_parsed_path(const char *url, const char *expected_path) +{ + struct url_info info; + char *parsed = url_parse(url, &info); + char *path; + + cl_assert(parsed != NULL); + path = xstrndup(parsed + info.path_off, info.path_len); + cl_assert_equal_s(path, expected_path); + free(path); + free(parsed); +} + +void test_urlmatch_normalization__parse_scp(void) +{ + check_parsed_path("host:path", "/path"); + check_parsed_path("user@host:path", "/path"); + check_parsed_path("host:~user/repo", "~user/repo"); + check_parsed_path("user@host:~user/repo", "~user/repo"); + check_parsed_path("[host]:src", "/src"); + check_parsed_path("[host:123]:src", "/src"); + check_parsed_path("[::1]:repo", "/repo"); + check_parsed_path("user@[::1]:repo", "/repo"); +} + +void test_urlmatch_normalization__parse_url_form(void) +{ + check_parsed_path("ssh://host/repo", "/repo"); + check_parsed_path("ssh://host/~user/repo", "~user/repo"); + check_parsed_path("git://host:9418/repo", "/repo"); + check_parsed_path("git://host/~user/repo", "~user/repo"); + check_parsed_path("ssh://[::1]:1234/repo", "/repo"); + check_parsed_path("http://[2001:db8::1]/repo", "/repo"); +} + +void test_urlmatch_normalization__parse_strips_query_and_fragment(void) +{ + check_parsed_path("ssh://host/~user/repo?q", "~user/repo"); + check_parsed_path("ssh://host/~user/repo#frag", "~user/repo"); + check_parsed_path("git://host/~user/repo?q", "~user/repo"); + check_parsed_path("user@host:~user/repo?q", "~user/repo"); + check_parsed_path("https://host/repo?q", "/repo"); + check_parsed_path("https://host/repo#frag", "/repo"); +}
diff --git a/tempfile.c b/tempfile.c index 82dfa3d..f0fdf58 100644 --- a/tempfile.c +++ b/tempfile.c
@@ -373,3 +373,15 @@ int delete_tempfile(struct tempfile **tempfile_p) return err ? -1 : 0; } + +void reassign_tempfile_ownership(pid_t from, pid_t to) +{ + volatile struct volatile_list_head *pos; + + list_for_each(pos, &tempfile_list) { + struct tempfile *p = list_entry(pos, struct tempfile, list); + + if (is_tempfile_active(p) && p->owner == from) + p->owner = to; + } +}
diff --git a/tempfile.h b/tempfile.h index 2d2ae5b..2227a09 100644 --- a/tempfile.h +++ b/tempfile.h
@@ -282,4 +282,15 @@ int delete_tempfile(struct tempfile **tempfile_p); */ int rename_tempfile(struct tempfile **tempfile_p, const char *path); +/* + * Reassign ownership of all active tempfiles whose `owner` field matches + * `from` to `to`. + * + * This is intended for use by `daemonize()`; after `fork(2)`-ing, the parent + * transfers ownership to the daemonized child so that its atexit handler does + * not unlink tempfiles that should outlive it, and the child claims the + * inherited tempfiles so that they are cleaned up when the daemon exits. + */ +void reassign_tempfile_ownership(pid_t from, pid_t to); + #endif /* TEMPFILE_H */
diff --git a/tools/coccinelle/strbuf.cocci b/tools/coccinelle/strbuf.cocci index f586128..667903d 100644 --- a/tools/coccinelle/strbuf.cocci +++ b/tools/coccinelle/strbuf.cocci
@@ -78,3 +78,9 @@ @@ - SB.buf ? SB.buf : "" + SB.buf + +@@ +expression SB, OID; +@@ +- strbuf_addstr(SB, oid_to_hex(OID)) ++ strbuf_add_oid_hex(SB, OID)
diff --git a/tools/generate-configlist.sh b/tools/generate-configlist.sh index e28054f..d1d2ba4 100755 --- a/tools/generate-configlist.sh +++ b/tools/generate-configlist.sh
@@ -42,9 +42,12 @@ then QUOTED_OUTPUT="$(printf '%s\n' "$OUTPUT" | sed 's,[&/\],\\&,g')" { + printf '%s' "$QUOTED_OUTPUT: " printf '%s\n' "$SOURCE_DIR"/Documentation/*config.adoc \ "$SOURCE_DIR"/Documentation/config/*.adoc | - sed -e 's/[# ]/\\&/g' -e "s/^/$QUOTED_OUTPUT: /" + sed -e 's/[# ]/\\&/g' | + tr '\n' ' ' + printf '\n' printf '%s:\n' "$SOURCE_DIR"/Documentation/*config.adoc \ "$SOURCE_DIR"/Documentation/config/*.adoc | sed -e 's/[# ]/\\&/g'
diff --git a/trailer.c b/trailer.c index 470f86a..6d8ec7f 100644 --- a/trailer.c +++ b/trailer.c
@@ -988,10 +988,9 @@ static int ends_with_blank_line(const char *buf, size_t len) static void unfold_value(struct strbuf *val) { - struct strbuf out = STRBUF_INIT; size_t i; + size_t pos = 0; - strbuf_grow(&out, val->len); i = 0; while (i < val->len) { char c = val->buf[i++]; @@ -999,18 +998,14 @@ static void unfold_value(struct strbuf *val) /* Collapse continuation down to a single space. */ while (i < val->len && isspace(val->buf[i])) i++; - strbuf_addch(&out, ' '); - } else { - strbuf_addch(&out, c); + c = ' '; } + val->buf[pos++] = c; } + strbuf_setlen(val, pos); /* Empty lines may have left us with whitespace cruft at the edges */ - strbuf_trim(&out); - - /* output goes back to val as if we modified it in-place */ - strbuf_swap(&out, val); - strbuf_release(&out); + strbuf_trim(val); } static struct trailer_block *trailer_block_new(void)
diff --git a/transport-helper.c b/transport-helper.c index 4e5d1d9..80f90eb 100644 --- a/transport-helper.c +++ b/transport-helper.c
@@ -154,8 +154,6 @@ static struct child_process *get_helper(struct transport *transport) helper->trace2_child_class = helper->args.v[0]; /* "remote-<name>" */ - helper->clean_on_exit = 1; - helper->wait_after_clean = 1; code = start_command(helper); if (code < 0 && errno == ENOENT) die(_("unable to find remote helper for '%s'"), data->name); @@ -622,8 +620,22 @@ static int run_connect(struct transport *transport, struct strbuf *cmdbuf) return ret; } +static const char *connect_service_cmd(enum git_connect_service service) +{ + switch (service) { + case GIT_CONNECT_UPLOAD_PACK: + return "git-upload-pack"; + case GIT_CONNECT_RECEIVE_PACK: + return "git-receive-pack"; + case GIT_CONNECT_UPLOAD_ARCHIVE: + return "git-upload-archive"; + } + BUG("unknown git_connect_service: %d", service); +} + static int process_connect_service(struct transport *transport, - const char *name, const char *exec) + enum git_connect_service service, + const char *exec) { struct helper_data *data = transport->data; struct strbuf cmdbuf = STRBUF_INIT; @@ -633,7 +645,7 @@ static int process_connect_service(struct transport *transport, * Handle --upload-pack and friends. This is fire and forget... * just warn if it fails. */ - if (strcmp(name, exec)) { + if (strcmp(connect_service_cmd(service), exec)) { int r = set_helper_option(transport, "servpath", exec); if (r > 0) warning(_("setting remote service path not supported by protocol")); @@ -642,13 +654,15 @@ static int process_connect_service(struct transport *transport, } if (data->connect) { - strbuf_addf(&cmdbuf, "connect %s\n", name); + strbuf_addf(&cmdbuf, "connect %s\n", + connect_service_cmd(service)); ret = run_connect(transport, &cmdbuf); } else if (data->stateless_connect && (get_protocol_version_config() == protocol_v2) && - (!strcmp("git-upload-pack", name) || - !strcmp("git-upload-archive", name))) { - strbuf_addf(&cmdbuf, "stateless-connect %s\n", name); + (service == GIT_CONNECT_UPLOAD_PACK || + service == GIT_CONNECT_UPLOAD_ARCHIVE)) { + strbuf_addf(&cmdbuf, "stateless-connect %s\n", + connect_service_cmd(service)); ret = run_connect(transport, &cmdbuf); if (ret) transport->stateless_rpc = 1; @@ -662,32 +676,33 @@ static int process_connect(struct transport *transport, int for_push) { struct helper_data *data = transport->data; - const char *name; + enum git_connect_service service; const char *exec; int ret; - name = for_push ? "git-receive-pack" : "git-upload-pack"; + service = for_push ? GIT_CONNECT_RECEIVE_PACK : GIT_CONNECT_UPLOAD_PACK; if (for_push) exec = data->transport_options.receivepack; else exec = data->transport_options.uploadpack; - ret = process_connect_service(transport, name, exec); + ret = process_connect_service(transport, service, exec); if (ret) do_take_over(transport); return ret; } -static int connect_helper(struct transport *transport, const char *name, - const char *exec, int fd[2]) +static int connect_helper(struct transport *transport, enum git_connect_service service, + const char *exec, int fd[2]) { struct helper_data *data = transport->data; /* Get_helper so connect is inited. */ get_helper(transport); - if (!process_connect_service(transport, name, exec)) - die(_("can't connect to subservice %s"), name); + if (!process_connect_service(transport, service, exec)) + die(_("can't connect to subservice %s"), + connect_service_cmd(service)); fd[0] = data->helper->out; fd[1] = data->helper->in; @@ -756,8 +771,9 @@ static int fetch_refs(struct transport *transport, set_helper_option(transport, "filter", spec); } - if (data->transport_options.negotiation_tips) - warning("Ignoring --negotiation-tip because the protocol does not support it."); + if (data->transport_options.negotiation_restrict_tips) + warning(_("ignoring %s because the protocol does not support it."), + "--negotiation-restrict"); if (data->fetch) return fetch_with_fetch(transport, nr_heads, to_fetch); @@ -1053,7 +1069,7 @@ static int push_refs_with_push(struct transport *transport, if (ref->peer_ref) strbuf_addstr(&buf, ref->peer_ref->name); else - strbuf_addstr(&buf, oid_to_hex(&ref->new_oid)); + strbuf_add_oid_hex(&buf, &ref->new_oid); } strbuf_addch(&buf, ':'); strbuf_addstr(&buf, ref->name); @@ -1345,24 +1361,18 @@ int transport_helper_init(struct transport *transport, const char *name) /* This should be enough to hold debugging message. */ #define PBUFFERSIZE 8192 +static int transfer_debug_enabled = -1; + /* Print bidirectional transfer loop debug message. */ __attribute__((format (printf, 1, 2))) static void transfer_debug(const char *fmt, ...) { - /* - * NEEDSWORK: This function is sometimes used from multiple threads, and - * we end up using debug_enabled racily. That "should not matter" since - * we always write the same value, but it's still wrong. This function - * is listed in .tsan-suppressions for the time being. - */ - va_list args; char msgbuf[PBUFFERSIZE]; - static int debug_enabled = -1; - if (debug_enabled < 0) - debug_enabled = getenv("GIT_TRANSLOOP_DEBUG") ? 1 : 0; - if (!debug_enabled) + if (transfer_debug_enabled < 0) + BUG("somebody forgot to check GIT_TRANSLOOP_DEBUG!"); + if (!transfer_debug_enabled) return; va_start(args, fmt); @@ -1632,6 +1642,9 @@ int bidirectional_transfer_loop(int input, int output) { struct bidirectional_transfer_state state; + if (transfer_debug_enabled < 0) + transfer_debug_enabled = getenv("GIT_TRANSLOOP_DEBUG") ? 1 : 0; + /* Fill the state fields. */ state.ptg.src = input; state.ptg.dest = 1;
diff --git a/transport-internal.h b/transport-internal.h index 90ea749..051f3ab 100644 --- a/transport-internal.h +++ b/transport-internal.h
@@ -1,6 +1,8 @@ #ifndef TRANSPORT_INTERNAL_H #define TRANSPORT_INTERNAL_H +#include "connect.h" + struct ref; struct transport; struct strvec; @@ -58,7 +60,8 @@ struct transport_vtable { * process involved generating new commits. **/ int (*push_refs)(struct transport *transport, struct ref *refs, int flags); - int (*connect)(struct transport *connection, const char *name, + int (*connect)(struct transport *connection, + enum git_connect_service service, const char *executable, int fd[2]); /** get_refs_list(), fetch(), and push_refs() can keep
diff --git a/transport.c b/transport.c index e53936d..0f5ec30 100644 --- a/transport.c +++ b/transport.c
@@ -29,6 +29,7 @@ #include "object-name.h" #include "color.h" #include "bundle-uri.h" +#include "sideband.h" static enum git_colorbool transport_use_color = GIT_COLOR_UNKNOWN; static char transport_colors[][COLOR_MAXLEN] = { @@ -308,8 +309,8 @@ static int connect_setup(struct transport *transport, int for_push) data->conn = git_connect(data->fd, transport->url, for_push ? - "git-receive-pack" : - "git-upload-pack", + GIT_CONNECT_RECEIVE_PACK : + GIT_CONNECT_UPLOAD_PACK, for_push ? data->options.receivepack : data->options.uploadpack, @@ -463,7 +464,8 @@ static int fetch_refs_via_pack(struct transport *transport, args.refetch = data->options.refetch; args.stateless_rpc = transport->stateless_rpc; args.server_options = transport->server_options; - args.negotiation_tips = data->options.negotiation_tips; + args.negotiation_restrict_tips = data->options.negotiation_restrict_tips; + args.negotiation_include_tips = data->options.negotiation_include_tips; args.reject_shallow_remote = transport->smart_options->reject_shallow; if (!data->finished_handshake) { @@ -491,11 +493,12 @@ static int fetch_refs_via_pack(struct transport *transport, warning(_("server does not support wait-for-done")); ret = -1; } else { - negotiate_using_fetch(data->options.negotiation_tips, + negotiate_using_fetch(data->options.negotiation_restrict_tips, transport->server_options, transport->stateless_rpc, data->fd, - data->options.acked_commits); + data->options.acked_commits, + data->options.negotiation_include_tips); ret = 0; } goto cleanup; @@ -919,6 +922,8 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re args.atomic = !!(flags & TRANSPORT_PUSH_ATOMIC); args.push_options = transport->push_options; args.url = transport->url; + args.negotiation_include = &transport->remote->negotiation_include; + args.negotiation_restrict = &transport->remote->negotiation_restrict; if (flags & TRANSPORT_PUSH_CERT_ALWAYS) args.push_cert = SEND_PACK_PUSH_CERT_ALWAYS; @@ -956,12 +961,13 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re return ret; } -static int connect_git(struct transport *transport, const char *name, +static int connect_git(struct transport *transport, + enum git_connect_service service, const char *executable, int fd[2]) { struct git_transport_data *data = transport->data; data->conn = git_connect(data->fd, transport->url, - name, executable, 0); + service, executable, 0); fd[0] = data->fd[0]; fd[1] = data->fd[1]; return 0; @@ -979,9 +985,13 @@ static int disconnect_git(struct transport *transport) finish_connect(data->conn); } - if (data->options.negotiation_tips) { - oid_array_clear(data->options.negotiation_tips); - free(data->options.negotiation_tips); + if (data->options.negotiation_restrict_tips) { + oid_array_clear(data->options.negotiation_restrict_tips); + free(data->options.negotiation_restrict_tips); + } + if (data->options.negotiation_include_tips) { + oid_array_clear(data->options.negotiation_include_tips); + free(data->options.negotiation_include_tips); } list_objects_filter_release(&data->options.filter_options); oid_array_clear(&data->extra_have); @@ -1246,6 +1256,8 @@ struct transport *transport_get(struct remote *remote, const char *url) ret->hash_algo = &hash_algos[GIT_HASH_SHA1_LEGACY]; + sideband_apply_url_config(ret->url); + return ret; } @@ -1391,8 +1403,10 @@ static int run_pre_push_hook(struct transport *transport, opt.feed_pipe_cb_data_free = pre_push_hook_data_free; /* - * pre-push hooks expect stdout & stderr to be separate, so don't merge - * them to keep backwards compatibility with existing hooks. + * pre-push hooks keep stdout and stderr separate by default for + * backwards compatibility. When the user opts into parallel execution + * via hook.jobs > 1 or -j, get_hook_jobs() will set stdout_to_stderr=1 + * automatically so run-command can de-interleave the outputs. */ opt.stdout_to_stderr = 0; @@ -1651,11 +1665,12 @@ void transport_unlock_pack(struct transport *transport, unsigned int flags) string_list_clear(&transport->pack_lockfiles, 0); } -int transport_connect(struct transport *transport, const char *name, +int transport_connect(struct transport *transport, + enum git_connect_service service, const char *exec, int fd[2]) { if (transport->vtable->connect) - return transport->vtable->connect(transport, name, exec, fd); + return transport->vtable->connect(transport, service, exec, fd); else die(_("operation not supported by protocol")); }
diff --git a/transport.h b/transport.h index 892f194..7e5867c 100644 --- a/transport.h +++ b/transport.h
@@ -5,6 +5,7 @@ #include "remote.h" #include "list-objects-filter-options.h" #include "string-list.h" +#include "connect.h" struct git_transport_options { unsigned thin : 1; @@ -40,13 +41,14 @@ struct git_transport_options { /* * This is only used during fetch. See the documentation of - * negotiation_tips in struct fetch_pack_args. + * these member names in struct fetch_pack_args. * - * This field is only supported by transports that support connect or + * These fields are only supported by transports that support connect or * stateless_connect. Set this field directly instead of using * transport_set_option(). */ - struct oid_array *negotiation_tips; + struct oid_array *negotiation_restrict_tips; + struct oid_array *negotiation_include_tips; /* * If allocated, whenever transport_fetch_refs() is called, add known @@ -324,7 +326,8 @@ char *transport_anonymize_url(const char *url); void transport_take_over(struct transport *transport, struct child_process *child); -int transport_connect(struct transport *transport, const char *name, +int transport_connect(struct transport *transport, + enum git_connect_service service, const char *exec, int fd[2]); /* Transport methods defined outside transport.c */
diff --git a/tree-walk.h b/tree-walk.h index 29a5532..9646c47 100644 --- a/tree-walk.h +++ b/tree-walk.h
@@ -177,7 +177,7 @@ struct traverse_info { /** * Walk trees starting with "tree_oid" to find the entry for "name", and - * return the the object name and the mode of the found entry via the + * return the object name and the mode of the found entry via the * "oid" and "mode" parameters. Return 0 if the entry is found, and -1 * otherwise. */
diff --git a/unpack-trees.c b/unpack-trees.c index 998a1e6..b42020f 100644 --- a/unpack-trees.c +++ b/unpack-trees.c
@@ -1780,14 +1780,14 @@ static int clear_ce_flags(struct index_state *istate, xsnprintf(label, sizeof(label), "clear_ce_flags(0x%08lx,0x%08lx)", (unsigned long)select_mask, (unsigned long)clear_mask); - trace2_region_enter("unpack_trees", label, the_repository); + trace2_region_enter("unpack_trees", label, istate->repo); rval = clear_ce_flags_1(istate, istate->cache, istate->cache_nr, &prefix, select_mask, clear_mask, pl, 0, 0); - trace2_region_leave("unpack_trees", label, the_repository); + trace2_region_leave("unpack_trees", label, istate->repo); stop_progress(&istate->progress); return rval; @@ -1882,7 +1882,7 @@ static int verify_absent(const struct cache_entry *, */ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o) { - struct repository *repo = the_repository; + struct repository *repo = o->src_index->repo; int i, ret; static struct cache_entry *dfc; struct pattern_list pl; @@ -1903,7 +1903,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options BUG("o->df_conflict_entry is an output only field"); trace_performance_enter(); - trace2_region_enter("unpack_trees", "unpack_trees", the_repository); + trace2_region_enter("unpack_trees", "unpack_trees", repo); prepare_repo_settings(repo); if (repo->settings.command_requires_full_index) { @@ -2007,9 +2007,9 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options } trace_performance_enter(); - trace2_region_enter("unpack_trees", "traverse_trees", the_repository); + trace2_region_enter("unpack_trees", "traverse_trees", repo); ret = traverse_trees(o->src_index, len, t, &info); - trace2_region_leave("unpack_trees", "traverse_trees", the_repository); + trace2_region_leave("unpack_trees", "traverse_trees", repo); trace_performance_leave("traverse_trees"); if (ret < 0) goto return_failed; @@ -2106,7 +2106,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options dir_clear(o->internal.dir); o->internal.dir = NULL; } - trace2_region_leave("unpack_trees", "unpack_trees", the_repository); + trace2_region_leave("unpack_trees", "unpack_trees", repo); trace_performance_leave("unpack_trees"); return ret;
diff --git a/upload-pack.c b/upload-pack.c index 9f6d6fe..a52856d 100644 --- a/upload-pack.c +++ b/upload-pack.c
@@ -886,7 +886,7 @@ static void deepen(struct upload_pack_data *data, int depth) data->deepen_relative, depth, SHALLOW, NOT_SHALLOW); send_shallow(data, result); - free_commit_list(result); + commit_list_free(result); } send_unshallow(data); @@ -900,7 +900,7 @@ static void deepen_by_rev_list(struct upload_pack_data *data, disable_commit_graph(the_repository); result = get_shallow_commits_by_rev_list(argv, SHALLOW, NOT_SHALLOW); send_shallow(data, result); - free_commit_list(result); + commit_list_free(result); send_unshallow(data); } @@ -1336,6 +1336,7 @@ static int upload_pack_config(const char *var, const char *value, void *cb_data) { struct upload_pack_data *data = cb_data; + struct repo_config_values *cfg = repo_config_values(the_repository); if (!strcmp("uploadpack.allowtipsha1inwant", var)) { if (git_config_bool(var, value)) @@ -1366,7 +1367,7 @@ static int upload_pack_config(const char *var, const char *value, if (value) data->allow_packfile_uris = 1; } else if (!strcmp("core.precomposeunicode", var)) { - precomposed_unicode = git_config_bool(var, value); + cfg->precomposed_unicode = git_config_bool(var, value); } else if (!strcmp("transfer.advertisesid", var)) { data->advertise_sid = git_config_bool(var, value); }
diff --git a/url.c b/url.c index 3ca5987..a598182 100644 --- a/url.c +++ b/url.c
@@ -132,3 +132,26 @@ void str_end_url_with_slash(const char *url, char **dest) free(*dest); *dest = strbuf_detach(&buf, NULL); } + +int url_is_local_not_ssh(const char *url) +{ + const char *colon = strchr(url, ':'); + const char *slash = strchr(url, '/'); + return !colon || (slash && slash < colon) || + (has_dos_drive_prefix(url) && is_valid_path(url)); +} + +enum url_scheme url_get_scheme(const char *name) +{ + if (!strcmp(name, "ssh")) + return URL_SCHEME_SSH; + if (!strcmp(name, "git")) + return URL_SCHEME_GIT; + if (!strcmp(name, "git+ssh")) /* deprecated - do not use */ + return URL_SCHEME_SSH; + if (!strcmp(name, "ssh+git")) /* deprecated - do not use */ + return URL_SCHEME_SSH; + if (!strcmp(name, "file")) + return URL_SCHEME_FILE; + return URL_SCHEME_UNKNOWN; +}
diff --git a/url.h b/url.h index cd9140e..7289523 100644 --- a/url.h +++ b/url.h
@@ -21,6 +21,22 @@ char *url_decode_parameter_value(const char **query); void end_url_with_slash(struct strbuf *buf, const char *url); void str_end_url_with_slash(const char *url, char **dest); +int url_is_local_not_ssh(const char *url); + +enum url_scheme { + URL_SCHEME_UNKNOWN = 0, + URL_SCHEME_LOCAL, + URL_SCHEME_FILE, + URL_SCHEME_SSH, + URL_SCHEME_GIT, +}; + +/* + * Identify the URL scheme by name. Returns URL_SCHEME_UNKNOWN + * if the name does not match any scheme that Git knows about. + */ +enum url_scheme url_get_scheme(const char *name); + /* * The set of unreserved characters as per STD66 (RFC3986) is * '[A-Za-z0-9-._~]'. These characters are safe to appear in URI
diff --git a/urlmatch.c b/urlmatch.c index eea8300..bf8cce6 100644 --- a/urlmatch.c +++ b/urlmatch.c
@@ -5,6 +5,7 @@ #include "hex-ll.h" #include "strbuf.h" #include "urlmatch.h" +#include "url.h" #define URL_ALPHA "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" #define URL_DIGIT "0123456789" @@ -440,6 +441,132 @@ char *url_normalize(const char *url, struct url_info *out_info) return url_normalize_1(url, out_info, 0); } +char *url_parse(const char *url_orig, struct url_info *out_info) +{ + struct strbuf url; + char *host, *separator; + char *detached, *normalized; + char *url_decoded; + enum url_scheme scheme = URL_SCHEME_LOCAL; + struct url_info local_info; + struct url_info *info = out_info ? out_info : &local_info; + bool scp_syntax = false; + + if (is_url(url_orig)) + url_decoded = url_decode(url_orig); + else + url_decoded = xstrdup(url_orig); + + strbuf_init(&url, strlen(url_decoded) + sizeof("ssh://")); + strbuf_addstr(&url, url_decoded); + free(url_decoded); + + host = strstr(url.buf, "://"); + if (host) { + /* + * Temporarily NUL-terminate the scheme name + * so we can pass it to url_get_scheme(), + * then restore the ':' so the buffer + * is intact for url_normalize() below. + */ + char saved = *host; + *host = '\0'; + scheme = url_get_scheme(url.buf); + *host = saved; + host += 3; + } else { + if (!url_is_local_not_ssh(url.buf)) { + scp_syntax = true; + scheme = URL_SCHEME_SSH; + strbuf_insertstr(&url, 0, "ssh://"); + host = url.buf + strlen("ssh://"); + } + } + + /* + * Path starts after ':' in scp style SSH URLs. + * + * The host portion can begin with an optional "user@", + * and the host itself can be wrapped in '[' ']' brackets. + * The bracket form is git's legacy way of supporting: + * + * - IPv6 literals: [::1]:repo + * - host:port pairs in the short form: [myhost:123]:src + * - Plain hostnames that happen to need bracketing: [host]:path + * + * Treat '[' followed by 0 or 1 inner colons as the host:port + * or plain hostname form and strip the brackets so url_normalize + * sees host[:port] natively. Two or more inner colons mark an + * IPv6 literal: keep the brackets for url_normalize to recognize. + * + * The scp path separator is the ':' that follows the host part, + * and we must skip over user@ and any '[...]' before searching. + */ + if (scp_syntax) { + char *user_at; + char *host_start; + char *bracket_end; + + user_at = strchr(host, '@'); + host_start = user_at ? user_at + 1 : host; + + if (*host_start == '[') { + char *p; + int inner_colons; + + bracket_end = strchr(host_start, ']'); + inner_colons = 0; + for (p = host_start + 1; bracket_end && p < bracket_end; p++) + if (*p == ':') + inner_colons++; + + if (bracket_end && inner_colons <= 1) { + size_t close_off = bracket_end - url.buf; + size_t open_off = host_start - url.buf; + strbuf_remove(&url, close_off, 1); + strbuf_remove(&url, open_off, 1); + separator = url.buf + close_off - 1; + } else if (bracket_end) { + separator = strchr(bracket_end + 1, ':'); + } else { + separator = strchr(host_start, ':'); + } + } else { + separator = strchr(host_start, ':'); + } + + if (separator) { + if (separator[1] == '/') + strbuf_remove(&url, separator - url.buf, 1); + else + *separator = '/'; + } + } + + detached = strbuf_detach(&url, NULL); + normalized = url_normalize(detached, info); + free(detached); + + if (!normalized) + return NULL; + + /* + * Point path to ~ for URLs like this: + * + * ssh://host.xz/~user/repo + * git://host.xz/~user/repo + * host.xz:~user/repo + */ + if (scheme == URL_SCHEME_GIT || scheme == URL_SCHEME_SSH) { + if (normalized[info->path_off + 1] == '~') { + info->path_off++; + info->path_len--; + } + } + + return normalized; +} + static size_t url_match_prefix(const char *url, const char *url_prefix, size_t url_prefix_len)
diff --git a/urlmatch.h b/urlmatch.h index 5ba85ce..6b3ce42 100644 --- a/urlmatch.h +++ b/urlmatch.h
@@ -35,6 +35,7 @@ struct url_info { }; char *url_normalize(const char *, struct url_info *); +char *url_parse(const char *, struct url_info *); struct urlmatch_item { size_t hostmatch_len;
diff --git a/userdiff.c b/userdiff.c index fe710a6..b5412e6 100644 --- a/userdiff.c +++ b/userdiff.c
@@ -344,14 +344,24 @@ PATTERNS("rust", "|[0-9][0-9_a-fA-Fiosuxz]*(\\.([0-9]*[eE][+-]?)?[0-9_fF]*)?" "|[-+*\\/<>%&^|=!:]=|<<=?|>>=?|&&|\\|\\||->|=>|\\.{2}=|\\.{3}|::"), PATTERNS("scheme", - "^[\t ]*(\\(((define|def(struct|syntax|class|method|rules|record|proto|alias)?)[-*/ \t]|(library|module|struct|class)[*+ \t]).*)$", /* - * R7RS valid identifiers include any sequence enclosed - * within vertical lines having no backslashes + * An unindented opening parenthesis identifies a top-level + * expression in all Lisp dialects. */ - "\\|([^\\\\]*)\\|" - /* All other words should be delimited by spaces or parentheses */ - "|([^][)(}{[ \t])+"), + "^(\\(.*)$\n" + /* For Scheme: a possibly indented left paren followed by a keyword. */ + "^[\t ]*(\\(((define|def(struct|syntax|class|method|rules|record|proto|alias)?)[-*/ \t]|(library|module|struct|class)[*+ \t]).*)$\n" + /* + * For all Lisp dialects: a slightly indented line starting with "(def". + */ + "^ ?(\\([Dd][Ee][Ff].*)$", + /* + * The union of R7RS and Common Lisp symbol syntax: allows arbitrary + * strings between vertical bars, including any escaped characters. + */ + "\\|([^|\\\\]|\\\\.)*\\|" + /* All other words should be delimited by spaces or parentheses. */ + "|([^][)(}{ \t])+"), PATTERNS("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$", "\\\\[a-zA-Z@]+|\\\\.|([a-zA-Z0-9]|[^\x01-\x7f])+"), { .name = "default", .binary = -1 },
diff --git a/worktree.c b/worktree.c index d874e23..97eddc3 100644 --- a/worktree.c +++ b/worktree.c
@@ -66,7 +66,7 @@ static int is_current_worktree(struct worktree *wt) return is_current; } -struct worktree *get_worktree_from_repository(struct repository *repo) +struct worktree *get_current_worktree(struct repository *repo) { struct worktree *wt = xcalloc(1, sizeof(*wt)); char *gitdir = absolute_pathdup(repo->gitdir); @@ -1104,7 +1104,7 @@ void write_worktree_linking_files(const char *dotgit, const char *gitdir, strbuf_realpath(&repo, repo.buf, 1); if (use_relative_paths && !the_repository->repository_format_relative_worktrees) { - if (upgrade_repository_format(1) < 0) + if (upgrade_repository_format(the_repository, 1) < 0) die(_("unable to upgrade repository format to support relative worktrees")); if (repo_config_set_gently(the_repository, "extensions.relativeWorktrees", "true")) die(_("unable to set extensions.relativeWorktrees setting"));
diff --git a/worktree.h b/worktree.h index d19ec29..1075409 100644 --- a/worktree.h +++ b/worktree.h
@@ -42,7 +42,7 @@ struct worktree **get_worktrees_without_reading_head(void); * Construct a struct worktree corresponding to repo->gitdir and * repo->worktree. */ -struct worktree *get_worktree_from_repository(struct repository *repo); +struct worktree *get_current_worktree(struct repository *repo); /* * Returns 1 if linked worktrees exist, 0 otherwise.
diff --git a/wt-status.c b/wt-status.c index 479ccc3..b173723 100644 --- a/wt-status.c +++ b/wt-status.c
@@ -1206,7 +1206,7 @@ static void wt_longstatus_print_verbose(struct wt_status *s) status_printf_ln(s, c, "--------------------------------------------------"); status_printf_ln(s, c, _("Changes not staged for commit:")); - setup_work_tree(); + setup_work_tree(the_repository); rev.diffopt.a_prefix = "i/"; rev.diffopt.b_prefix = "w/"; run_diff_files(&rev, 0); @@ -1827,7 +1827,7 @@ void wt_status_get_state(struct repository *r, struct stat st; struct object_id oid; enum replay_action action; - struct worktree *wt = get_worktree_from_repository(r); + struct worktree *wt = get_current_worktree(r); if (!stat(git_path_merge_head(r), &st)) { wt_status_check_rebase(wt, state);
diff --git a/xdiff-interface.c b/xdiff-interface.c index f043330..5ee2b96 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c
@@ -325,6 +325,18 @@ int parse_conflict_style_name(const char *value) return -1; } +const char *conflict_style_name(int style) +{ + switch (style) { + case XDL_MERGE_DIFF3: + return "diff3"; + case XDL_MERGE_ZEALOUS_DIFF3: + return "zdiff3"; + default: + return "merge"; + } +} + int git_xmerge_style = -1; int git_xmerge_config(const char *var, const char *value,
diff --git a/xdiff-interface.h b/xdiff-interface.h index fbc4cee..ce54e1c 100644 --- a/xdiff-interface.h +++ b/xdiff-interface.h
@@ -55,6 +55,7 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags); void xdiff_clear_find_func(xdemitconf_t *xecfg); struct config_context; int parse_conflict_style_name(const char *value); +const char *conflict_style_name(int style); int git_xmerge_config(const char *var, const char *value, const struct config_context *ctx, void *cb); extern int git_xmerge_style;
diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c index 5455b46..c5a892f 100644 --- a/xdiff/xdiffi.c +++ b/xdiff/xdiffi.c
@@ -348,7 +348,7 @@ int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, kvdf += xe->xdf2.nreff + 1; kvdb += xe->xdf2.nreff + 1; - xenv.mxcost = xdl_bogosqrt(ndiags); + xenv.mxcost = (long)xdl_bogosqrt((uint64_t)ndiags); if (xenv.mxcost < XDL_MAX_COST_MIN) xenv.mxcost = XDL_MAX_COST_MIN; xenv.snake_cnt = XDL_SNAKE_CNT;
diff --git a/xdiff/xemit.c b/xdiff/xemit.c index 04f7e91..7cd9cf0 100644 --- a/xdiff/xemit.c +++ b/xdiff/xemit.c
@@ -46,12 +46,20 @@ static long saturating_add(long a, long b) xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg) { xdchange_t *xch, *xchp, *lxch; - long max_common = saturating_add(saturating_add(xecfg->ctxlen, - xecfg->ctxlen), - xecfg->interhunkctxlen); - long max_ignorable = xecfg->ctxlen; + long max_common; + long max_ignorable; long ignored = 0; /* number of ignored blank lines */ + if (xecfg->ctxlen < 0) + BUG("negative context length: %ld", xecfg->ctxlen); + if (xecfg->interhunkctxlen < 0) + BUG("negative inter-hunk context length: %ld", xecfg->interhunkctxlen); + + max_common = saturating_add(saturating_add(xecfg->ctxlen, + xecfg->ctxlen), + xecfg->interhunkctxlen); + max_ignorable = xecfg->ctxlen; + /* remove ignorable changes that are too far before other changes */ for (xchp = *xscr; xchp && xchp->ignore; xchp = xchp->next) { xch = xchp->next;
diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c index 29dad98..659ad4e 100644 --- a/xdiff/xmerge.c +++ b/xdiff/xmerge.c
@@ -199,9 +199,9 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, int size, int i, int style, xdmerge_t *m, char *dest, int marker_size) { - int marker1_size = (name1 ? strlen(name1) + 1 : 0); - int marker2_size = (name2 ? strlen(name2) + 1 : 0); - int marker3_size = (name3 ? strlen(name3) + 1 : 0); + int marker1_size = (name1 && *name1 ? strlen(name1) + 1 : 0); + int marker2_size = (name2 && *name2 ? strlen(name2) + 1 : 0); + int marker3_size = (name3 && *name3 ? strlen(name3) + 1 : 0); int needs_cr = is_cr_needed(xe1, xe2, m); if (marker_size <= 0)
diff --git a/xdiff/xprepare.c b/xdiff/xprepare.c index cd4fc40..11bada2 100644 --- a/xdiff/xprepare.c +++ b/xdiff/xprepare.c
@@ -171,12 +171,6 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_ if (!XDL_CALLOC_ARRAY(xdf->changed, xdf->nrec + 2)) goto abort; - if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) && - (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF)) { - if (!XDL_ALLOC_ARRAY(xdf->reference_index, xdf->nrec + 1)) - goto abort; - } - xdf->changed += 1; xdf->nreff = 0; xdf->dstart = 0; @@ -197,8 +191,9 @@ void xdl_free_env(xdfenv_t *xe) { } -static bool xdl_clean_mmatch(uint8_t const *action, long i, long s, long e) { - long r, rdis0, rpdis0, rdis1, rpdis1; +static bool xdl_clean_mmatch(uint8_t const *action, ptrdiff_t i, ptrdiff_t len) { + ptrdiff_t r, rdis0, rpdis0, rdis1, rpdis1; + ptrdiff_t s = 0, e = len - 1; /* * Limits the window that is examined during the similar-lines @@ -268,22 +263,24 @@ static bool xdl_clean_mmatch(uint8_t const *action, long i, long s, long e) { * might be potentially discarded if they appear in a run of discardable. */ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) { - long i, nm, mlim; - xrecord_t *recs; + ptrdiff_t i, nm, mlim1, mlim2; xdlclass_t *rcrec; uint8_t *action1 = NULL, *action2 = NULL; bool need_min = !!(cf->flags & XDF_NEED_MINIMAL); int ret = 0; + ptrdiff_t off = xdf1->dstart; + ptrdiff_t len1 = xdf1->dend - off + 1; + ptrdiff_t len2 = xdf2->dend - off + 1; /* * Create temporary arrays that will help us decide if * changed[i] should remain false, or become true. */ - if (!XDL_CALLOC_ARRAY(action1, xdf1->nrec + 1)) { - ret = -1; - goto cleanup; - } - if (!XDL_CALLOC_ARRAY(action2, xdf2->nrec + 1)) { + if (!XDL_CALLOC_ARRAY(action1, len1) || + !XDL_CALLOC_ARRAY(action2, len2) || + !XDL_ALLOC_ARRAY(xdf1->reference_index, len1) || + !XDL_ALLOC_ARRAY(xdf2->reference_index, len2)) + { ret = -1; goto cleanup; } @@ -291,20 +288,44 @@ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xd /* * Initialize temporary arrays with DISCARD, KEEP, or INVESTIGATE. */ - if ((mlim = xdl_bogosqrt((long)xdf1->nrec)) > XDL_MAX_EQLIMIT) - mlim = XDL_MAX_EQLIMIT; - for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) { - rcrec = cf->rcrecs[recs->minimal_perfect_hash]; + if (need_min) { + /* i.e. infinity */ + mlim1 = PTRDIFF_MAX; + } else { + mlim1 = xdl_bogosqrt((uint64_t)xdf1->nrec); + if (mlim1 > XDL_MAX_EQLIMIT) + mlim1 = XDL_MAX_EQLIMIT; + } + for (i = 0; i < len1; i++) { + size_t mph1 = xdf1->recs[i + off].minimal_perfect_hash; + rcrec = cf->rcrecs[mph1]; nm = rcrec ? rcrec->len2 : 0; - action1[i] = (nm == 0) ? DISCARD: (nm >= mlim && !need_min) ? INVESTIGATE: KEEP; + if (nm == 0) + action1[i] = DISCARD; + else if (nm < mlim1) + action1[i] = KEEP; + else /* nm >= mlim1 */ + action1[i] = INVESTIGATE; } - if ((mlim = xdl_bogosqrt((long)xdf2->nrec)) > XDL_MAX_EQLIMIT) - mlim = XDL_MAX_EQLIMIT; - for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) { - rcrec = cf->rcrecs[recs->minimal_perfect_hash]; + if (need_min) { + /* i.e. infinity */ + mlim2 = PTRDIFF_MAX; + } else { + mlim2 = xdl_bogosqrt((uint64_t)xdf2->nrec); + if (mlim2 > XDL_MAX_EQLIMIT) + mlim2 = XDL_MAX_EQLIMIT; + } + for (i = 0; i < len2; i++) { + size_t mph2 = xdf2->recs[i + off].minimal_perfect_hash; + rcrec = cf->rcrecs[mph2]; nm = rcrec ? rcrec->len1 : 0; - action2[i] = (nm == 0) ? DISCARD: (nm >= mlim && !need_min) ? INVESTIGATE: KEEP; + if (nm == 0) + action2[i] = DISCARD; + else if (nm < mlim2) + action2[i] = KEEP; + else /* nm >= mlim2 */ + action2[i] = INVESTIGATE; } /* @@ -312,27 +333,45 @@ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xd * false, or become true. */ xdf1->nreff = 0; - for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; - i <= xdf1->dend; i++, recs++) { - if (action1[i] == KEEP || - (action1[i] == INVESTIGATE && !xdl_clean_mmatch(action1, i, xdf1->dstart, xdf1->dend))) { - xdf1->reference_index[xdf1->nreff++] = i; - /* changed[i] remains false, i.e. keep */ - } else - xdf1->changed[i] = true; - /* i.e. discard */ + for (i = 0; i < len1; i++) { + uint8_t action = action1[i]; + + if (action == INVESTIGATE) { + if (!xdl_clean_mmatch(action1, i, len1)) + action = KEEP; + else + action = DISCARD; + } + + if (action == KEEP) { + xdf1->reference_index[xdf1->nreff++] = i + off; + /* changed[i] remains false */ + } else if (action == DISCARD) { + xdf1->changed[i + off] = true; + } else { + BUG("Illegal state for action"); + } } xdf2->nreff = 0; - for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; - i <= xdf2->dend; i++, recs++) { - if (action2[i] == KEEP || - (action2[i] == INVESTIGATE && !xdl_clean_mmatch(action2, i, xdf2->dstart, xdf2->dend))) { - xdf2->reference_index[xdf2->nreff++] = i; - /* changed[i] remains false, i.e. keep */ - } else - xdf2->changed[i] = true; - /* i.e. discard */ + for (i = 0; i < len2; i++) { + uint8_t action = action2[i]; + + if (action == INVESTIGATE) { + if (!xdl_clean_mmatch(action2, i, len2)) + action = KEEP; + else + action = DISCARD; + } + + if (action == KEEP) { + xdf2->reference_index[xdf2->nreff++] = i + off; + /* changed[i] remains false */ + } else if (action == DISCARD) { + xdf2->changed[i + off] = true; + } else { + BUG("Illegal state for action"); + } } cleanup:
diff --git a/xdiff/xutils.c b/xdiff/xutils.c index 77ee1ad..9a999ac 100644 --- a/xdiff/xutils.c +++ b/xdiff/xutils.c
@@ -23,8 +23,8 @@ #include "xinclude.h" -long xdl_bogosqrt(long n) { - long i; +uint64_t xdl_bogosqrt(uint64_t n) { + uint64_t i; /* * Classical integer square root approximation using shifts.
diff --git a/xdiff/xutils.h b/xdiff/xutils.h index 615b4a9..58f9d74 100644 --- a/xdiff/xutils.h +++ b/xdiff/xutils.h
@@ -25,7 +25,7 @@ -long xdl_bogosqrt(long n); +uint64_t xdl_bogosqrt(uint64_t n); int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize, xdemitcb_t *ecb); int xdl_cha_init(chastore_t *cha, long isize, long icount);