diff --git a/.b4-config b/.b4-config new file mode 100644 index 00000000000000..fd4fb56b6d5678 --- /dev/null +++ b/.b4-config @@ -0,0 +1,6 @@ +# Note that these are default values that you can tweak via the typical +# git-config(1) machinery. You thus shouldn't ever have to change this file. +# See also https://b4.docs.kernel.org/en/latest/config.html. +[b4] +send-same-thread = shallow +prep-cover-template = ./.b4-cover-template diff --git a/.b4-cover-template b/.b4-cover-template new file mode 100644 index 00000000000000..8168d8a10b3a9e --- /dev/null +++ b/.b4-cover-template @@ -0,0 +1,12 @@ +${cover} + +--- +${shortlog} + +${diffstat} + +${range_diff} +--- +base-commit: ${base_commit} +change-id: ${change_id} +${prerequisites} diff --git a/.gitattributes b/.gitattributes index 556322be01b4a8..26490ad60a74d0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,7 +2,7 @@ *.[ch] whitespace=indent,trail,space,incomplete diff=cpp *.sh whitespace=indent,trail,space,incomplete text eol=lf *.perl text eol=lf diff=perl -*.pl text eof=lf diff=perl +*.pl text eol=lf diff=perl *.pm text eol=lf diff=perl *.py text eol=lf diff=python *.bat text eol=crlf diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3da5326f0ba90a..85cfedf5b0e0a4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -407,7 +407,7 @@ jobs: 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 @@ -420,7 +420,9 @@ jobs: CI_JOB_IMAGE: ${{matrix.vector.image}} CUSTOM_PATH: /custom runs-on: ubuntu-latest - container: ${{matrix.vector.image}} + container: + image: ${{ matrix.vector.image }} + options: ${{ github.repository_visibility == 'private' && '--pids-limit 16384 --ulimit nproc=16384:16384 --ulimit nofile=32768:32768' || '' }} steps: - name: prepare libc6 for actions if: matrix.vector.jobname == 'linux32' diff --git a/.gitignore b/.gitignore index 4da58c6754899e..4173111c01b2b6 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,7 @@ /git-diagnose /git-diff /git-diff-files +/git-diff-hunks /git-diff-index /git-diff-pairs /git-diff-tree diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e0b9a0d82b684f..1c4d04da9dcd4c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -42,15 +42,15 @@ test:linux: - 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 @@ test:linux: - jobname: linux-asan-ubsan image: ubuntu:rolling CC: clang + - 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 - - jobname: linux-meson - image: ubuntu:rolling - CC: gcc + # 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 @@ -81,13 +88,8 @@ test:osx: tags: - saas-macos-large-m2pro variables: - TEST_OUTPUT_DIRECTORY: "/Volumes/RAMDisk" + TEST_OUTPUT_DIRECTORY: "/tmp/test-output" before_script: - # Create a 4GB RAM disk that we use to store test output on. This small hack - # significantly speeds up tests by more than a factor of 2 because the - # macOS runners use network-attached storage as disks, which is _really_ - # slow with the many small writes that our tests do. - - sudo diskutil apfs create $(hdiutil attach -nomount ram://8192000) RAMDisk - ./ci/install-dependencies.sh script: - ./ci/run-build-and-tests.sh @@ -145,6 +147,9 @@ test:mingw64: needs: - job: "build:mingw64" artifacts: true + variables: + # Windows runners don't have enough RAM to run EXPENSIVE tests. + GIT_TEST_LONG: false before_script: - *windows_before_script - git-sdk/usr/bin/bash.exe -l -c 'tar xf artifacts/artifacts.tar.gz' @@ -161,11 +166,16 @@ test:mingw64: TEST_OUTPUT_DIRECTORY: "C:/Git-Test" tags: - saas-windows-medium-amd64 + cache: + key: + files: + - ci/install-dependencies.ps1 + paths: + - .dependencies before_script: - *windows_before_script - - choco install -y git meson ninja rust-ms - - Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1 - - refreshenv + - ./ci/install-dependencies.ps1 + - $env:Path = "C:\Meson;C:\Rust\bin;$env:Path" - New-Item -Path $env:TEST_OUTPUT_DIRECTORY -ItemType Directory build:msvc-meson: @@ -188,6 +198,9 @@ test:msvc-meson: script: - | & "C:/Program Files/Git/usr/bin/bash.exe" -l -c 'ci/run-test-slice-meson.sh build $CI_NODE_INDEX $CI_NODE_TOTAL' + variables: + # Windows runners don't have enough RAM to run EXPENSIVE tests. + GIT_TEST_LONG: false after_script: - | if ($env:CI_JOB_STATUS -ne "success") { diff --git a/.mailmap b/.mailmap index c2e3939beb286d..f8ede075ea172f 100644 --- a/.mailmap +++ b/.mailmap @@ -277,6 +277,7 @@ Sven Verdoolaege SZEDER Gábor Tao Qingyun <845767657@qq.com> Tay Ray Chuan +Taylor Blau Ted Percival Theodore Ts'o Thomas Ackermann diff --git a/.tsan-suppressions b/.tsan-suppressions index 5ba86d68459e61..d84883bd90f9a6 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/Makefile b/Documentation/Makefile index 2699f0b24af192..170fcee66e23d5 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -33,6 +33,7 @@ MAN5_TXT += gitattributes.adoc MAN5_TXT += gitformat-bundle.adoc MAN5_TXT += gitformat-chunk.adoc MAN5_TXT += gitformat-commit-graph.adoc +MAN5_TXT += gitformat-diff-hunks.adoc MAN5_TXT += gitformat-index.adoc MAN5_TXT += gitformat-loose.adoc MAN5_TXT += gitformat-pack.adoc diff --git a/Documentation/MyFirstContribution.adoc b/Documentation/MyFirstContribution.adoc index b9fdefce0224c9..34bfa2e3a9aaec 100644 --- a/Documentation/MyFirstContribution.adoc +++ b/Documentation/MyFirstContribution.adoc @@ -790,7 +790,7 @@ We can note a few things: v3", etc. in place of "PATCH". For example, "[PATCH v2 1/3]" would be the first of three patches in the second iteration. Each iteration is sent with a new cover letter (like "[PATCH v2 0/3]" above), itself a reply to the cover letter of the - previous iteration (more on that below). + first iteration (more on that below). NOTE: A single-patch topic is sent with "[PATCH]", "[PATCH v2]", etc. without _i_/_n_ numbering (in the above thread overview, no single-patch topic appears, @@ -833,7 +833,7 @@ This patchset is part of the MyFirstContribution tutorial and should not be merged. ---- -At this point the tutorial diverges, in order to demonstrate two +At this point the tutorial diverges, in order to demonstrate three different methods of formatting your patchset and getting it reviewed. The first method to be covered is GitGitGadget, which is useful for those @@ -845,9 +845,14 @@ more fine-grained control over the emails to be sent. This method requires some setup which can change depending on your system and will not be covered in this tutorial. +The third method to be covered is `b4`, which builds on top of `git +format-patch` and `git send-email`. This method is the recommended way to +submit patches via mail as it automates a lot of the bookkeeping required by +`git send-email`. + Regardless of which method you choose, your engagement with reviewers will be -the same; the review process will be covered after the sections on GitGitGadget -and `git send-email`. +the same; the review process will be covered after the sections on GitGitGadget, +`git send-email` and `b4`. [[howto-ggg]] == Sending Patches via GitGitGadget @@ -1214,7 +1219,7 @@ between your last version and now, if it's something significant. You do not need the exact same body in your second cover letter; focus on explaining to reviewers the changes you've made that may not be as visible. -You will also need to go and find the Message-ID of your previous cover letter. +You will also need to go and find the Message-ID of your first cover letter. You can either note it when you send the first series, from the output of `git send-email`, or you can look it up on the https://lore.kernel.org/git[mailing list]. Find your cover letter in the @@ -1227,8 +1232,8 @@ Message-ID: Your Message-ID is ``. This example will be used below as well; make sure to replace it with the correct Message-ID for your -**previous cover letter** - that is, if you're sending v2, use the Message-ID -from v1; if you're sending v3, use the Message-ID from v2. +**first cover letter** - that is, for any subsequent version that you send, +always use the Message-ID from v1. While you're looking at the email, you should also note who is CC'd, as it's common practice in the mailing list to keep all CCs on a thread. You can add @@ -1296,6 +1301,87 @@ index 88f126184c..38da593a60 100644 2.21.0.392.gf8f6787159e-goog ---- +[[howto-b4]] +== Sending Patches with `b4` + +`b4` is a tool that builds on top of `git format-patch` and `git send-email`. +It automates much of the bookkeeping involved in sending a patch series to a +mailing-list-based project. + +Refer to the https://b4.docs.kernel.org/[b4 documentation] for a full reference. + +[[prep-b4]] +=== Preparing a Patch Series + +`b4` tracks your patch series as a branch. To start tracking the `psuh` branch +you have been working on, run: + +---- +$ b4 prep --enroll master +---- + +This enrolls the current branch, using `master` as the base of the topic. `b4` +manages the cover letter as part of the branch, so you can edit it at any time +with: + +---- +$ b4 prep --edit-cover +---- + +The cover letter not only tracks the content of the top-level mail, but also +the set of recipients. You can add recipients by adding `To:` and `Cc:` +trailer lines. + +[[send-b4]] +=== Sending the Patches + +Before sending the series out for real, you can inspect what `b4` would send by +passing `--dry-run`: + +---- +$ b4 send --dry-run +---- + +Once you are happy with the result, send the series with: + +---- +$ b4 send +---- + +[[v2-b4]] +=== Sending v2 + +When you are ready to send a new iteration of your series, refine your +patches as usual using linkgit:git-rebase[1]. Note that you typically want to +rebase on top of the cover letter. You can configure an alias to enable easy +rebases going forward: + +--- +$ git config set alias.b4-rebase 'rebase "HEAD^{/--- b4-submit-tracking ---}"' +$ git b4-rebase -i +--- + +Before sending out the new version you should also update the cover letter with +`b4 prep --edit-cover` to note the relevant changes compared to the previous +version. You can inspect the changes between the two versions with `b4 prep +--compare-to=v1`. + +Same as with the first version, you can use `b4 send` to send out the second +version. `b4` automatically bumps the version to `v2`, generates the range-diff +against the previous iteration, and threads the new series as a reply to the +cover letter of the first version. + +[[configure-b4]] +=== Configure b4 + +`b4` can be configured via linkgit:git-config[1]. In addition to that, projects +can have their own set of defaults in `.b4-config` in the root tree, which also +uses Git's config format. The user's configuration always takes precedence over +the per-project defaults. + +Refer to the https://b4.docs.kernel.org/en/latest/config.html[b4 config documentation] +for more information on the available options. + [[now-what]] == My Patch Got Emailed - Now What? @@ -1330,6 +1416,28 @@ previous one" patches over 2 days), reviewers would strongly prefer if a single polished version came 2 days later instead, and that version with fewer mistakes were the only one they would need to review. +This consideration applies not only when going from the initial patch to v2, +but also to later iterations of the same series. There is no fixed rule for how +long to wait before sending a new version. A useful default is to send at most +one new version of the same patch series per day. This gives multiple reviewers +time to comment, gives reviewers across time zones a fair chance to +participate, lets you batch feedback together, and gives you time to think +through the comments you received. Knowing that you should not immediately send +another version also encourages you to review the patches more carefully before +sending them, catch small mistakes such as typos and off-by-one errors +yourself, and let reviewers spend more of their attention on design, +algorithms, and other substantial issues. + +The right timing depends on the topic and the feedback. Larger series usually +need more review time. If the only comments so far are minor, such as typo +fixes, it often makes sense to wait a little longer in case deeper reviews are +still coming. If the comments call for substantial rework, do not rush out an +updated version before you have reviewed the larger changes carefully. Instead, +reply to the review that prompted the rewrite, say that you are preparing a +substantial rework, and mention which parts of the current series will become +obsolete so reviewers can avoid spending time on them until the updated series +is ready. + [[reviewing]] === Responding to Reviews @@ -1337,11 +1445,13 @@ fewer mistakes were the only one they would need to review. After a few days, you will hopefully receive a reply to your patchset with some comments. Woohoo! Now you can get back to work. -It's good manners to reply to each comment, notifying the reviewer that you have -made the change suggested, feel the original is better, or that the comment -inspired you to do something a new way which is superior to both the original -and the suggested change. This way reviewers don't need to inspect your v2 to -figure out whether you implemented their comment or not. +It's good manners to reply to each comment in the mailing list discussion +instead of letting the next version of your patch be your only response. Tell +the reviewer whether you plan to make the suggested change, keep the original, +or pursue a different approach. This way reviewers can respond to your reasoning +before you spend time preparing a version they may not agree with, and later do +not need to inspect your v2 to figure out whether you implemented their comment +or not. Reviewers may ask you about what you wrote in the patchset, either in the proposed commit log message or in the changes themselves. You @@ -1367,6 +1477,11 @@ effect which had not occurred to you. It is always okay to ask for clarification if you aren't sure why a change was suggested, or what the reviewer is asking you to do. +When replying to review comments, quote only the parts of the message that are +relevant to your response. It is usually helpful to trim away unrelated context, +such as large portions of the patch that are not being discussed, while keeping +enough quoted text for readers to understand what you are responding to. + Make sure your email client has a plaintext email mode and it is turned on; the Git list rejects HTML email. Please also follow the mailing list etiquette outlined in the diff --git a/Documentation/RelNotes/2.55.0.adoc b/Documentation/RelNotes/2.55.0.adoc index f037568499a95a..e7e77a8112385c 100644 --- a/Documentation/RelNotes/2.55.0.adoc +++ b/Documentation/RelNotes/2.55.0.adoc @@ -31,7 +31,9 @@ UI, Workflows & Features subcommand "git url-parse". * Misspelt proxy URL (e.g., httt://...) did not trigger any warning - or failure, which has been corrected. + or failure, which has been corrected. We had a regression in this + update that broke https:// proxies, but that has been caught and + corrected. * Document the fact that .git/info/exclude is shared across worktrees linked to the same repository. @@ -62,6 +64,20 @@ UI, Workflows & Features 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. -------------------------------------------------------------- @@ -69,8 +85,8 @@ 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. + * Rust support is enabled by default (but still allows opting out); + in Git version 3.0, Rust will become mandatory. * Preparation of the xdiff/ codebase to work with Rust. @@ -160,6 +176,62 @@ Performance, Internal Implementation, Development Support etc. * 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. + + * Wean the Windows builds in GitLab CI procedure away from + (unfortunately unreliable) Chocolatey to install dependencies. + (merge 0e7b51fed2 ps/gitlab-ci-windows later to maint). + + * Build-fix for 32-bit Windows. + + * Xcode 15 and later has a linker set to complain when the same library + archive is listed twice on the command line. Squelch the annoyance. + Fixes since v2.54 ----------------- @@ -294,6 +366,71 @@ Fixes since v2.54 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). @@ -316,3 +453,8 @@ Fixes since v2.54 (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). + (merge 0bf506efd4 kw/gitattributes-typofix later to maint). diff --git a/Documentation/RelNotes/2.56.0.adoc b/Documentation/RelNotes/2.56.0.adoc new file mode 100644 index 00000000000000..811f74bc7dbcb6 --- /dev/null +++ b/Documentation/RelNotes/2.56.0.adoc @@ -0,0 +1,352 @@ +Git v2.56 Release Notes +======================= + +UI, Workflows & Features +------------------------ + + * Advice shown by "git status" when the local branch is behind or has + diverged from its push branch has been updated to suggest "git pull + ". + + * The handling of promisor-remote protocol capability has been updated + to allow the other side to add to the list of promisor remotes via the + 'promisor.acceptFromServerURL' configuration variable. + + * The 'ort' merge backend has been hardened against corrupt trees by + ensuring it aborts under appropriate error conditions. + + * The `fetch.followRemoteHEAD` configuration variable has been added to + provide a default for the per-remote `remote..followRemoteHEAD` + setting. + + * "git log --follow" has been updated to better handle non-linear + history, in which the path being tracked gets renamed differently in + multiple history lines. + + * The "git repo info" command has been taught new keys to output both + absolute and relative paths for "gitdir" and "commondir", supported by + a new path-formatting helper extracted from "git rev-parse". + + * When 'git push origin/main' or 'git branch origin main' is run, the + command is now recognized as a potential typo, and advice has been + added to offer a typo fix. + + * The 'git refs' toolbox has been extended with new 'create', 'delete', + 'update', and 'rename' subcommands to create, delete, update, and + rename references, respectively. + + * The experimental 'git history' command has been taught a new 'drop' + subcommand to remove a commit, with its descendants replayed onto its + parent. + + * The alignment of commit object name abbreviations in 'git blame' + output has been optimized to reserve a column for marks (caret, + question mark, or asterisk) only when such marks are actually shown. + + * Option parsing with 'git rev-parse --parseopt' and in most 'git' + subcommands has been updated to exit with 0 (instead of 129) when the + help option ('-h' or '--help') is requested directly by the user, + aligning with standard Unix convention. + + * The '[includeIf "condition"]' conditional inclusion facility for + configuration files has been taught to use the location of the + worktree in its condition. + + * The usage string and SYNOPSIS for 'git fast-export' have been + standardized to make them consistent with each other and with other + commands. + + +Performance, Internal Implementation, Development Support etc. +-------------------------------------------------------------- + + * The refactoring of 'setup.c' has been continued to drop remaining + global state (`git_work_tree_cfg`, `is_bare_repository_cfg`), updating + `is_bare_repository()` to no longer implicitly rely on + `the_repository`. + + * Project-specific configuration for b4 has been introduced, and the + documentation has been updated to recommend using it as a + streamlined method for submitting patches. + + * The default format path of git cat-file --batch has been optimized + to use strbuf_add_oid_hex() and strbuf_add_uint() instead of + strbuf_addf(), yielding a noticeable speedup. + + * Commands that list branches and tags (like git branch and git tag) + have been optimized to pass the namespace prefix when initializing + their ref iterator, avoiding a loose-ref scaling regression in + repositories with many unrelated loose references. + + * The packed object source has been refactored into a proper struct + odb_source. + + * The global configuration variables protect_hfs and protect_ntfs have + been migrated into struct repo_config_values to tie them to + per-repository configuration state. + + * The trailer sections in SubmittingPatches have been updated to + encourage use of standard trailers. + + * The documentation in SubmittingPatches has been updated to clarify how + patch contributors should respond to design and viability critiques, + and how the resolution of such critiques should be recorded in the + final commit messages. + + * The pack-objects command has been updated to support reachability + bitmaps and delta-islands concurrently with the `--path-walk` option, + allowing faster packaging by falling back to path-walk when bitmaps + cannot fully satisfy the request. + + * Documentation on community contribution guidelines has been updated to + encourage replying to review comments before rerolling, and to advise + a default limit of at most one reroll per day to give reviewers across + different time zones enough time to participate. + + * The lazy priority queue optimization pattern (deferring actual removal + in 'prio_queue_get()' to allow get+put fusion) has been folded + directly into 'prio_queue' itself, speeding up commit traversal + workflows and simplifying callers. + + * The 'reprepare()' callback for object database sources has been + generalized into a 'prepare()' callback with an optional flush cache + flag, and a new 'odb_prepare()' wrapper has been introduced to allow + pre-opening object database sources. + + * The 'whence' field in 'struct object_info' has been removed. The + backend-specific object information retrieval has been refactored into + an opt-in 'struct object_info_source' structure. + + * A racy build failure under Meson has been corrected by ensuring that + the generated header file 'hook-list.h' is built before compiling + files in 'builtin_sources' that depend on it. + + * The repository discovery and repository configuration phases, which + were previously intertwined in 'setup.c', have been split. Repository + discovery has been updated to populate a 'struct repo_discovery' + without modifying the repository state, which is then taken by + repository configuration to initialize the repository, paving the way + for clean unification of repository configuration. + + * The 'SubmittingPatches' document has been updated to explicitly + describe the expectation for contributors to retract or abandon their + patch series when they are no longer pursuing it. + + * The contributor guide has been updated to advise new contributors to + trim irrelevant quoted text when replying to review comments, matching + the existing advice given to reviewers. + + * The build system has been updated to support building universal macOS + binaries when 'Rust' is enabled, by compiling separate static archives + for each target triple listed in 'RUST_TARGETS' and combining them + using the macOS 'lipo' tool. The 'git-credential-osxkeychain' helper + has been updated to link against '$(RUST_LIB)' when 'Rust' is enabled. + + * The test suite has been updated to use the 'test_grep' helper instead + of bare 'grep' for test assertions, allowing file contents to be + printed on failure for easier debugging. A new 'greplint' linter has + been introduced to detect and prevent new bare 'grep' assertions from + being added to the test suite. + + * The pipelines in 't1410-reflog.sh' have been replaced with the + 'test_stdout_line_count' helper to avoid suppressing the exit code of + 'git' commands, ensuring failures are not hidden from the test suite. + + * The cache-scanning loop in 'next_cache_entry()' has been optimized + to avoid rescanning already-unpacked index entries, preventing a + quadratic performance slow-down when diffing the working tree + against a commit with a pathspec matching early index entries. + + * The global configuration variable 'ignore_case' (representing the + 'core.ignorecase' configuration) has been migrated into 'struct + repo_config_values' to tie it to a specific repository instance. + + * The performance of ref updates and reads using the 'reftable' backend + in the presence of many deletion tombstone records has been optimized + by removing the tombstone suppression flag from the merged iterator + and instead skipping tombstones at higher-level call sites where + iteration bounds are known. + + * Various code paths have been hardened against potential NULL-pointer + dereferences and invalid file descriptor accesses flagged by + Coverity. + + * The in-tree 'b4' cover letter template has been updated to include the + 'change-id' trailer, ensuring that sent tags generated by 'b4' contain + the required tracking information for subsequent runs. + + * 'git receive-pack' has been refactored to use ODB transaction + interfaces instead of directly managing 'tmp_objdir' for staging + incoming objects, bringing it closer to being ODB backend agnostic. + + * The test script 't/t9811-git-p4-label-import.sh' has been + modernized to use 'test_path_is_file' and 'test_path_is_missing' + instead of raw 'test -f' and '! test -f' calls. + + * A redundant strbuf_reset() call in the 'HAVE_GETDELIM' path of + strbuf_getwholeline() has been removed, as getdelim() overwrites the + buffer and the length is updated afterward. + + * The object database enumeration interface odb_for_each_object() has + been taught to accept object filters, allowing the underlying backends + to optimize the traversal by using reachability bitmaps when + available. 'git cat-file --batch-all-objects' has been updated to use + this generic interface, simplifying its code and avoiding direct + access to ODB backend internals. + + * The test script 't/t1100-commit-tree-options.sh' has been modernized + by converting test cases to the modern style (using single quotes and + tab indentation) and moving the creation of the expected file inside + the setup test so it runs under the protection of the test harness. + + * The test script 't/t7614-merge-signoff.sh' has been updated to avoid + suppressing the exit code of 'git' commands in a pipe. + + * The 'git rev-list --no-walk' command has been corrected to restore + pathspec filtering, which was lost when the streaming walk was + refactored. + + +Fixes since v2.55 +----------------- + + * A regression in the error diagnosis code for invalid .git files has + been fixed, avoiding a potential NULL-pointer crash when reporting + that a .git file does not point to a valid repository. + (merge 54a441bcea jk/setup-gitfile-diag-fix later to maint). + + * Support for hashing loose or packed objects larger than 4GB on Windows + and other LLP64 platforms has been improved by converting object header + buffers and data-handling functions from 'unsigned long' to 'size_t'. + (merge d99e13d0be po/hash-object-size-t later to maint). + + * The display of the rebase todo list in "git status" has been + improved to correctly abbreviate object IDs for more commands and + avoid misinterpreting refs as object IDs. + (merge 6f34e5f9e3 pw/status-rebase-todo later to maint). + + * Reference backend configuration has been updated to load lazily to + avoid recursive calls during repository initialization when 'onbranch' + configuration conditions are evaluated. This has also fixed a memory + leak and allowed the unused `chdir_notify_reparent()` machinery to be + dropped. + (merge d6522d01df ps/refs-onbranch-fixes later to maint). + + * The connectivity check has been refactored to search for promisor + objects in a generic way using the object database interface, + rather than iterating packfiles directly. This allows connectivity + checks to work properly in repositories that do not use packfiles. + (merge 66ee9cb930 ps/connected-generic-promisor-checks later to maint). + + * A test checking interactions between git rebase --quit and + autostash in t3420-rebase-autostash.sh has been corrected to use + test_path_is_missing instead of ! grep on a file that shouldn't + exist in the conflicted state. + (merge eaad121fef sg/t3420-do-not-grep-in-missing-file later to maint). + + * The GPG and SSH signature parsing code has been corrected to strip + carriage return characters only when they immediately precede line + feeds, instead of unconditionally stripping all carriage returns. + (merge 5dea8b690b ad/gpg-strip-cr-before-lf later to maint). + + * A memory leak in the 'reftable_writer_new()' initialization function + has been fixed by delaying the allocation of 'struct reftable_writer' + until after input options are validated. + (merge c6fb3b9c3e jk/reftable-leakfix later to maint). + + * A memory leak in the '--base' handling of 'git format-patch' has been + plugged, and the leak reporting of the test suite when running under a + TAP harness has been improved. + (merge 973a0373ff jk/format-patch-leakfix later to maint). + + * A write file stream resource leak has been fixed as part of a code + cleanup. + (merge ebb4d2ffa3 jc/history-message-prep-fix later to maint). + + * Various memory leaks in the Bloom-filter code paths that are exposed + when running tests with the 'GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=1' + environment variable have been plugged. + (merge 459088ec2e jk/bloom-leak-fixes later to maint). + + * The wincred credential helper has been updated to avoid memory + corruption when erasing credentials and to prevent silent + credential loss when storing OAuth tokens, by correcting buffer + allocations and arguments passed to safe-CRT APIs. + (merge f635ab9ab4 js/wincred-fixes later to maint). + + * Various code paths that initialize a cryptographic hash context but + bail out or finish without calling 'git_hash_final()' have been taught + to call 'git_hash_discard()' to release allocated resources, fixing + memory leaks when Git is built with non-default backends like + 'OpenSSL' or 'libgcrypt'. + (merge 600588d2aa jk/hash-algo-leak-fixes later to maint). + + * Various resource leaks, invalid file descriptor closures, and process + handle ownership issues flagged by Coverity have been fixed. + (merge 9184231173 js/coverity-fixes later to maint). + + * Dockerized CI jobs running in private GitHub repositories have been + adjusted to use explicit process and file limits, preventing resource + exhaustion errors on private runners. + (merge bad766fbac js/ci-dockerized-pid-limit later to maint). + + * Various test scripts have been updated to clean up large temporary + files and repositories, reducing peak disk usage during testing. + Also, expensive tests have been disabled on platforms that lack + sufficient resources (like 32-bit platforms and Windows CI runners), + and the long test suite has been enabled in GitLab CI. + (merge 84248444ad ps/t-fixes-for-git-test-long later to maint). + + * The UTF-8 precomposition wrapper on macOS has been updated to use a + flexible array member to represent the name of a directory entry, + preventing fortified libc checks from failing when the name is + reallocated to be larger than 'NAME_MAX' bytes. + (merge 1eb281159f ih/precompose-flex-array later to maint). + + * The 'git_hash_*()' wrappers have been updated to be used consistently + across the codebase instead of direct calls to members of 'struct + git_hash_algo', and 'git_hash_discard()' has been made idempotent to + simplify cleanups. + (merge 9e396aa553 jk/git-hash-cleanups later to maint). + + * The sideband demultiplexer has been updated to recognize ANSI SGR + escape sequences that use colon-separated subfields (e.g., for + 256-color or true-color codes). + (merge 3792b2aea4 mm/sideband-ansi-sgr-colon-fix later to maint). + + * The 'reftable' code has been hardened against corrupted tables by + fixing out-of-bounds writes, out-of-bounds reads, and abort calls + during parsing. + (merge ca93c27328 ps/reftable-hardening later to maint). + + * A description in the release notes for Git 2.55.0 has been + retroactively updated to clarify that Rust support is enabled by + default, but still optional, and will become mandatory in Git 3.0. + (merge 18b2009d14 jc/relnotes-2.55-rust-fix later to maint). + + * The early-exit optimization in 'paint_down_to_common()' has been + gated on the queue being generation-ordered, fixing a bug where + 'git merge-base' (without '--all') could return incorrect results + on repositories with v1 commit graphs and clock skew. + (merge ae68032a8d kk/commit-reach-find-all-fix later to maint). + + * The client-side parser of the server-advertised bundle-URI list has + been updated to drain the remaining response in order to avoid + protocol desynchronization when the server sends a misconfigured list. + Also, the server-side has been taught to omit empty configuration + values instead of sending invalid key-value lines. + (merge 50de1169e4 tc/bundle-uri-empty-fix later to maint). + + * The 'topo_levels' slab was propagated only to the topmost layer of a + split commit-graph chain, causing topological levels for commits in + base layers to be recomputed during incremental writes. This has been + corrected. + + * The stream-based object signature verification path has been + corrected to avoid double-closing the stream on read errors. + (merge cfd52a74a0 ps/odb-stream-double-close-fix later to maint). + + * The '-i' shorthand for the '--init' option, which was accepted by the + 'git submodule update' command until it was broken in a modernization + of the option-parsing code, has been restored. + (merge ff1da37f58 dm/submodule-update-i-shorthand later to maint). diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index 176567738d47df..c269e474e3f2bf 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -48,13 +48,41 @@ area. . You get comments and suggestions for improvements. You may even get them in an "on top of your change" patch form. You are expected to - respond to them with "Reply-All" on the mailing list, while taking - them into account while preparing an updated set of patches. + respond to them with "Reply-All" on the mailing list, instead of + letting an updated patch series be your only response. Tell + reviewers which suggestions you plan to use, which ones you disagree + with, and when a comment leads you to consider a different approach. + Use these replies and any follow-up discussion as input when + preparing an updated set of patches. ++ +Be particularly mindful of critiques regarding the high-level design +or viability of your proposal (e.g., questioning if the feature is +worth implementing, or if the chosen approach is appropriate). Defend +your design decisions on the list first and work with reviewers and +other members to improve the design before revising the implementation. +This will avoid wasting effort on an implementation before its design is +solid. ++ +Make sure that any new version explains and justifies those design +decisions more clearly, in the cover letter and in the revised commit +messages. Aim to make the reviewers say "it is now clear why we may +want to do this with the updated version". ++ +Topics with unresolved fundamental design critiques will not be +considered ready for merging. + It is often beneficial to allow some time for reviewers to provide feedback before sending a new version, rather than sending an updated series immediately after receiving a review. This helps collect broader -input and avoids unnecessary churn from many rapid iterations. +input, gives reviewers in different time zones a fair chance to comment, +and avoids unnecessary churn from many rapid iterations. Waiting also +encourages you to polish each version before sending it, so reviewers +can focus on substantial issues rather than typos or other small +mistakes. ++ +As a rough default, avoid sending more than one new version of the same +series per day, while considering the size of the series and the depth +of review. . These early update iterations are expected to be full replacements, not incremental updates on top of what you posted already. If you @@ -92,9 +120,23 @@ input and avoids unnecessary churn from many rapid iterations. 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. +But sometimes things do not work as planned: + +. If a mailing list discussion convinces you that your changes aren't + ideal, please explicitly retract the topic to save the maintainer + time and effort. + +. If you must drop a topic due to shifting priorities, lack of time, + or other commitments, notify the list as a courtesy so others can + take over. Anyone can resurrect the topic later when they have the + capacity to do so. + +. Topics with unaddressed review comments that remain inactive for + four weeks may be discarded by the maintainer. + In the following sections, many techniques and conventions are listed to help your patches get reviewed effectively in such a life cycle. @@ -237,6 +279,7 @@ Do not forget to update the documentation to describe the updated 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 @@ -322,6 +365,10 @@ The body should provide a meaningful commit message, which: . alternate solutions considered but discarded, if any. +. records the resolution of design or viability concerns raised by the + community during the review, if any, ensuring the historical record + explains why the chosen approach was accepted over alternatives. + [[present-tense]] The problem statement that describes the status quo is written in the present tense. Write "The code does X when it is given input Y", @@ -374,7 +421,7 @@ or, on an older version of Git without support for --pretty=reference: .... [[sign-off]] -=== Certify your work by adding your `Signed-off-by` trailer +=== Certify your work by adding your `Signed-off-by:` trailer To improve tracking of who did what, we ask you to certify that you wrote the patch or have the right to pass it on under the same license @@ -411,7 +458,7 @@ d. I understand and agree that this project and the contribution this project or the open source license(s) involved. ____ -you add a "Signed-off-by" trailer to your commit, that looks like +you add a `Signed-off-by:` trailer to your commit, that looks like this: .... @@ -421,19 +468,23 @@ this: This line can be added by Git if you run the git-commit command with the -s option. -Notice that you can place your own `Signed-off-by` trailer when +Notice that you can place your own `Signed-off-by:` trailer when forwarding somebody else's patch with the above rules for D-C-O. Indeed you are encouraged to do so. Do not forget to place an in-body "From: " line at the beginning to properly attribute the change to its true author (see (2) above). +Place this `Signed-off-by:` trailer at the end, after trailers added by +others and after other trailers added by you; see +<> below ("chronological order"). + This procedure originally came from the Linux kernel project, so our rule is quite similar to theirs, but what exactly it means to sign-off your patch differs from project to project, so it may be different from that of the project you are accustomed to. [[real-name]] -Please use a known identity in the `Signed-off-by` trailer, since we cannot +Please use a known identity in the `Signed-off-by:` trailer, since we cannot accept anonymous contributions. It is common, but not required, to use some form of your real name. We realize that some contributors are not comfortable doing so or prefer to contribute under a pseudonym or preferred name and we can accept @@ -444,7 +495,15 @@ The goal of this policy is to allow us to have sufficient information to contact you if questions arise about your contribution. [[commit-trailers]] -If you like, you can put extra trailers at the end: +=== Commit trailers +It is polite to credit people who have helped with your work to a +substantial enough degree. This project uses commit trailers for that, +where the credited person is written out like a Git author, i.e. with +both their name and their email address. Note that the threshold to +credit someone is a judgement call, and crediting someone for simple +review work is certainly not necessary. + +These are the common trailers in use: . `Reported-by:` is used to credit someone who found the bug that the patch attempts to fix. @@ -457,6 +516,10 @@ If you like, you can put extra trailers at the end: and found it to have the desired effect. . `Co-authored-by:` is used to indicate that people exchanged drafts of a patch before submitting it. +. `Based-on-patch-by:` is used when someone else authored parts of the + patch that you are submitting. This might be relevant if someone sent + a patch to the mailing list with their sign-off. (Be mindful and ask + them to sign off on it if they did not.) . `Helped-by:` is used to credit someone who suggested ideas for changes without providing the precise changes in patch form. . `Mentored-by:` is used to credit someone with helping develop a @@ -468,8 +531,42 @@ While you can also create your own trailer if the situation warrants it, we encourage you to instead use one of the common trailers in this project highlighted above. +Other projects might regularly refer to other kinds of data, like +`Fixes:` and `Link:` in the Linux Kernel project, but these ones in +particular are not used in this project. + 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". +`Signed-off-by:` over `Signed-Off-By:` and `Acked-by:` over `Acked-By:`. + +As mentioned under <> above, trailers are added in +chronological order; one person might sign-off on a patch and send it to +someone else, who then in turn adds her own sign-off. Further, any +trailers that you add beyond your sign-off should come before that +sign-off. That makes it clear what trailers which person added. + +[[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) @@ -562,8 +659,8 @@ when the maintainer did not heavily participate in the discussion and instead left the review to trusted others. Do not forget to add trailers such as `Acked-by:`, `Reviewed-by:` and -`Tested-by:` lines as necessary to credit people who helped your -patch, and "cc:" them when sending such a final version for inclusion. +`Tested-by:` (see <>), and "cc:" them +when sending such a final version for inclusion. ==== `format-patch` and `send-email` @@ -573,8 +670,10 @@ your existing e-mail client (often optimized for "multipart/*" MIME type e-mails) might render your patches unusable. NOTE: Here we outline the procedure using `format-patch` and -`send-email`, but you can instead use GitGitGadget to send in your -patches (see link:MyFirstContribution.html[MyFirstContribution]). +`send-email`, but you can instead use GitGitGadget or `b4` to send in +your patches (see link:MyFirstContribution.html[MyFirstContribution]). +Contributors are encouraged to use `b4`, which automates much of the +bookkeeping that is otherwise done by hand. People on the Git mailing list need to be able to read and comment on the changes you are submitting. It is important for @@ -588,10 +687,13 @@ grouped into their own e-mail thread to help readers find all parts of the series. To that end, send them as replies to either an additional "cover letter" message (see below), the first patch, or the respective preceding patch. Here is a link:MyFirstContribution.html#v2-git-send-email[step-by-step guide] on -how to submit updated versions of a patch series. +how to submit updated versions of a patch series. Before sending another +version, make sure you have answered meaningful review comments in the existing +discussion. Also give reviewers enough time to comment before sending another +version. If your log message (including your name on the -`Signed-off-by` trailer) is not writable in ASCII, make sure that +`Signed-off-by:` trailer) is not writable in ASCII, make sure that you send off a message in the correct encoding. WARNING: Be wary of your MUAs word-wrap @@ -611,7 +713,7 @@ previously sent. The `git format-patch` command follows the best current practice to format the body of an e-mail message. At the beginning of the patch should come your commit message, ending with the -`Signed-off-by` trailers, and a line that consists of three dashes, +`Signed-off-by:` trailers, and a line that consists of three dashes, followed by the diffstat information and the patch itself. If you are forwarding a patch from somebody else, optionally, at the beginning of the e-mail message just before the commit diff --git a/Documentation/config.adoc b/Documentation/config.adoc index a80e7db46d9697..8a172d52f37ec7 100644 --- a/Documentation/config.adoc +++ b/Documentation/config.adoc @@ -146,6 +146,51 @@ refer to linkgit:gitignore[5] for details. For convenience: This is the same as `gitdir` except that matching is done case-insensitively (e.g. on case-insensitive file systems) +`worktree`:: + The data that follows the keyword `worktree` and a colon is used as a + glob pattern. If the working directory of the current worktree matches + the pattern, the include condition is met. ++ +The worktree location is the path where files are checked out (as returned +by `git rev-parse --show-toplevel`). This is different from `gitdir`, which +matches the `.git` directory path. In a linked worktree, the worktree path +is the directory where that worktree's files are located, not the main +repository's `.git` directory. ++ +The pattern uses the same glob syntax as `gitdir` (including `~/`, `./`, +`**/`, and trailing-`/` prefix matching). This condition will never match +in a bare repository (which has no worktree). ++ +Unlike `gitdir`, the `worktree` condition currently matches only the +realpath-resolved worktree location. If the working tree was entered via a +symbolic link, a pattern that uses the symbolic-link spelling may not match; +use the real path instead. ++ +This is useful when you want to apply configuration based on where the +working tree is located on the filesystem. For example, a contributor who +works on the same project both personally and as an employee can use +different `user.name` and `user.email` values depending on which directory +the worktree is checked out under: ++ +---- +[includeIf "worktree:/home/user/work/"] + path = ~/.config/git/work.inc +[includeIf "worktree:/home/user/personal/"] + path = ~/.config/git/personal.inc +---- ++ +While `extensions.worktreeConfig` (see linkgit:git-worktree[1]) also supports +per-worktree configuration, it stores the config inside each repository's +`.git/config.worktree` file and requires running `git config --worktree` +inside each worktree individually. In contrast, `includeIf "worktree:..."` +can be set once in a global or system-level configuration file (e.g. +`~/.config/git/config`) and applies to all repositories at once based on +their worktree location. + +`worktree/i`:: + This is the same as `worktree` except that matching is done + case-insensitively (e.g. on case-insensitive file systems) + `onbranch`:: The data that follows the keyword `onbranch` and a colon is taken to be a pattern with standard globbing wildcards and two additional @@ -244,6 +289,14 @@ Example [includeIf "gitdir:~/to/group/"] path = /path/to/foo.inc +; include if the worktree is at /path/to/project-build +[includeIf "worktree:/path/to/project-build"] + path = build-config.inc + +; include for all worktrees inside /path/to/group +[includeIf "worktree:/path/to/group/"] + path = group-config.inc + ; relative paths are always relative to the including ; file (if the condition is true); their location is not ; affected by the condition @@ -276,13 +329,15 @@ boolean:: 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 `= ` 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 @@ -417,6 +472,8 @@ include::config/credential.adoc[] include::config/diff.adoc[] +include::config/diff-hunks.adoc[] + include::config/difftool.adoc[] include::config/extensions.adoc[] diff --git a/Documentation/config/advice.adoc b/Documentation/config/advice.adoc index 257db58918179a..81f80a92745123 100644 --- a/Documentation/config/advice.adoc +++ b/Documentation/config/advice.adoc @@ -48,6 +48,10 @@ all advice messages. to create a local branch after the fact. diverging:: Shown when a fast-forward is not possible. + fetchRemoteHEADWarn:: + Shown when linkgit:git-fetch[1] reveals that a remote `HEAD` + differs from what is set locally and the user has opted into + receiving a warning in this situation. fetchShowForcedUpdates:: Shown when linkgit:git-fetch[1] takes a long time to calculate forced updates after ref updates, or to warn @@ -90,6 +94,11 @@ all advice messages. Shown when linkgit:git-push[1] rejects a forced update of a branch when its remote-tracking ref has updates that we do not have locally. + pushRepoLooksLikeRef:: + Shown when the repository given to linkgit:git-push[1] is not + a configured remote but looks like a `/` ref, + suggesting that the remote and branch be given as separate + arguments. pushUnqualifiedRefname:: Shown when linkgit:git-push[1] gives up trying to guess based on the source and destination refs what diff --git a/Documentation/config/core.adoc b/Documentation/config/core.adoc index a0ebf03e2eb050..9595619c610425 100644 --- a/Documentation/config/core.adoc +++ b/Documentation/config/core.adoc @@ -670,12 +670,13 @@ but risks losing recent work in the event of an unclean system shutdown. * `pack` hardens objects added to the repo in packfile form. * `pack-metadata` hardens packfile bitmaps and indexes. * `commit-graph` hardens the commit-graph file. +* `diff-hunks` hardens the diff-hunks store. * `index` hardens the index when it is modified. * `objects` is an aggregate option that is equivalent to `loose-object,pack`. * `reference` hardens references modified in the repo. * `derived-metadata` is an aggregate option that is equivalent to - `pack-metadata,commit-graph`. + `pack-metadata,commit-graph,diff-hunks`. * `committed` is an aggregate option that is currently equivalent to `objects`. This mode sacrifices some performance to ensure that work that is committed to the repository with `git commit` or similar commands @@ -750,6 +751,13 @@ core.commitGraph:: to parse the graph structure of commits. Defaults to true. See linkgit:git-commit-graph[1] for more information. +core.diffHunks:: + If true, then Git will consult the diff-hunks store (if it + exists) to skip recomputing diff hunk coordinates in commands + such as `git log --stat` and linkgit:git-blame[1]. This controls + only reading; writing the store is controlled by `diffHunks.write`. + See linkgit:git-diff-hunks[1] for more information. Defaults to true. + core.useReplaceRefs:: If set to `false`, behave as if the `--no-replace-objects` option was given on the command line. See linkgit:git[1] and diff --git a/Documentation/config/diff-hunks.adoc b/Documentation/config/diff-hunks.adoc new file mode 100644 index 00000000000000..ad76d1c6a9a2e4 --- /dev/null +++ b/Documentation/config/diff-hunks.adoc @@ -0,0 +1,8 @@ +diffHunks.write:: + If true, diff-producing commands (`git diff`, `git log`, + `git show`, or `git diff-tree` with a `--stat`, `--numstat`, or + `--shortstat` format) write the hunks + they compute to the diff-hunks store, filling it as a side effect. + The `GIT_DIFF_HUNKS_WRITE` environment variable overrides this for + a single invocation. Reading the store is controlled separately by + `core.diffHunks`. See linkgit:git-diff-hunks[1]. Defaults to false. diff --git a/Documentation/config/diff.adoc b/Documentation/config/diff.adoc index 1135a62a0ad3de..349bdbe4927b6e 100644 --- a/Documentation/config/diff.adoc +++ b/Documentation/config/diff.adoc @@ -218,6 +218,12 @@ endif::git-diff[] Set this option to `true` to make the diff driver cache the text conversion outputs. See linkgit:gitattributes[5] for details. +`diff..process`:: + The command to run as a long-running process that answers + which line ranges changed between two blobs. See + linkgit:gitattributes[5] for the protocol and when it is + consulted. + `diff.indentHeuristic`:: Set this option to `false` to disable the default heuristics that shift diff hunk boundaries to make patches easier to read. diff --git a/Documentation/config/fetch.adoc b/Documentation/config/fetch.adoc index 04ac90912d3a58..00435e9a16d9f9 100644 --- a/Documentation/config/fetch.adoc +++ b/Documentation/config/fetch.adoc @@ -126,3 +126,22 @@ the new bundle URI. The creation token values are chosen by the provider serving the specific bundle URI. If you modify the URI at `fetch.bundleURI`, then be sure to remove the value for the `fetch.bundleCreationToken` value before fetching. + +`fetch.followRemoteHEAD`:: + When fetching using a default refspec, this setting determines how to handle + differences between a fetched remote's `HEAD` and the local + `remotes//HEAD` symbolic-ref. Its value is one of ++ +-- +`create`;; + Create `remotes//HEAD` if a ref exists on the remote, but not locally. + An existing symbolic-ref will not be touched. This is the default value. +`warn`;; + Display a warning if the remote advertises a different `HEAD` than what is + set locally. Behaves like "create" if the local symbolic-ref doesn't exist. +`always`;; + Silently update `remotes//HEAD` whenever the remote advertises a new + value. +`never`;; + Never create or modify the `remotes//HEAD` symbolic-ref. +-- diff --git a/Documentation/config/log.adoc b/Documentation/config/log.adoc index f20cc25cd7c3bf..757a7be196ab38 100644 --- a/Documentation/config/log.adoc +++ b/Documentation/config/log.adoc @@ -53,8 +53,7 @@ This is the same as the `--decorate` option of the `git log`. `log.follow`:: If `true`, `git log` will act as if the `--follow` option was used when a single is given. This has the same limitations as `--follow`, - i.e. it cannot be used to follow multiple files and does not work well - on non-linear history. + i.e. it cannot be used to follow multiple files. `log.graphColors`:: A list of colors, separated by commas, that can be used to draw diff --git a/Documentation/config/promisor.adoc b/Documentation/config/promisor.adoc index b0fa43b8393a53..c1f3d766a64cc0 100644 --- a/Documentation/config/promisor.adoc +++ b/Documentation/config/promisor.adoc @@ -32,24 +32,136 @@ variable is set to "true", and the "name" and "url" fields are always advertised regardless of this setting. promisor.acceptFromServer:: - If set to "all", a client will accept all the promisor remotes - a server might advertise using the "promisor-remote" - capability. If set to "knownName" the client will accept - promisor remotes which are already configured on the client - and have the same name as those advertised by the client. This - is not very secure, but could be used in a corporate setup - where servers and clients are trusted to not switch name and - URLs. If set to "knownUrl", the client will accept promisor - remotes which have both the same name and the same URL - configured on the client as the name and URL advertised by the - server. This is more secure than "all" or "knownName", so it - should be used if possible instead of those options. Default - is "none", which means no promisor remote advertised by a - server will be accepted. By accepting a promisor remote, the - client agrees that the server might omit objects that are - lazily fetchable from this promisor remote from its responses - to "fetch" and "clone" requests from the client. Name and URL - comparisons are case sensitive. See linkgit:gitprotocol-v2[5]. + Controls which promisor remotes advertised by a server (using the + "promisor-remote" protocol capability) a client will accept. By + accepting a promisor remote, the client agrees that the server + might omit objects that are lazily fetchable from this promisor + remote from its responses to "fetch" and "clone" requests. ++ +Note that this option does not cause new remotes to be automatically +created in the client's configuration. It only allows remotes which +are somehow already configured to be trusted for the current +operation, or their fields to be updated (if `promisor.storeFields` is +set and the remote already exists locally). To allow Git to +automatically create and persist new remotes from server +advertisements, use `promisor.acceptFromServerUrl`. ++ +The available options are: ++ +* `none` (default): No promisor remote advertised by a server will be + accepted. ++ +* `knownUrl`: The client will accept promisor remotes that are already + configured on the client and have both the same name and the same URL + as advertised by the server. This is more secure than `all` or + `knownName`, and should be used if possible instead of those options. ++ +* `knownName`: The client will accept promisor remotes that are already + configured on the client and have the same name as those advertised + by the server. This is not very secure, but could be used in a corporate + setup where servers and clients are trusted to not switch names and URLs. ++ +* `all`: The client will accept all the promisor remotes a server might + advertise. This is the least secure option and should only be used in + fully trusted environments. ++ +Name and URL comparisons are case-sensitive. See linkgit:gitprotocol-v2[5] +for protocol details. + +promisor.acceptFromServerUrl:: + A glob pattern to specify which server-advertised URLs a + client is allowed to act on. When a URL matches, the client + will accept the advertised remote as a promisor remote, may + automatically create a new remote configuration for it and may + automatically accept field updates (such as authentication + tokens) from the server, even if `promisor.acceptFromServer` + is set to `none` (the default). ++ +This option can appear multiple times in config files. An advertised +URL will be accepted if it matches _ANY_ glob pattern specified by +this option in _ANY_ config file read by Git. ++ +When both `promisor.acceptFromServer` and `promisor.acceptFromServerUrl` +are set, `promisor.acceptFromServerUrl` is consulted first and takes +precedence: if a matching pattern leads to acceptance (either by +auto-configuring an unknown remote or by accepting field updates for +a known remote whose URL matches both the local configuration and the +allowlist), the advertised remote is accepted regardless of the +`promisor.acceptFromServer` setting. If no pattern in +`promisor.acceptFromServerUrl` triggers acceptance, the decision is +left to `promisor.acceptFromServer`. ++ +Note however that, even when an advertised URL matches a pattern in +`promisor.acceptFromServerUrl`, an already-existing remote on the +client whose name matches the advertised name but whose configured URL +differs from the advertised one will _NOT_ be accepted through +`promisor.acceptFromServerUrl`. This prevents a server from silently +re-pointing an existing client-side remote at a different URL. (Such a +remote may still be accepted through `promisor.acceptFromServer=all` +or `=knownName`, which have their own, looser semantics; see the +documentation of that option.) ++ +Be _VERY_ careful with these patterns: `*` matches any sequence of +characters within the 'host' and 'path' parts of a URL (but cannot +cross part boundaries). An overly broad pattern is a major security +risk, as a matching URL allows a server to auto-configure new remotes +and to update fields (such as authentication tokens) on known remotes +without further confirmation. To minimize security risks, follow these +guidelines: ++ +-- +1. Start with a secure protocol scheme, like `https://` or `ssh://`. ++ +2. Only allow domain names or paths where you control and trust _ALL_ + the content. Be especially careful with shared hosting platforms + like `github.com` or `gitlab.com`. A broad pattern like + `https://gitlab.com/*` is dangerous because it trusts every + repository on the entire platform. Always restrict such patterns to + your specific organization or namespace (e.g., + `https://gitlab.com/your-org/*`). ++ +3. Never use globs at the end of domain names. For example, + `https://cdn.your-org.com/*` might be safe, but + `https://cdn.your-org.com*/*` is a major security risk because + the latter matches `https://cdn.your-org.com.hacker.net/repo`. ++ +4. Be careful using globs at the beginning of domain names. While the + code ensures a `*` in the host cannot cross into the path, a + pattern like `https://*.example.com/*` will still match any + subdomain. This is extremely dangerous on shared hosting platforms + (e.g., `https://*.github.io/*` trusts every user's site on the + entire platform). +-- ++ +Before matching, both the advertised URL and the pattern are +normalized: the scheme and host are lowercased, percent-encoded +characters are decoded where possible, and path segments like `..` +are resolved. The port must also match exactly (e.g., +`https://example.com:8080/*` will not match a URL advertised on +port 9999). The username and password components of the URL are +ignored during matching. Note that embedding credentials in URLs is +discouraged. Passing authentication tokens via the `token` field of +the `promisor-remote` capability is strongly preferred. ++ +The glob pattern can optionally be prefixed with a remote name and an +equals sign (e.g., `cdn=https://cdn.example.com/*`). If such a prefix +is provided, accepted remotes will be saved under that name. If no +such prefix is provided, a safe remote name will be automatically +generated by sanitizing the URL and prefixing it with +`promisor-auto-`. ++ +If a remote with the chosen name already exists but points to a +different URL, Git will append a numeric suffix (e.g., `-1`, `-2`) to +the name to prevent overwriting existing configurations. You should +make sure that this doesn't happen often though, as remotes will be +rejected if the numeric suffix increases too much. In all cases, the +original name advertised by the server is recorded in the +`remote..advertisedAs` configuration variable for tracing and +debugging purposes. ++ +For the security implications of accepting a promisor remote, see the +documentation of `promisor.acceptFromServer`. For details on the +protocol, see linkgit:gitprotocol-v2[5]. promisor.checkFields:: A comma or space separated list of additional remote related @@ -63,11 +175,13 @@ If one of these field names (e.g., "token") is being checked for an 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/remote.adoc b/Documentation/config/remote.adoc index eb9c8a3c488448..3a20d0f7524e72 100644 --- a/Documentation/config/remote.adoc +++ b/Documentation/config/remote.adoc @@ -91,6 +91,15 @@ remote..promisor:: When set to true, this remote will be used to fetch promisor objects. +remote..advertisedAs:: + When a promisor remote is automatically configured using + information advertised by a server through the + `promisor-remote` protocol capability (see + `promisor.acceptFromServerUrl`), the server's originally + advertised name is saved in this variable. This is for + information, tracing and debugging purposes. Users should not + typically modify or create such configuration entries. + remote..partialclonefilter:: The filter that will be applied when fetching from this promisor remote. Changing or clearing this value will only affect fetches for new commits. @@ -157,15 +166,12 @@ Blank values signal to ignore all previous values, allowing a reset of the list from broader config scenarios. remote..followRemoteHEAD:: - How linkgit:git-fetch[1] should handle updates to `remotes//HEAD` - when fetching using the configured refspecs of a remote. - The default value is "create", which will create `remotes//HEAD` - if it exists on the remote, but not locally; this will not touch an - already existing local reference. Setting it to "warn" will print - a message if the remote has a different value than the local one; - in case there is no local reference, it behaves like "create". - A variant on "warn" is "warn-if-not-$branch", which behaves like - "warn", but if `HEAD` on the remote is `$branch` it will be silent. - Setting it to "always" will silently update `remotes//HEAD` to - the value on the remote. Finally, setting it to "never" will never - change or create the local reference. + When fetching this remote using its default refspec, this setting determines + how to handle differences between the remote's `HEAD` and the local + `remotes//HEAD` symbolic-ref. Overrides the value of + `fetch.followRemoteHEAD`. See `fetch.followRemoteHEAD` for a description of + accepted values. ++ +In addition to the values supported by `fetch.followRemoteHEAD`, this setting +may also take on the value "warn-if-not-`$branch`", which behaves like "warn", +but ignores the warning if the remote's `HEAD` is `remotes//$branch`. diff --git a/Documentation/config/safe.adoc b/Documentation/config/safe.adoc index 5b1690aebe8f58..99fbfc06188c52 100644 --- a/Documentation/config/safe.adoc +++ b/Documentation/config/safe.adoc @@ -2,12 +2,14 @@ safe.bareRepository:: Specifies which bare repositories Git will work with. The currently supported values are: + +-- * `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]). 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 diff --git a/Documentation/config/sideband.adoc b/Documentation/config/sideband.adoc index 96fade7f5fee39..f5b159e15fd834 100644 --- a/Documentation/config/sideband.adoc +++ b/Documentation/config/sideband.adoc @@ -9,11 +9,11 @@ sideband.allowControlCharacters:: `color`:: Allow ANSI color sequences, line feeds and horizontal tabs, but mask all other control characters. This is the default. - `cursor:`: + `cursor`:: Allow control sequences that move the cursor. This is disabled by default. `erase`:: - Allow control sequences that erase charactrs. This is + Allow control sequences that erase characters. This is disabled by default. `false`:: Mask all control characters other than line feeds and diff --git a/Documentation/date-formats.adoc b/Documentation/date-formats.adoc index e24517c496fce4..330424b2baccda 100644 --- a/Documentation/date-formats.adoc +++ b/Documentation/date-formats.adoc @@ -9,6 +9,11 @@ Git internal format:: `` is the number of seconds since the UNIX epoch. `` 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 `` 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-algorithm-option.adoc b/Documentation/diff-algorithm-option.adoc index 8e3a0b63d784d8..16e6fc7261d093 100644 --- a/Documentation/diff-algorithm-option.adoc +++ b/Documentation/diff-algorithm-option.adoc @@ -18,3 +18,6 @@ For instance, if you configured the `diff.algorithm` variable to a non-default value and want to use the default one, then you have to use `--diff-algorithm=default` option. ++ +Explicitly choosing a diff algorithm on the command line also +bypasses `diff..process` (see linkgit:gitattributes[5]). diff --git a/Documentation/diff-options.adoc b/Documentation/diff-options.adoc index 8a63b5e164114a..cf359d88b4ca53 100644 --- a/Documentation/diff-options.adoc +++ b/Documentation/diff-options.adoc @@ -457,6 +457,14 @@ endif::git-diff[] + 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=`:: Use __ to decide what a word is, instead of considering @@ -825,7 +833,20 @@ endif::git-format-patch[] to use this option with linkgit:git-log[1] and friends. `--no-ext-diff`:: - Disallow external diff drivers. + Disallow external diff drivers and processes, including + `diff..command` and `diff..process` + (see linkgit:gitattributes[5]). + +`--diff-process`:: +`--no-diff-process`:: + Allow (or forbid) consulting a diff process configured with + ++diff.++____++.process++ (see linkgit:gitattributes[5]), + leaving external diff drivers unaffected. `git diff`, `git log`, + `git show`, and `git blame` allow consulting by default; the + plumbing diff commands forbid it unless this option or + `--ext-diff` is given. linkgit:git-format-patch[1] accepts the + option but ignores it: a generated patch is always based on the + builtin diff. `--textconv`:: `--no-textconv`:: diff --git a/Documentation/fetch-options.adoc b/Documentation/fetch-options.adoc index 8074004377c1ed..035f780e583cee 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..skipFetchAll` configuration variable set. This overrides the configuration variable `fetch.all`. diff --git a/Documentation/git-blame.adoc b/Documentation/git-blame.adoc index 8808009e87eb1d..2b74e455997c8c 100644 --- a/Documentation/git-blame.adoc +++ b/Documentation/git-blame.adoc @@ -88,11 +88,12 @@ include::blame-options.adoc[] include::diff-algorithm-option.adoc[] `--abbrev=`:: - Instead of using the default _7+1_ hexadecimal digits as the - abbreviated object name, use _+1_ digits, where __ is at - least __ but ensures the commit object names are unique. - Note that 1 column - is used for a caret to mark the boundary commit. + Instead of using the default _7_ hexadecimal digits as the + abbreviated object name, use at least __ digits, but ensure + the commit object names are unique. + If commits marked with caret (boundary), question mark (ignored) + or asterisk (unblamable) are shown, extend unmarked object names + to align them. THE DEFAULT FORMAT diff --git a/Documentation/git-config.adoc b/Documentation/git-config.adoc index 00545b20542c60..57af010ade5b5b 100644 --- a/Documentation/git-config.adoc +++ b/Documentation/git-config.adoc @@ -119,10 +119,10 @@ OPTIONS Append a comment at the end of new or modified lines. + If __ 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 __ must not contain linefeed characters (no multi-line comments are permitted). @@ -221,6 +221,7 @@ Use `--no-value` to unset __. + Valid ``'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 @@ Valid ``'s include: 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 ` argument will apply to each listed config value. If the value does not successfully parse in that diff --git a/Documentation/git-diff-hunks.adoc b/Documentation/git-diff-hunks.adoc new file mode 100644 index 00000000000000..25cab2ea7db8d8 --- /dev/null +++ b/Documentation/git-diff-hunks.adoc @@ -0,0 +1,146 @@ +git-diff-hunks(1) +================= + +NAME +---- +git-diff-hunks - Inspect and manage the diff-hunks store + +SYNOPSIS +-------- +[synopsis] +git diff-hunks verify +git diff-hunks clear + +DESCRIPTION +----------- + +The diff-hunks store is a cache of diff hunk coordinates, the line +ranges that changed between two blobs, so that commands +which need them, such as linkgit:git-blame[1] and `git log` and `git diff` +with the `--stat`, `--numstat`, and `--shortstat` formats, can skip +running the diff algorithm, and blame can skip loading the blob +content. (The summary formats still test each pair for binariness, +which can load the blobs.) + +The store is a single file, `$GIT_DIR/objects/info/diff-hunks`. Reading is +enabled by default; writing is off by default. A `git diff`, `git log`, +`git show`, or `git diff-tree` that produces one of the stat formats +fills the store as a side effect, but only when writing is enabled for +that run (see "WARMING THE STORE" below), so ordinary reads never +modify the repository. When the store does not have the pair, holds a +different object hash, the file is unreadable, or an object replacement +redirects one of the blobs, the consumer falls back to computing the +diff. A store only speeds up these commands; it never changes their +output. + +`git diff-hunks` itself only inspects and manages the file. See +linkgit:gitformat-diff-hunks[5] for the file format. + +WARMING THE STORE +----------------- + +The store is filled by running ordinary commands with writing enabled. +Turn writing on for a single invocation with the `GIT_DIFF_HUNKS_WRITE` +environment variable, or persistently with the `diffHunks.write` +configuration; the environment variable takes precedence. A repository +owner warms the store by running the diff-producing commands they care +about with writing on, for example: + + GIT_DIFF_HUNKS_WRITE=1 git log --all --stat >/dev/null + +A `--stat` walk records one entry per blob pair; +linkgit:git-blame[1] replays the coordinates and the summary formats +sum the counts, so a single warming walk serves both. +A warming run seeds from the existing store and rewrites the file +with the newly computed pairs merged in, so a later run adds to what +earlier runs recorded rather than discarding it. + +A walk records only the pairs it diffs. `git log --all --stat` diffs +each commit against its first parent, so a blame that follows a +merge's second parent computes those pairs itself: blame coverage is +partial on history with merges. Warming with a walk that also diffs +the other parents, for example `git log --all -m --stat`, raises +blame coverage at the cost of a larger store and a longer warming +run. + +COMMANDS +-------- + +`verify`:: + Check the integrity of the store: the trailing hash checksum, the + chunk table of contents, the sort order of the index, and the + bounds of every entry. Exits with non-zero status if the store is + corrupt. An absent store is valid. + +`clear`:: + Remove the store file. + +CORRECTNESS +----------- + +A stored result is interchangeable with a freshly computed one because an +entry is keyed by the inputs that determine the diff: + +* the object IDs of the old and new blob, so a result is used only for + the exact contents it was computed from; and +* the diff algorithm and ignore flags (`xdl_opts`) the hunks were + computed under. A lookup whose `xdl_opts` differ from a stored entry + misses. This is why, for example, `blame -w` and + `--diff-algorithm=` (including a per-path + `diff..algorithm`) do not reuse entries recorded under the + default settings: they change `xdl_opts`. + +The context length is not part of the key because only trim-stable +pairs are recorded: pairs whose zero-context trimmed diff and untrimmed +diff are identical, so one entry answers blame (zero context) and the +summary formats (any context) alike. The rare pair where +the zero-context trimming optimization picks a different but +equally valid set of hunks is +never recorded and is always computed. + +Some options shape the hunks in ways the key does not express, so a +diff that uses them is excluded from the store in both directions: +break detection (`-B`), `--ignore-matching-lines` (`-I`), and +`--anchored`. `--ignore-blank-lines` is different: it is an ignore +flag and therefore part of the key, but the summary formats exclude +it anyway, because it coalesces hunks differently between the code +path that emits text and the one that replays coordinates, so a +served answer would not match a store-less run. +linkgit:git-blame[1] additionally does not +consult the store for reverse blame, ignored revisions, or paths with a +textconv driver. + +The store carries a trailing hash checksum, but readers do not +re-checksum it on every load. As with the commit-graph and +multi-pack-index, the writer fsyncs the file (honoring `core.fsync`) and +commits it atomically, so a committed store is intact; every offset and +count is still bounds-checked as it is read. The checksum is verified by +`git diff-hunks verify`, not on the read path, so structural corruption +that fails a bounds check is read as an absent entry, while a record +that stays within bounds but whose bytes were altered is served until +`verify` detects the mismatch. + +CONFIGURATION +------------- + +`core.diffHunks`:: + Whether commands read the store. Defaults to true. See + linkgit:git-config[1]. + +`diffHunks.write`:: + Whether diff-producing commands write to the store. Defaults to + false. The `GIT_DIFF_HUNKS_WRITE` environment variable overrides it + for a single invocation. See linkgit:git-config[1]. + +Writing the store honors the `core.fsync` configuration through the +`diff-hunks` component; see linkgit:git-config[1]. + +SEE ALSO +-------- +linkgit:git-blame[1], +linkgit:git-log[1], +linkgit:gitformat-diff-hunks[5] + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc index 297b57bb2efdc2..719aeca244d534 100644 --- a/Documentation/git-fast-export.adoc +++ b/Documentation/git-fast-export.adoc @@ -9,7 +9,7 @@ git-fast-export - Git data exporter SYNOPSIS -------- [verse] -'git fast-export' [] | 'git fast-import' +'git fast-export' [] [] [[--] ...] DESCRIPTION ----------- diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc index 5662382450289a..f7905c0f7c0322 100644 --- a/Documentation/git-format-patch.adoc +++ b/Documentation/git-format-patch.adoc @@ -221,10 +221,9 @@ populated with placeholder text. for generating the cover letter. --subject-prefix=:: - Instead of the standard '[PATCH]' prefix in the subject - line, instead use '[]'. This can be used - to name a patch series, and can be combined with the - `--numbered` option. + Use '[]' 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 index c40d52e9f6d108..505a52feccd466 100644 --- a/Documentation/git-format-rev.adoc +++ b/Documentation/git-format-rev.adoc @@ -33,7 +33,7 @@ OPTIONS The argument `rev` is also accepted. `text`;; Formats all commit object names found in freeform text. These - must the full object names, i.e. abbreviated hexidecimal object + 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 diff --git a/Documentation/git-history.adoc b/Documentation/git-history.adoc index 2ba812179533b8..28b477cd378150 100644 --- a/Documentation/git-history.adoc +++ b/Documentation/git-history.adoc @@ -8,6 +8,7 @@ git-history - EXPERIMENTAL: Rewrite history SYNOPSIS -------- [synopsis] +git history drop [--dry-run] [--update-refs=(branches|head)] [--empty=(drop|keep|abort)] git history fixup [--dry-run] [--update-refs=(branches|head)] [--reedit-message] [--empty=(drop|keep|abort)] git history reword [--dry-run] [--update-refs=(branches|head)] git history split [--dry-run] [--update-refs=(branches|head)] [--] [...] @@ -51,13 +52,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. +supported. Likewise, `drop` cannot remove the root commit or a merge commit. COMMANDS -------- The following commands are available to rewrite history in different ways: +`drop `:: + Remove the specified commit from the history. All descendants of the + commit are replayed directly onto its parent. ++ +The root commit cannot be dropped as that may lead to edge cases where refs +end up with no commits anymore. Merge commits cannot be dropped either; see +LIMITATIONS. ++ +If `HEAD` points at a commit that is to be rewritten, the index and working +tree are updated to match the new `HEAD`. The command aborts before any +references are updated in case local modifications would be overwritten. ++ +If replaying any descendant would result in a conflict, the command aborts +with an error. + `fixup `:: Apply the currently staged changes to the specified commit. This is similar in nature to `git commit --fixup=` followed by `git @@ -170,6 +186,26 @@ 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. +Drop a commit +~~~~~~~~~~~~~ + +---------- +$ git log --oneline +abc1234 (HEAD -> main) third +def5678 second +ghi9012 first + +$ git history drop 'main^{/second}' + +$ git log --oneline +jkl3456 (HEAD -> main) third +ghi9012 first +---------- + +The `second` commit has been removed from the history, and `third` has been +replayed directly on top of `first`. All branches that pointed at the dropped +commit have been moved to its parent. + Split a commit ~~~~~~~~~~~~~~ diff --git a/Documentation/git-pack-objects.adoc b/Documentation/git-pack-objects.adoc index 8a27aa19fd3f1f..65cd00c152f495 100644 --- a/Documentation/git-pack-objects.adoc +++ b/Documentation/git-pack-objects.adoc @@ -402,11 +402,13 @@ will be automatically changed to version `1`. of filenames that cause collisions in Git's default name-hash algorithm. + -Incompatible with `--delta-islands`. The `--use-bitmap-index` option is -ignored in the presence of `--path-walk`. The `--path-walk` option -supports the `--filter=` forms `blob:none`, `blob:limit=`, -`tree:0`, `object:type=`, and `sparse:`. These supported filter -types can be combined with the `combine:+` form. +When `--use-bitmap-index` is specified with `--path-walk`, a successful +bitmap traversal is used for object enumeration, with path-walk +remaining as the fallback traversal when the bitmap cannot satisfy the +request. The `--path-walk` option supports the `--filter=` forms +`blob:none`, `blob:limit=`, `tree:0`, `object:type=`, and +`sparse:`. These supported filter types can be combined with the +`combine:+` form. DELTA ISLANDS diff --git a/Documentation/git-refs.adoc b/Documentation/git-refs.adoc index fa33680cc781fe..ce278c59bfc1dc 100644 --- a/Documentation/git-refs.adoc +++ b/Documentation/git-refs.adoc @@ -20,6 +20,10 @@ git refs list [--count=] [--shell|--perl|--python|--tcl] [ --stdin | (...)] git refs exists git refs optimize [--all] [--no-prune] [--auto] [--include ] [--exclude ] +git refs create [--message=] [--no-deref] [--create-reflog] +git refs delete [--message=] [--no-deref] [] +git refs update [--message=] [--no-deref] [--create-reflog] [] +git refs rename [--message=] DESCRIPTION ----------- @@ -51,6 +55,28 @@ optimize:: usage. This subcommand is an alias for linkgit:git-pack-refs[1] and offers identical functionality. +create:: + Create the given reference, which must not already exist, pointing at + ``. + +delete:: + Delete the given reference. This subcommand mirrors `git update-ref -d` + (see linkgit:git-update-ref[1]). When `` is given, the + reference is only deleted after verifying that it currently contains + ``. + +update:: + Update the given reference to point at ``. If `` + is given, the reference is only updated after verifying that it + currently contains ``. As a special case, an all-zeroes + `` deletes the branch, whereas an all-zeroes `` + ensures that the branch does not yet exist. + +rename:: + Rename the reference `` to ``. The old reference must + exist and the new reference must not yet exist, and both must have a + well-formed name (see linkgit:git-check-ref-format[1]). + OPTIONS ------- @@ -90,6 +116,20 @@ The following options are specific to 'git refs optimize': include::pack-refs-options.adoc[] +The following options are specific to commands which write references: + +`--create-reflog`:: + Create a reflog for the reference even if one would not ordinarily be + created. + +`--message=`:: + Use the given string for the reflog entry associated with the + update. An empty message is rejected. + +`--no-deref`:: + Operate on itself rather than the reference it points to via a + symbolic ref. + KNOWN LIMITATIONS ----------------- diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc index 42262c198347e5..ed7d80c690c720 100644 --- a/Documentation/git-repo.adoc +++ b/Documentation/git-repo.adoc @@ -104,6 +104,21 @@ values that they return: `object.format`:: The object format (hash algorithm) used in the repository. +`path.commondir.absolute`:: + The canonical absolute path to the Git repository's common + directory (the shared `.git` directory containing objects, + refs, and global configuration). + +`path.commondir.relative`:: + The path to the Git repository's common directory relative to + the current working directory. + +`path.gitdir.absolute`:: + The canonical absolute path to the Git repository directory (the `.git` directory). + +`path.gitdir.relative`:: + The path to the Git repository directory relative to the current working directory. + `references.format`:: The reference storage format. The valid values are: + diff --git a/Documentation/git-sparse-checkout.adoc b/Documentation/git-sparse-checkout.adoc index 0d1618f161ed63..e286584c67f98f 100644 --- a/Documentation/git-sparse-checkout.adoc +++ b/Documentation/git-sparse-checkout.adoc @@ -134,7 +134,7 @@ the `clean.requireForce` config option is set to `false`. + 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/gitattributes.adoc b/Documentation/gitattributes.adoc index bd76167a45eb71..dd4fa0ad2d189a 100644 --- a/Documentation/gitattributes.adoc +++ b/Documentation/gitattributes.adoc @@ -784,6 +784,17 @@ with the above configuration, i.e. `j-c-diff`, with 7 parameters, just like `GIT_EXTERNAL_DIFF` program is called. See linkgit:git[1] for details. +An external diff driver replaces the patch Git would otherwise +produce for the path: Git runs the command and shows its output in +place of its own. Output features that post-process Git's diff do +not apply to the driver's output; word diff, function context (`-W`), +`--color-moved`, and coloring all act on Git's builtin diff, not the +driver's output. +The driver is consulted only when Git generates a textual patch. The +summary formats (`--stat`, `--numstat`, `--shortstat`, and +`--dirstat`), `git blame`, and `git log -L` do not run it and +continue to use Git's builtin diff. + If the program is able to ignore certain changes (similar to `git diff --ignore-space-change`), then also set the option `trustExitCode` to true. It is then expected to return exit code 1 if @@ -821,6 +832,166 @@ NOTE: If `diff..command` is defined for path with the (see above), and adding `diff..algorithm` has no effect, as the algorithm is not passed to the external diff driver. +Answering diffs from a long-running process +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Unlike `diff..command`, which replaces the textual patch, the +process configured in `diff..process` feeds hunks back into +Git's own machinery: +it answers "which line ranges changed between these two blobs", and +Git's output is produced from that answer. A process is started +lazily, once per configured command string and per repository, and +consulted over a pkt-line protocol (following the long-running filter +process protocol; see the "Long Running Filter Process" section above +for the filter analogue). + +The process is asked by object names alone: a request carries the +pathname and the `old-oid`/`new-oid` of the blob pair, and no content. +The pathname is the repository-relative old-side (preimage) path, not +the shortened display path a `--relative` diff shows, so a driver +scoped to a directory matches whatever directory the command runs from. +This suits a process that keeps a persistent cache keyed on the pair, and +a process that fetches the blobs itself (for example via +`git cat-file --batch`) to compute its own notion of the changed +lines. Pairs with a side that is not a stored blob (a working-tree +file, textconv output) are not sent; Git computes those itself. + +The exchange opens with a handshake: Git announces its role and +version, the process replies in kind, and then Git lists the +capabilities it supports and the process replies with the ones it +implements. A process that announces a capability Git did not list +aborts the command. + +----------------------- +packet: git> git-diff-client +packet: git> version=1 +packet: git> 0000 +packet: git< git-diff-server +packet: git< version=1 +packet: git< 0000 +packet: git> capability=hunks-by-oid +packet: git> 0000 +packet: git< capability=hunks-by-oid +packet: git< 0000 +----------------------- + +After the handshake, each request and response looks like: + +----------------------- +packet: git> command=hunks-by-oid +packet: git> pathname=path/file.c +packet: git> old-oid= +packet: git> new-oid= +packet: git> 0000 +packet: git< hunk +packet: git< 0000 +packet: git< status=success +packet: git< 0000 +----------------------- + +Start values are 1-based and counts are non-negative; a count of 0 +describes a pure insertion or deletion at the 1-based line the change +sits before (a start of 0 is accepted for an empty file side). Hunks +must be listed in order, must not overlap, and must keep the unchanged +runs between them the same length on both sides; Git validates this +and, with a warning, falls back to its builtin diff on a response +that violates these rules. + +A `status=success` response with zero hunks asserts that the blobs are +equivalent, including their trailing newlines. A process that cannot +answer a pair from its object names (or cannot rule out a +trailing-newline-only difference) responds `status=need-content`, and +Git produces that pair's diff itself. An asserted equivalence makes +the pair vanish from the summary formats, but the pair still counts +as changed for `--exit-code`, the same way a whitespace-only pair +does under `-w`. + +The status names the disposition of the whole request. Git +understands three: `success` (the hunk lines are the answer), +`need-content` (Git produces this pair's diff itself), and `abort`, which +withdraws the capability the request used: Git stops sending +`hunks-by-oid` requests to that process for the rest of the command, +while the process stays alive for request forms negotiated under +other capabilities. Any other status is a protocol error: Git warns, +stops the process, and uses the builtin diff for the remainder of the +command. + +Every response has the same shape whatever its status: zero or more +hunk lines, a flush packet, and a status packet terminated with a +flush packet. A response that carries no hunks, `need-content` +included, still begins with the empty hunk section's flush packet; a +bare status packet is a protocol error: + +----------------------- +packet: git< 0000 +packet: git< status=need-content +packet: git< 0000 +----------------------- + +The process must read the entire request before it responds; Git +writes the whole request before it reads the response. + +The protocol extends without breaking deployed processes: a process +must ignore request keys it does not recognize, and Git ignores +trailing space-separated tokens after the last field of a hunk line, +so a later protocol version can append request keys and hunk fields. +New request forms arrive as capabilities, which a process may decline +to announce; announcing a capability Git did not request aborts the +command, as it does under the long-running filter process protocol. + +There is no shutdown handshake: Git's side of the pipes closes when +the command exits, and the process should exit when it reads EOF. No +flush point is guaranteed, so a process that maintains persistent +state (such as a cache) should persist as it answers rather than at +exit. Git applies no timeout to a response; a process that hangs +hangs the command, as with the long-running filter processes. + +`git blame` and the `--stat`, `--numstat`, and `--shortstat` formats +consult the process; the textual patch and `git log -L` range +tracking are produced by the builtin machinery, so a process whose +answers deliberately differ from the builtin diff shows that +difference only in blame and those formats. `--dirstat=lines` routes +through the diffstat path and consults; the other `--dirstat` modes do +not. A merge's `--stat` (including under `--cc`) is computed against +the first parent, so it consults like any other stat; the combined +patch itself compares one merge result against all of its parents at +once, which the pairwise request above does not express, so that patch +uses the builtin diff, and extending the protocol to combined diffs is +left for future work. A content-carrying extension of this +protocol would bring patch output and `git log -L` range tracking to +the same answer. + +Consulting is allowed per command, as with textconv: `git diff`, +`git log` (`git whatchanged` included) and `git show`, and +`git blame` consult a configured process; the plumbing diff commands +do not unless `--ext-diff` or `--diff-process` is given explicitly, +and the interactive-patch commands (`git add -p` and friends), which +build the hunks they present from plumbing output, always stage from +the builtin diff. `git range-diff` generates the patches it +compares with `--no-ext-diff`. `--diff-process` and +`--no-diff-process` allow or forbid only the consulting; +`--no-ext-diff` disables all external diff mechanisms, this one +included. Options the process is never told about never select it: +with the whitespace-ignoring options, `--ignore-matching-lines`, and +`--anchored`, the pair is answered as when no process is configured. +`--diff-algorithm` (or a configured `diff.algorithm`) forces a builtin +algorithm and bypasses the process the same way. A per-path +++diff.++____++.algorithm++ does so for `git diff` and the stat +formats, which build their diff parameters from it; `git blame` builds +its parameters from its own diff options, so a per-driver algorithm +does not by itself keep blame from consulting the process. +`git format-patch` never +consults the process, so generated patches are always based on the +builtin diff and apply for recipients without the process. On a +path whose driver has a process, the process is consulted before the +diff-hunks store (see linkgit:git-diff-hunks[1]): a pair the process +answers is never served from the store and never recorded into it. +A pair the process does not answer, for example with +`status=need-content`, gets the builtin diff, so the store may serve +it and a warming run may record it: the store holds builtin results, +and for such a pair the builtin result is what would be computed +anyway. + Defining a custom hunk-header ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/Documentation/gitformat-diff-hunks.adoc b/Documentation/gitformat-diff-hunks.adoc new file mode 100644 index 00000000000000..f75ab73dd94a75 --- /dev/null +++ b/Documentation/gitformat-diff-hunks.adoc @@ -0,0 +1,129 @@ +gitformat-diff-hunks(5) +======================= + +NAME +---- +gitformat-diff-hunks - Precomputed diff hunk store format + +SYNOPSIS +-------- +[verse] +$GIT_DIR/objects/info/diff-hunks + +DESCRIPTION +----------- + +The diff-hunks store memoizes diff hunk coordinates so that commands +that need them, such as `git log --stat` and linkgit:git-blame[1], can +skip running the diff algorithm (and, for blame, loading the blob +content; the summary formats still test each pair for binariness, +which can load the blobs). See +linkgit:git-diff-hunks[1] for how the store is filled and managed and the +configuration that controls it. + +The store is a single file, `$GIT_DIR/objects/info/diff-hunks`, written +in one pass and replaced atomically, so a reader sees either the old +file or the complete new one. + +Entries are keyed by the object IDs of the blob pair that was diffed +and by the diff algorithm and ignore flags (`xdl_opts`) the pair was +diffed under. A blob pair fully determines the diff input, so an entry +is valid regardless of which commits, branches, or index states the +pair was encountered in, and identical diffs performed in different +contexts share one entry. A reader whose `xdl_opts` differ from an +entry does not match it and falls back to computing the diff. + +FILE FORMAT +----------- + +All multi-byte integers are stored in network byte order. The file is an +8-byte header, the chunk table of contents and chunk data described in +linkgit:gitformat-chunk[5], and a trailing checksum. + +HEADER +~~~~~~ + +- 4-byte signature: `DHPF` (diff-hunks precomputed format) +- 1-byte version number: currently 1 +- 1-byte hash version: 1 for SHA-1, 2 for SHA-256. A store whose hash + function differs from the repository's is ignored. +- 1-byte number of chunks +- 1-byte reserved + +CHUNK LOOKUP +~~~~~~~~~~~~ + +A table of contents in the format of linkgit:gitformat-chunk[5], listing +the offset of each chunk. Both chunks below are required; a file missing +either is treated as corrupt. + +CHUNK DATA +~~~~~~~~~~ + +DHIX (index):: + A sorted sequence of fixed-size entries. Each entry is the old + blob object ID, the new blob object ID, a 4-byte `xdl_opts` + value, and a 4-byte offset into the DHDT chunk. Entries are + sorted by old object ID, then new object ID, then `xdl_opts`, + so lookups can use binary search on the full key. + +DHDT (hunk data):: + For each index entry, at its offset: a 4-byte hunk count followed + by that many 16-byte hunk records. A hunk record is four 4-byte + values: old start, old count, new start, new count. + Starts are 0-based line numbers in the old and new blob; counts + are numbers of lines. The hunk count is at least 1: a record with + no hunks would claim the blob pair equivalent, which the store + never records, so readers treat such a record as invalid. + Identical hunk blocks are stored once: + distinct index entries whose recorded hunks are byte-for-byte + equal point at the same offset. + +TRAILER +~~~~~~~ + +A checksum of all preceding bytes, computed with the repository hash +function. + +CORRECTNESS +----------- + +Serving hunks from a valid store produces the same output as recomputing +the diff. The diff of a blob pair is not unique: a zero context length +triggers xdiff's common-tail trimming, which can pick a different but +equally valid set of hunks than an untrimmed diff does. A pair is +therefore recorded only when its trimmed and untrimmed diffs are +identical, which is the common case. Such an entry answers any consumer +at any context: git-blame replays its coordinates directly (it diffs at +zero context), and diffstat sums its per-hunk line counts, which the +context length does not change. The rare pair whose two diffs differ is +never recorded, so every consumer computes it. + +A store that cannot be used is ignored, and the consumer falls back to +computing the diff. Every offset and count read from the file is +bounds-checked, so a store that is missing, truncated, of an unknown +version, or of a different object hash does not change the diff output +and does not produce a diagnostic; `git diff-hunks verify` is what +reports corruption. + +The store is not re-checksummed on the read path. The writer fsyncs the +file (honoring `core.fsync`) and commits it atomically, so a +committed store is intact, the same trust model the commit-graph and +multi-pack-index use. The trailing checksum is recomputed by +`git diff-hunks verify` to detect corruption. + +The checksum detects corruption but does not prove who wrote the file. A +reader trusts the coordinates in a store that passes its checks, so +anything able to write a checksum-valid file at the store path can +influence output, the same as it could by writing objects directly. + +LIMITATIONS +----------- + +- Hunk counts, offsets, and line coordinates are 32-bit, capping the + hunk data at 4 GiB and a single entry at roughly 268 million hunks. + A result whose coordinates cannot be represented is not recorded. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc index befa697d21c281..2beb70595fc1e5 100644 --- a/Documentation/gitprotocol-v2.adoc +++ b/Documentation/gitprotocol-v2.adoc @@ -866,10 +866,11 @@ the server advertised, the client shouldn't advertise the On the server side, the "promisor.advertise" and "promisor.sendFields" configuration options can be used to control what it advertises. On -the client side, the "promisor.acceptFromServer" configuration option -can be used to control what it accepts, and the "promisor.storeFields" -option, to control what it stores. See the documentation of these -configuration options in linkgit:git-config[1] for more information. +the client side, the "promisor.acceptFromServer" and +"promisor.acceptFromServerUrl" configuration options can be used to +control what it accepts, and the "promisor.storeFields" option, to +control what it stores. See the documentation of these configuration +options in linkgit:git-config[1] for more information. Note that in the future it would be nice if the "promisor-remote" protocol capability could be used by the server, when responding to diff --git a/Documentation/line-range-options.adoc b/Documentation/line-range-options.adoc index 72f639b5e79ea4..d619ffa6336513 100644 --- a/Documentation/line-range-options.adoc +++ b/Documentation/line-range-options.adoc @@ -9,13 +9,20 @@ __ and __ (or __) must exist in the starting revision. You can specify this option more than once. Implies `--patch`. 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. + The following non-patch diff formats are supported: `--raw`, + `--name-only`, `--name-status`, `--summary`, `--check`, + `--stat`, `--numstat`, and `--shortstat`. + The stat formats count only lines within the tracked range. + `--dirstat` is not supported + with `-L`: it summarizes change as each directory's share of + the total churn, not as counts for the tracked lines. Use + `--numstat` for exact per-file counts within the range. + Patch formatting options such as `--word-diff`, `--color-moved`, `--no-prefix`, and whitespace options (`-w`, `-b`) are supported, -as are pickaxe options (`-S`, `-G`) and `--diff-filter`. +as are pickaxe options (`-S`, `-G`) and `--diff-filter`. `-G` is +scoped to the tracked range; `-S` is still evaluated over the whole +file, so an `-S` query may select a commit for a change outside the +range. + include::line-range-format.adoc[] diff --git a/Documentation/meson.build b/Documentation/meson.build index f4854f802d455f..85f37da47e9cac 100644 --- a/Documentation/meson.build +++ b/Documentation/meson.build @@ -41,6 +41,7 @@ manpages = { 'git-describe.adoc' : 1, 'git-diagnose.adoc' : 1, 'git-diff-files.adoc' : 1, + 'git-diff-hunks.adoc' : 1, 'git-diff-index.adoc' : 1, 'git-diff-pairs.adoc' : 1, 'git-difftool.adoc' : 1, @@ -175,6 +176,7 @@ manpages = { 'gitformat-bundle.adoc' : 5, 'gitformat-chunk.adoc' : 5, 'gitformat-commit-graph.adoc' : 5, + 'gitformat-diff-hunks.adoc' : 5, 'gitformat-index.adoc' : 5, 'gitformat-loose.adoc' : 5, 'gitformat-pack.adoc' : 5, diff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc index 9e666b9f10bc25..eaee6ee8399c57 100644 --- a/Documentation/rev-list-options.adoc +++ b/Documentation/rev-list-options.adoc @@ -16,7 +16,10 @@ ordering and formatting options, such as `--reverse`. `-`:: `-n `:: `--max-count=`:: - Limit the output to __ commits. + Limit the output to the first __ commits that would be shown. + +`--max-count-oldest=`:: + Limit the output to the last __ commits that would be shown. `--skip=`:: Skip __ commits before starting to show the commit output. diff --git a/Documentation/technical/build-systems.adoc b/Documentation/technical/build-systems.adoc index 3c5237b9fd4727..ca5b5d96f149ba 100644 --- a/Documentation/technical/build-systems.adoc +++ b/Documentation/technical/build-systems.adoc @@ -47,7 +47,7 @@ Auto-detection of the following items is considered to be important: - 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 @@ by the build system: - 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 @@ The following list of build systems are considered: === 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 2359d7d106f842..241d2f763dd436 100644 --- a/Documentation/technical/hash-function-transition.adoc +++ b/Documentation/technical/hash-function-transition.adoc @@ -545,7 +545,7 @@ Alternates ~~~~~~~~~~ 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 fd6979b70e6ac3..a72f090fe2b8d2 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,6 +1,6 @@ #!/bin/sh -DEF_VER=v2.54.0 +DEF_VER=v2.55.GIT LF=' ' diff --git a/Makefile b/Makefile index b31ecb07564a73..853e3cdacd5731 100644 --- a/Makefile +++ b/Makefile @@ -500,6 +500,14 @@ include shared.mak # # Building Rust code requires Cargo. # +# Define RUST_TARGETS if you want to cross-compile. If left unspecified, it uses +# the default Rust target on the system. +# +# On macOS, this supports specifying multiple targets, separated by a space. +# This will produce a Universal static library using `lipo`. +# +# Example: RUST_TARGETS="aarch64-apple-darwin x86_64-apple-darwin" +# # == SHA-1 and SHA-256 defines == # # === SHA-1 backend === @@ -811,6 +819,7 @@ TEST_BUILTINS_OBJS += test-csprng.o TEST_BUILTINS_OBJS += test-date.o TEST_BUILTINS_OBJS += test-delete-gpgsig.o TEST_BUILTINS_OBJS += test-delta.o +TEST_BUILTINS_OBJS += test-diff-process-backend.o TEST_BUILTINS_OBJS += test-dir-iterator.o TEST_BUILTINS_OBJS += test-drop-caches.o TEST_BUILTINS_OBJS += test-dump-cache-tree.o @@ -939,16 +948,19 @@ TEST_SHELL_PATH = $(SHELL_PATH) LIB_FILE = libgit.a +ifndef NO_RUST ifdef DEBUG -RUST_TARGET_DIR = target/debug +RUST_BUILD_CONFIG = debug else -RUST_TARGET_DIR = target/release +RUST_BUILD_CONFIG = release endif ifeq ($(uname_S),Windows) -RUST_LIB = $(RUST_TARGET_DIR)/gitcore.lib +RUST_LIB_NAME = gitcore.lib else -RUST_LIB = $(RUST_TARGET_DIR)/libgitcore.a +RUST_LIB_NAME = libgitcore.a +endif +RUST_LIB = target/$(RUST_BUILD_CONFIG)/$(RUST_LIB_NAME) endif GITLIBS = common-main.o $(LIB_FILE) @@ -1140,6 +1152,8 @@ LIB_OBJS += diff-delta.o LIB_OBJS += diff-merges.o LIB_OBJS += diff-lib.o LIB_OBJS += diff-no-index.o +LIB_OBJS += diff-process.o +LIB_OBJS += diff-provider.o LIB_OBJS += diff.o LIB_OBJS += diffcore-break.o LIB_OBJS += diffcore-delta.o @@ -1147,6 +1161,7 @@ LIB_OBJS += diffcore-order.o LIB_OBJS += diffcore-pickaxe.o LIB_OBJS += diffcore-rename.o LIB_OBJS += diffcore-rotate.o +LIB_OBJS += diff-hunks.o LIB_OBJS += dir-iterator.o LIB_OBJS += dir.o LIB_OBJS += editor.o @@ -1217,6 +1232,8 @@ 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/source-packed.o LIB_OBJS += odb/streaming.o LIB_OBJS += odb/transaction.o LIB_OBJS += oid-array.o @@ -1232,6 +1249,7 @@ LIB_OBJS += pack-refs.o LIB_OBJS += pack-revindex.o LIB_OBJS += pack-write.o LIB_OBJS += packfile.o +LIB_OBJS += packfile-list.o LIB_OBJS += pager.o LIB_OBJS += parallel-checkout.o LIB_OBJS += parse.o @@ -1406,6 +1424,7 @@ BUILTIN_OBJS += builtin/credential.o BUILTIN_OBJS += builtin/describe.o BUILTIN_OBJS += builtin/diagnose.o BUILTIN_OBJS += builtin/diff-files.o +BUILTIN_OBJS += builtin/diff-hunks.o BUILTIN_OBJS += builtin/diff-index.o BUILTIN_OBJS += builtin/diff-pairs.o BUILTIN_OBJS += builtin/diff-tree.o @@ -2598,6 +2617,7 @@ FUZZ_OBJS += oss-fuzz/fuzz-date.o FUZZ_OBJS += oss-fuzz/fuzz-pack-headers.o FUZZ_OBJS += oss-fuzz/fuzz-pack-idx.o FUZZ_OBJS += oss-fuzz/fuzz-parse-attr-line.o +FUZZ_OBJS += oss-fuzz/fuzz-reftable.o FUZZ_OBJS += oss-fuzz/fuzz-url-decode-mem.o .PHONY: fuzz-objs fuzz-objs: $(FUZZ_OBJS) @@ -3016,11 +3036,35 @@ scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS) $(LIB_FILE): $(LIB_OBJS) $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^ +ifndef NO_RUST +ifeq ($(RUST_TARGETS),) $(RUST_LIB): Cargo.toml $(RUST_SOURCES) $(LIB_FILE) $(QUIET_CARGO)cargo build $(CARGO_ARGS) +else +ifneq ($(words $(RUST_TARGETS)),1) +ifneq ($(uname_S),Darwin) +$(error Building universal Rust libraries requires macOS (lipo is not available on $(uname_S))) +endif +endif + +RUST_MEMBER_LIBS = $(foreach target,$(RUST_TARGETS),target/$(target)/$(RUST_BUILD_CONFIG)/$(RUST_LIB_NAME)) +$(RUST_MEMBER_LIBS): target/%/$(RUST_BUILD_CONFIG)/$(RUST_LIB_NAME): Cargo.toml $(RUST_SOURCES) $(LIB_FILE) + $(QUIET_CARGO)cargo build $(CARGO_ARGS) --target $* + +$(RUST_LIB): $(RUST_MEMBER_LIBS) + $(call mkdir_p_parent_template) + $(QUIET_GEN)\ + if test $(words $(RUST_TARGETS)) -gt 1; \ + then \ + lipo -create $^ -output $@; \ + else \ + cp $< $@; \ + fi +endif .PHONY: rust rust: $(RUST_LIB) +endif export DEFAULT_EDITOR DEFAULT_PAGER @@ -4071,9 +4115,10 @@ $(LIBGIT_HIDDEN_EXPORT): $(LIBGIT_PARTIAL_EXPORT) contrib/libgit-sys/libgitpub.a: $(LIBGIT_HIDDEN_EXPORT) $(AR) $(ARFLAGS) $@ $^ -contrib/credential/osxkeychain/git-credential-osxkeychain: contrib/credential/osxkeychain/git-credential-osxkeychain.o $(LIB_FILE) GIT-LDFLAGS +# When Rust is enabled, git-credential-osxkeychain depends on Rust symbols in $(RUST_LIB) +contrib/credential/osxkeychain/git-credential-osxkeychain: contrib/credential/osxkeychain/git-credential-osxkeychain.o $(LIB_FILE) $(RUST_LIB) 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/README.md b/README.md index d87bca1b8c3ebf..46489b0971d04d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -[![Build status](https://github.com/git/git/workflows/CI/badge.svg)](https://github.com/git/git/actions?query=branch%3Amaster+event%3Apush) +[![GitHub build status](https://github.com/git/git/workflows/CI/badge.svg)](https://github.com/git/git/actions?query=branch%3Amaster+event%3Apush) +[![GitLab build status](https://gitlab.com/git-scm/git/badges/master/pipeline.svg)](https://gitlab.com/git-scm/git/-/pipelines?ref=master) Git - fast, scalable, distributed revision control system ========================================================= diff --git a/RelNotes b/RelNotes index 159e44a9490cc3..752580e69384ba 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes/2.55.0.adoc \ No newline at end of file +Documentation/RelNotes/2.56.0.adoc \ No newline at end of file diff --git a/advice.c b/advice.c index 0018501b7bc103..63bf8b0c5f0481 100644 --- a/advice.c +++ b/advice.c @@ -69,6 +69,7 @@ static struct { [ADVICE_PUSH_NON_FF_CURRENT] = { "pushNonFFCurrent" }, [ADVICE_PUSH_NON_FF_MATCHING] = { "pushNonFFMatching" }, [ADVICE_PUSH_REF_NEEDS_UPDATE] = { "pushRefNeedsUpdate" }, + [ADVICE_PUSH_REPO_LOOKS_LIKE_REF] = { "pushRepoLooksLikeRef" }, [ADVICE_PUSH_UNQUALIFIED_REF_NAME] = { "pushUnqualifiedRefName" }, [ADVICE_PUSH_UPDATE_REJECTED] = { "pushUpdateRejected" }, [ADVICE_PUSH_UPDATE_REJECTED_ALIAS] = { "pushNonFastForward" }, /* backwards compatibility */ diff --git a/advice.h b/advice.h index 8def28068861df..66f6cd6a772d8c 100644 --- a/advice.h +++ b/advice.h @@ -36,6 +36,7 @@ enum advice_type { ADVICE_PUSH_NON_FF_CURRENT, ADVICE_PUSH_NON_FF_MATCHING, ADVICE_PUSH_REF_NEEDS_UPDATE, + ADVICE_PUSH_REPO_LOOKS_LIKE_REF, ADVICE_PUSH_UNQUALIFIED_REF_NAME, ADVICE_PUSH_UPDATE_REJECTED, ADVICE_PUSH_UPDATE_REJECTED_ALIAS, diff --git a/apply.c b/apply.c index 249248d4f205ca..5e87ba772e8531 100644 --- a/apply.c +++ b/apply.c @@ -3232,7 +3232,7 @@ static int apply_binary_fragment(struct apply_state *state, struct patch *patch) { struct fragment *fragment = patch->fragments; - unsigned long len; + size_t len; void *dst; if (!fragment) @@ -3321,7 +3321,7 @@ static int apply_binary(struct apply_state *state, if (odb_has_object(the_repository->objects, &oid, 0)) { /* We already have the postimage */ enum object_type type; - unsigned long size; + size_t size; char *result; result = odb_read_object(the_repository->objects, &oid, @@ -3384,7 +3384,7 @@ static int read_blob_object(struct strbuf *buf, const struct object_id *oid, uns strbuf_addf(buf, "Subproject commit %s\n", oid_to_hex(oid)); } else { enum object_type type; - unsigned long sz; + size_t sz; char *result; result = odb_read_object(the_repository->objects, oid, @@ -3611,7 +3611,7 @@ static int load_preimage(struct apply_state *state, static int resolve_to(struct image *image, const struct object_id *result_id) { - unsigned long size; + size_t size; enum object_type type; char *data; @@ -4008,7 +4008,7 @@ static int path_is_beyond_symlink_1(struct apply_state *state, struct strbuf *na struct cache_entry *ce; ce = index_file_exists(state->repo->index, name->buf, - name->len, ignore_case); + name->len, repo_ignore_case(the_repository)); if (ce && S_ISLNK(ce->ce_mode)) return 1; } else { diff --git a/archive.c b/archive.c index 51229107a57495..59790be98697c6 100644 --- a/archive.c +++ b/archive.c @@ -87,7 +87,7 @@ static void *object_file_to_archive(const struct archiver_args *args, const struct object_id *oid, unsigned int mode, enum object_type *type, - unsigned long *sizep) + size_t *sizep) { void *buffer; const struct commit *commit = args->convert ? args->commit : NULL; @@ -158,7 +158,7 @@ static int write_archive_entry(const struct object_id *oid, const char *base, write_archive_entry_fn_t write_entry = c->write_entry; int err; const char *path_without_prefix; - unsigned long size; + size_t size; void *buffer; enum object_type type; diff --git a/attr.c b/attr.c index 75369547b306d6..0e63f1b6de8f53 100644 --- a/attr.c +++ b/attr.c @@ -681,7 +681,7 @@ static enum git_attr_direction direction; void git_attr_set_direction(enum git_attr_direction new_direction) { - if (is_bare_repository() && new_direction != GIT_ATTR_INDEX) + if (is_bare_repository(the_repository) && new_direction != GIT_ATTR_INDEX) BUG("non-INDEX attr direction in a bare repo"); if (new_direction != direction) @@ -768,7 +768,7 @@ static struct attr_stack *read_attr_from_blob(struct index_state *istate, const char *path, unsigned flags) { struct object_id oid; - unsigned long sz; + size_t sz; enum object_type type; void *buf; unsigned short mode; @@ -848,7 +848,7 @@ static struct attr_stack *read_attr(struct index_state *istate, res = read_attr_from_index(istate, path, flags); } else if (tree_oid) { res = read_attr_from_blob(istate, tree_oid, path, flags); - } else if (!is_bare_repository()) { + } else if (!is_bare_repository(the_repository)) { if (direction == GIT_ATTR_CHECKOUT) { res = read_attr_from_index(istate, path, flags); if (!res) diff --git a/bisect.c b/bisect.c index e29d1cbc64dc44..94c7028d2a746a 100644 --- a/bisect.c +++ b/bisect.c @@ -154,7 +154,7 @@ static void show_list(const char *debug, int counted, int nr, struct commit *commit = p->item; unsigned commit_flags = commit->object.flags; enum object_type type; - unsigned long size; + size_t size; char *buf = odb_read_object(the_repository->objects, &commit->object.oid, &type, &size); diff --git a/blame.c b/blame.c index 977cbb70974f8c..3189bcdabaa886 100644 --- a/blame.c +++ b/blame.c @@ -23,6 +23,8 @@ #include "commit-slab.h" #include "bloom.h" #include "commit-graph.h" +#include "diff-provider.h" +#include "userdiff.h" define_commit_slab(blame_suspects, struct blame_origin *); static struct blame_suspects blame_suspects; @@ -1041,10 +1043,13 @@ static void fill_origin_blob(struct diff_options *opt, textconv_object(opt->repo, o->path, o->mode, &o->blob_oid, 1, &file->ptr, &file_size)) ; - else + else { + size_t file_size_st = 0; file->ptr = odb_read_object(the_repository->objects, &o->blob_oid, &type, - &file_size); + &file_size_st); + file_size = cast_size_t_to_ulong(file_size_st); + } file->size = file_size; if (!file->ptr) @@ -1933,6 +1938,46 @@ static int blame_chunk_cb(long start_a, long count_a, return 0; } +/* + * A hunk provider's key names the (old blob, new blob) pair and may only + * serve a diff whose result is determined by that pair and the xdiff + * settings. Textconv rewrites the buffers being diffed away from the + * blob contents the key names, so any origin whose path has a textconv + * driver must withhold the pair's identity. + */ +static int blame_textconv_active(struct blame_scoreboard *sb, + const char *path) +{ + struct userdiff_driver *drv; + + if (!sb->revs->diffopt.flags.allow_textconv) + return 0; + drv = userdiff_find_by_path(sb->repo->index, path); + return drv && drv->textconv; +} + +struct blame_diff_fill_data { + struct blame_scoreboard *sb; + struct blame_origin *parent, *target; + int ignore_diffs; +}; + +/* + * Content load for diff_provider_emit_hunks(): runs when the diff is + * computed. + */ +static int blame_diff_fill(void *data, mmfile_t *old_file, mmfile_t *new_file) +{ + struct blame_diff_fill_data *f = data; + + fill_origin_blob(&f->sb->revs->diffopt, f->parent, old_file, + &f->sb->num_read_blob, f->ignore_diffs); + fill_origin_blob(&f->sb->revs->diffopt, f->target, new_file, + &f->sb->num_read_blob, f->ignore_diffs); + f->sb->num_get_patch++; + return 0; +} + /* * We are looking at the origin 'target' and aiming to pass blame * for the lines it is suspected to its parent. Run diff to find @@ -1942,9 +1987,12 @@ static void pass_blame_to_parent(struct blame_scoreboard *sb, struct blame_origin *target, struct blame_origin *parent, int ignore_diffs) { - mmfile_t file_p, file_o; struct blame_chunk_cb_data d; struct blame_entry *newdest = NULL; + struct blame_diff_fill_data fill_data = { sb, parent, target, ignore_diffs }; + xpparam_t xpp = { .flags = sb->xdl_opts }; + struct diff_provider_request req = { .repo = sb->repo, .xpp = &xpp }; + int provider_usable; if (!target->suspects) return; /* nothing remains for this target */ @@ -1955,13 +2003,35 @@ static void pass_blame_to_parent(struct blame_scoreboard *sb, d.ignore_diffs = ignore_diffs; d.dstq = &newdest; d.srcq = &target->suspects; - fill_origin_blob(&sb->revs->diffopt, parent, &file_p, - &sb->num_read_blob, ignore_diffs); - fill_origin_blob(&sb->revs->diffopt, target, &file_o, - &sb->num_read_blob, ignore_diffs); - sb->num_get_patch++; + /* + * Offer the pair's identity only where blame's diff is the plain + * blob-pair diff the recording key describes; reverse blame, + * ignored revisions, and textconv paths withhold it and always + * compute. The working-tree/--contents pseudo-commit (marked by + * its null commit id) holds a blob that is not a stored object, + * so its pairs withhold identity too: no id may be sent that + * names bytes a process cannot look up. + */ + provider_usable = !sb->reverse && !ignore_diffs && + !is_null_oid(&target->commit->object.oid) && + !blame_textconv_active(sb, target->path) && + !blame_textconv_active(sb, parent->path); - if (diff_hunks(&file_p, &file_o, blame_chunk_cb, &d, sb->xdl_opts)) + /* + * Look up the driver by the parent (old) path, as builtin_diff() + * does with name_a, so a renamed file resolves to the same driver + * across diff and blame. A process that reports a pair + * equivalent emits no hunks, so blame passes the whole commit + * through and looks past it. + */ + if (provider_usable) { + req.old_oid = &parent->blob_oid; + req.new_oid = &target->blob_oid; + } + req.path = parent->path; + req.diffopt = &sb->revs->diffopt; + if (diff_provider_emit_hunks(&req, blame_diff_fill, &fill_data, + blame_chunk_cb, &d) == DIFF_PROVIDER_ERROR) die("unable to generate diff (%s -> %s)", oid_to_hex(&parent->commit->object.oid), oid_to_hex(&target->commit->object.oid)); @@ -2869,10 +2939,14 @@ void setup_scoreboard(struct blame_scoreboard *sb, textconv_object(sb->repo, sb->path, o->mode, &o->blob_oid, 1, (char **) &sb->final_buf, &sb->final_buf_size)) ; - else + else { + size_t final_buf_size_st = 0; sb->final_buf = odb_read_object(the_repository->objects, &o->blob_oid, &type, - &sb->final_buf_size); + &final_buf_size_st); + sb->final_buf_size = + cast_size_t_to_ulong(final_buf_size_st); + } if (!sb->final_buf) die(_("cannot read blob %s for path %s"), diff --git a/bloom.c b/bloom.c index a805ac0c296b37..c98d1672adb71a 100644 --- a/bloom.c +++ b/bloom.c @@ -16,6 +16,7 @@ define_commit_slab(bloom_filter_slab, struct bloom_filter); static struct bloom_filter_slab bloom_filters; +static int bloom_filter_slab_initialized; struct pathmap_hash_entry { struct hashmap_entry entry; @@ -263,7 +264,10 @@ void add_key_to_filter(const struct bloom_key *key, void init_bloom_filters(void) { + if (bloom_filter_slab_initialized) + return; init_bloom_filter_slab(&bloom_filters); + bloom_filter_slab_initialized = 1; } static void free_one_bloom_filter(struct bloom_filter *filter) @@ -276,6 +280,7 @@ static void free_one_bloom_filter(struct bloom_filter *filter) void deinit_bloom_filters(void) { deep_clear_bloom_filter_slab(&bloom_filters, free_one_bloom_filter); + bloom_filter_slab_initialized = 0; } struct bloom_keyvec *bloom_keyvec_new(const char *path, size_t len, diff --git a/builtin.h b/builtin.h index 4e47a4ebd30ba3..7e64da9f433836 100644 --- a/builtin.h +++ b/builtin.h @@ -175,6 +175,7 @@ int cmd_credential_store(int argc, const char **argv, const char *prefix, struct int cmd_describe(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_diagnose(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_diff_files(int argc, const char **argv, const char *prefix, struct repository *repo); +int cmd_diff_hunks(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_diff_index(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_diff(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_diff_pairs(int argc, const char **argv, const char *prefix, struct repository *repo); diff --git a/builtin/add.c b/builtin/add.c index c859f665199efa..60ffbede2be58a 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -581,7 +581,7 @@ int cmd_add(int argc, string_list_clear(&only_match_skip_worktree, 0); } - transaction = odb_transaction_begin(repo->objects); + odb_transaction_begin_or_die(repo->objects, &transaction, 0); ps_matched = xcalloc(pathspec.nr, 1); if (add_renormalize) diff --git a/builtin/bisect.c b/builtin/bisect.c index e7c2d2f3bb0f4a..3264e2da542698 100644 --- a/builtin/bisect.c +++ b/builtin/bisect.c @@ -663,6 +663,11 @@ static int bisect_successful(struct bisect_terms *terms) refs_read_ref(get_main_ref_store(the_repository), bad_ref, &oid); commit = lookup_commit_reference_by_name(bad_ref); + if (!commit) { + error(_("could not find commit for '%s'"), bad_ref); + free(bad_ref); + return BISECT_FAILED; + } repo_format_commit_message(the_repository, commit, "%s", &commit_name, &pp); @@ -724,7 +729,7 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc, struct object_id oid; const char *head; - if (is_bare_repository()) + if (is_bare_repository(the_repository)) no_checkout = 1; /* @@ -806,9 +811,11 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc, */ head = refs_resolve_ref_unsafe(get_main_ref_store(the_repository), "HEAD", 0, &head_oid, &flags); - if (!head) + if (!head) { if (repo_get_oid(the_repository, "HEAD", &head_oid)) return error(_("bad HEAD - I need a HEAD")); + head = "HEAD"; + } /* * Check if we are bisecting diff --git a/builtin/blame.c b/builtin/blame.c index ffbd3ce5c5a2e3..13e3ff9e36a50e 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -15,6 +15,7 @@ #include "hex.h" #include "commit.h" #include "diff.h" +#include "diff-hunks.h" #include "revision.h" #include "quote.h" #include "string-list.h" @@ -453,6 +454,36 @@ static void determine_line_heat(struct commit_info *ci, const char **dest_color) *dest_color = colorfield[i].col; } +static inline int maybe_putc(int c, FILE *out) +{ + return out ? putc(c, out) : 0; +} + +static size_t print_marks(FILE *out, const struct blame_entry *ent, int opt) +{ + size_t len = 0; + + if ((ent->suspect->commit->object.flags & UNINTERESTING) && + !blank_boundary && !(opt & OUTPUT_ANNOTATE_COMPAT)) { + maybe_putc('^', out); + len++; + } + if (mark_unblamable_lines && ent->unblamable) { + maybe_putc('*', out); + len++; + } + if (mark_ignored_lines && ent->ignored) { + maybe_putc('?', out); + len++; + } + return len; +} + +static size_t count_marks(const struct blame_entry *ent, int opt) +{ + return print_marks(NULL, ent, opt); +} + static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int opt, struct blame_entry *prev_ent) { @@ -499,23 +530,10 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, if (color) fputs(color, stdout); - if (suspect->commit->object.flags & UNINTERESTING) { - if (blank_boundary) { - memset(hex, ' ', strlen(hex)); - } else if (!(opt & OUTPUT_ANNOTATE_COMPAT)) { - length--; - putchar('^'); - } - } - - if (mark_unblamable_lines && ent->unblamable) { - length--; - putchar('*'); - } - if (mark_ignored_lines && ent->ignored) { - length--; - putchar('?'); - } + if ((suspect->commit->object.flags & UNINTERESTING) && + blank_boundary) + memset(hex, ' ', strlen(hex)); + length -= print_marks(stdout, ent, opt); printf("%.*s", (int)(length < GIT_MAX_HEXSZ ? length : GIT_MAX_HEXSZ), hex); if (opt & OUTPUT_ANNOTATE_COMPAT) { @@ -647,11 +665,15 @@ static void find_alignment(struct blame_scoreboard *sb, int *option) struct blame_entry *e; int compute_auto_abbrev = (abbrev < 0); int auto_abbrev = DEFAULT_ABBREV; + size_t max_marks_count = 0; for (e = sb->ent; e; e = e->next) { struct blame_origin *suspect = e->suspect; int num; + size_t marks_count = count_marks(e, *option); + if (max_marks_count < marks_count) + max_marks_count = marks_count; if (compute_auto_abbrev) auto_abbrev = update_auto_abbrev(auto_abbrev, suspect); if (strcmp(suspect->path, sb->path)) @@ -685,8 +707,12 @@ static void find_alignment(struct blame_scoreboard *sb, int *option) max_score_digits = decimal_width(largest_score); if (compute_auto_abbrev) - /* one more abbrev length is needed for the boundary commit */ - abbrev = auto_abbrev + 1; + abbrev = auto_abbrev; + if (abbrev < (int)the_hash_algo->hexsz) { + abbrev += max_marks_count; + if (abbrev > (int)the_hash_algo->hexsz) + abbrev = the_hash_algo->hexsz; + } } static void sanity_check_on_fail(struct blame_scoreboard *sb, int baa) @@ -999,6 +1025,7 @@ int cmd_blame(int argc, repo_init_revisions(the_repository, &revs, NULL); revs.date_mode = blame_date_mode; revs.diffopt.flags.allow_textconv = 1; + revs.diffopt.flags.allow_diff_process = 1; revs.diffopt.flags.follow_renames = 1; save_commit_buffer = 0; @@ -1013,6 +1040,8 @@ int cmd_blame(int argc, case PARSE_OPT_UNKNOWN: break; case PARSE_OPT_HELP: + exit(0); + case PARSE_OPT_HELP_ERROR: case PARSE_OPT_ERROR: case PARSE_OPT_SUBCOMMAND: exit(129); @@ -1033,7 +1062,7 @@ int cmd_blame(int argc, parse_done: revision_opts_finish(&revs); no_whole_file_rename = !revs.diffopt.flags.follow_renames; - xdl_opts |= revs.diffopt.xdl_opts & XDF_INDENT_HEURISTIC; + xdl_opts |= revs.diffopt.xdl_opts & DIFF_HUNKS_DEFAULT_XDL_OPTS; revs.diffopt.flags.follow_renames = 0; argc = parse_options_end(&ctx); @@ -1047,10 +1076,7 @@ int cmd_blame(int argc, } else if (show_progress < 0) show_progress = isatty(2); - if (0 < abbrev && abbrev < (int)the_hash_algo->hexsz) - /* one more abbrev length is needed for the boundary commit */ - abbrev++; - else if (!abbrev) + if (!abbrev) abbrev = the_hash_algo->hexsz; if (revs_file && read_ancestry(revs_file)) @@ -1163,7 +1189,7 @@ int cmd_blame(int argc, revs.disable_stdin = 1; setup_revisions(argc, argv, &revs, NULL); - if (!revs.pending.nr && is_bare_repository()) { + if (!revs.pending.nr && is_bare_repository(the_repository)) { struct commit *head_commit; struct object_id head_oid; @@ -1291,9 +1317,14 @@ int cmd_blame(int argc, output(&sb, output_option); if (show_stats) { + unsigned long hunk_hits, hunk_misses; + + diff_hunks_read_stats(sb.repo, &hunk_hits, &hunk_misses); printf("num read blob: %d\n", sb.num_read_blob); printf("num get patch: %d\n", sb.num_get_patch); printf("num commits: %d\n", sb.num_commits); + printf("num precomputed hits: %lu\n", hunk_hits); + printf("num precomputed misses: %lu\n", hunk_misses); } cleanup: diff --git a/builtin/branch.c b/builtin/branch.c index 1572a4f9ef2ab6..dede60d27b69ea 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -706,6 +706,29 @@ static int edit_branch_description(const char *branch_name) return 0; } +static void die_if_upstream_looks_like_remote(const char *new_upstream, const char *branch_name) +{ + struct strbuf remote_ref = STRBUF_INIT; + int code; + + if (strchr(new_upstream, '/') || + !remote_is_configured(remote_get(new_upstream), 0)) + return; + + strbuf_addf(&remote_ref, "refs/remotes/%s/%s", new_upstream, branch_name); + if (!refs_ref_exists(get_main_ref_store(the_repository), remote_ref.buf)) { + strbuf_release(&remote_ref); + return; + } + + code = die_message(_("--set-upstream-to takes a single / argument")); + advise_if_enabled(ADVICE_SET_UPSTREAM_FAILURE, + _("Did you mean to use: git branch --set-upstream-to=%s/%s?"), + new_upstream, branch_name); + strbuf_release(&remote_ref); + exit(code); +} + int cmd_branch(int argc, const char **argv, const char *prefix, @@ -957,6 +980,15 @@ int cmd_branch(int argc, if (!refs_ref_exists(get_main_ref_store(the_repository), branch->refname)) { if (!argc || branch_checked_out(branch->refname)) die(_("no commit on branch '%s' yet"), branch->name); + /* + * Check the advice up front to avoid the ref + * lookups when the hint is off. The helper still + * calls advise_if_enabled() so the hint carries the + * standard "disable this message" instructions. + */ + if (argc == 1 && + advice_enabled(ADVICE_SET_UPSTREAM_FAILURE)) + die_if_upstream_looks_like_remote(new_upstream, argv[0]); die(_("branch '%s' does not exist"), branch->name); } diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 924c1d224c3eb6..1458dd76d683e0 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -20,7 +20,6 @@ #include "userdiff.h" #include "oid-array.h" #include "packfile.h" -#include "pack-bitmap.h" #include "object-file.h" #include "object-name.h" #include "odb.h" @@ -84,7 +83,7 @@ static char *replace_idents_using_mailmap(char *object_buf, size_t *size) static int filter_object(const char *path, unsigned mode, const struct object_id *oid, - char **buf, unsigned long *size) + char **buf, size_t *size) { enum object_type type; @@ -120,7 +119,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) struct object_id oid; enum object_type type; char *buf; - unsigned long size; + size_t size; struct object_context obj_context = {0}; struct object_info oi = OBJECT_INFO_INIT; unsigned flags = OBJECT_INFO_LOOKUP_REPLACE; @@ -163,11 +162,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) if (odb_read_object_info_extended(the_repository->objects, &oid, &oi, flags) < 0) die("git cat-file: could not get object info"); - if (use_mailmap && (type == OBJ_COMMIT || type == OBJ_TAG)) { - size_t s = size; - buf = replace_idents_using_mailmap(buf, &s); - size = cast_size_t_to_ulong(s); - } + if (use_mailmap && (type == OBJ_COMMIT || type == OBJ_TAG)) + buf = replace_idents_using_mailmap(buf, &size); printf("%"PRIuMAX"\n", (uintmax_t)size); ret = 0; @@ -188,9 +184,15 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) break; case 'c': - if (textconv_object(the_repository, path, obj_context.mode, - &oid, 1, &buf, &size)) + { + unsigned long size_ul = 0; + int textconv_ret = textconv_object(the_repository, path, + obj_context.mode, &oid, 1, + &buf, &size_ul); + size = size_ul; + if (textconv_ret) break; + } /* else fallthrough */ case 'p': @@ -216,11 +218,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) if (!buf) die("Cannot read object %s", obj_name); - if (use_mailmap) { - size_t s = size; - buf = replace_idents_using_mailmap(buf, &s); - size = cast_size_t_to_ulong(s); - } + if (use_mailmap) + buf = replace_idents_using_mailmap(buf, &size); /* otherwise just spit out the data */ break; @@ -263,11 +262,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) buf = odb_read_object_peeled(the_repository->objects, &oid, exp_type_id, &size, NULL); - if (use_mailmap) { - size_t s = size; - buf = replace_idents_using_mailmap(buf, &s); - size = cast_size_t_to_ulong(s); - } + if (use_mailmap) + buf = replace_idents_using_mailmap(buf, &size); break; } default: @@ -288,7 +284,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) struct expand_data { struct object_id oid; enum object_type type; - unsigned long size; + size_t size; unsigned short mode; off_t disk_size; const char *rest; @@ -404,7 +400,7 @@ static void print_object_or_die(struct batch_options *opt, struct expand_data *d fflush(stdout); if (opt->transform_mode) { char *contents; - unsigned long size; + size_t size; if (!data->rest) die("missing path for '%s'", oid_to_hex(oid)); @@ -416,9 +412,12 @@ static void print_object_or_die(struct batch_options *opt, struct expand_data *d oid_to_hex(oid), data->rest); } else if (opt->transform_mode == 'c') { enum object_type type; - if (!textconv_object(the_repository, - data->rest, 0100644, oid, - 1, &contents, &size)) + unsigned long size_ul = 0; + if (textconv_object(the_repository, + data->rest, 0100644, oid, + 1, &contents, &size_ul)) + size = size_ul; + else contents = odb_read_object(the_repository->objects, oid, &type, &size); if (!contents) @@ -434,7 +433,7 @@ static void print_object_or_die(struct batch_options *opt, struct expand_data *d } else { enum object_type type; - unsigned long size; + size_t size; void *contents; contents = odb_read_object(the_repository->objects, oid, @@ -442,11 +441,8 @@ static void print_object_or_die(struct batch_options *opt, struct expand_data *d if (!contents) die("object %s disappeared", oid_to_hex(oid)); - if (use_mailmap) { - size_t s = size; - contents = replace_idents_using_mailmap(contents, &s); - size = cast_size_t_to_ulong(s); - } + if (use_mailmap) + contents = replace_idents_using_mailmap(contents, &size); if (type != data->type) die("object %s changed type!?", oid_to_hex(oid)); @@ -461,9 +457,12 @@ static void print_object_or_die(struct batch_options *opt, struct expand_data *d static void print_default_format(struct strbuf *scratch, struct expand_data *data, struct batch_options *opt) { - strbuf_addf(scratch, "%s %s %"PRIuMAX"%c", oid_to_hex(&data->oid), - type_name(data->type), - (uintmax_t)data->size, opt->output_delim); + strbuf_add_oid_hex(scratch, &data->oid); + strbuf_addch(scratch, ' '); + strbuf_addstr(scratch, type_name(data->type)); + strbuf_addch(scratch, ' '); + strbuf_add_uint(scratch, data->size); + strbuf_addch(scratch, opt->output_delim); } static void report_object_status(struct batch_options *opt, @@ -500,7 +499,7 @@ static void batch_object_write(const char *obj_name, data->info.sizep = &data->size; if (pack) - ret = packed_object_info(pack, offset, &data->info); + ret = packed_object_info(NULL, pack, offset, &data->info); else ret = odb_read_object_info_extended(the_repository->objects, &data->oid, &data->info, @@ -546,15 +545,13 @@ static void batch_object_write(const char *obj_name, } if (use_mailmap && (data->type == OBJ_COMMIT || data->type == OBJ_TAG)) { - size_t s = data->size; char *buf = NULL; buf = odb_read_object(the_repository->objects, &data->oid, &data->type, &data->size); if (!buf) die(_("unable to read %s"), oid_to_hex(&data->oid)); - buf = replace_idents_using_mailmap(buf, &s); - data->size = cast_size_t_to_ulong(s); + buf = replace_idents_using_mailmap(buf, &data->size); free(buf); } @@ -839,34 +836,13 @@ static int batch_one_object_oi(const struct object_id *oid, void *_payload) { struct for_each_object_payload *payload = _payload; - if (oi && oi->whence == OI_PACKED) - return payload->callback(oid, oi->u.packed.pack, oi->u.packed.offset, + if (oi && oi->source_infop->source->type == ODB_SOURCE_PACKED) + return payload->callback(oid, oi->source_infop->u.packed.pack, + oi->source_infop->u.packed.offset, payload->payload); return payload->callback(oid, NULL, 0, payload->payload); } -static int batch_one_object_packed(const struct object_id *oid, - struct packed_git *pack, - uint32_t pos, - void *_payload) -{ - struct for_each_object_payload *payload = _payload; - return payload->callback(oid, pack, nth_packed_object_offset(pack, pos), - payload->payload); -} - -static int batch_one_object_bitmapped(const struct object_id *oid, - enum object_type type UNUSED, - int flags UNUSED, - uint32_t hash UNUSED, - struct packed_git *pack, - off_t offset, - void *_payload) -{ - struct for_each_object_payload *payload = _payload; - return payload->callback(oid, pack, offset, payload->payload); -} - static void batch_each_object(struct batch_options *opt, for_each_object_fn callback, unsigned flags, @@ -876,52 +852,17 @@ static void batch_each_object(struct batch_options *opt, .callback = callback, .payload = _payload, }; + struct odb_source_info source_info; + struct object_info oi = { + .source_infop = &source_info, + }; struct odb_for_each_object_options opts = { .flags = flags, + .filter = &opt->objects_filter, }; - struct bitmap_index *bitmap = NULL; - struct odb_source *source; - - /* - * TODO: we still need to tap into implementation details of the object - * database sources. Ideally, we should extend `odb_for_each_object()` - * to handle object filters itself so that we can move the filtering - * logic into the individual sources. - */ - 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); - if (ret) - break; - } - - if (opt->objects_filter.choice != LOFC_DISABLED && - (bitmap = prepare_bitmap_git(the_repository)) && - !for_each_bitmapped_object(bitmap, &opt->objects_filter, - batch_one_object_bitmapped, &payload)) { - struct packed_git *pack; - - repo_for_each_pack(the_repository, pack) { - if (bitmap_index_contains_pack(bitmap, pack) || - open_pack_index(pack)) - continue; - for_each_object_in_pack(pack, batch_one_object_packed, - &payload, flags); - } - } else { - struct object_info oi = { 0 }; - - for (source = the_repository->objects->sources; source; source = source->next) { - struct odb_source_files *files = odb_source_files_downcast(source); - int ret = packfile_store_for_each_object(files->packed, &oi, - batch_one_object_oi, &payload, &opts); - if (ret) - break; - } - } - free_bitmap_index(bitmap); + odb_for_each_object_ext(the_repository->objects, &oi, + batch_one_object_oi, &payload, &opts); } static int batch_objects(struct batch_options *opt) @@ -929,6 +870,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; @@ -1001,8 +943,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); @@ -1030,7 +972,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; } diff --git a/builtin/check-attr.c b/builtin/check-attr.c index 98f64d5b922e6c..217d83ea7d5de0 100644 --- a/builtin/check-attr.c +++ b/builtin/check-attr.c @@ -116,7 +116,7 @@ int cmd_check_attr(int argc, struct object_id initialized_oid; int cnt, i, doubledash, filei; - if (!is_bare_repository()) + if (!is_bare_repository(the_repository)) setup_work_tree(the_repository); repo_config(the_repository, git_default_config, NULL); diff --git a/builtin/checkout.c b/builtin/checkout.c index b78b3a1d16def4..aee84ca89742b0 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -952,10 +952,13 @@ static void update_refs_for_switch(const struct checkout_opts *opts, const char *old_desc, *reflog_msg; if (opts->new_branch) { if (opts->new_orphan_branch) { - enum log_refs_config log_all_ref_updates = - repo_settings_get_log_all_ref_updates(the_repository); + enum log_refs_config log_all_ref_updates = LOG_REFS_UNSET; + const char *value; char *refname; + if (!repo_config_get_string_tmp(the_repository, "core.logallrefupdates", &value)) + log_all_ref_updates = refs_parse_log_all_ref_updates_config(value); + refname = mkpathdup("refs/heads/%s", opts->new_orphan_branch); if (opts->new_branch_log && !should_autocreate_reflog(log_all_ref_updates, refname)) { diff --git a/builtin/clone.c b/builtin/clone.c index d60d1b60bc238c..9d08cd87224304 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -1116,7 +1116,6 @@ 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(the_repository, work_tree); } if (real_git_dir) { @@ -1186,9 +1185,10 @@ int cmd_clone(int argc, * repository, and reference backends may persist that information into * their on-disk data structures. */ - 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); + init_db(the_repository, git_dir, real_git_dir, work_tree, option_template, + GIT_HASH_UNKNOWN, ref_storage_format, NULL, + do_not_override_repo_unix_permissions, + INIT_DB_QUIET | INIT_DB_SKIP_REFDB); if (real_git_dir) { free((char *)git_dir); diff --git a/builtin/config.c b/builtin/config.c index cf4ba0f7cc6f22..8d8ec0beead220 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 1c47d7c0b7c38d..c0abc931a5948d 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -251,56 +251,19 @@ static int compare_pt(const void *a_, const void *b_) return 0; } -struct lazy_queue { - struct prio_queue queue; - bool get_pending; -}; - -#define LAZY_QUEUE_INIT { { compare_commits_by_commit_date }, false } - -static void *lazy_queue_get(struct lazy_queue *queue) -{ - if (queue->get_pending) - prio_queue_get(&queue->queue); - else - queue->get_pending = true; - return prio_queue_peek(&queue->queue); -} - -static void lazy_queue_put(struct lazy_queue *queue, void *thing) -{ - if (queue->get_pending) - prio_queue_replace(&queue->queue, thing); - else - prio_queue_put(&queue->queue, thing); - queue->get_pending = false; -} - -static bool lazy_queue_empty(const struct lazy_queue *queue) -{ - return queue->queue.nr == (queue->get_pending ? 1 : 0); -} - -static void lazy_queue_clear(struct lazy_queue *queue) -{ - clear_prio_queue(&queue->queue); - queue->get_pending = false; -} - -static unsigned long finish_depth_computation(struct lazy_queue *queue, +static unsigned long finish_depth_computation(struct prio_queue *queue, struct possible_tag *best) { unsigned long seen_commits = 0; struct oidset unflagged = OIDSET_INIT; + struct commit *c; - for (size_t i = queue->get_pending ? 1 : 0; i < queue->queue.nr; i++) { - struct commit *commit = queue->queue.array[i].data; - if (!(commit->object.flags & best->flag_within)) - oidset_insert(&unflagged, &commit->object.oid); + prio_queue_for_each(queue, c) { + if (!(c->object.flags & best->flag_within)) + oidset_insert(&unflagged, &c->object.oid); } - while (!lazy_queue_empty(queue)) { - struct commit *c = lazy_queue_get(queue); + while ((c = prio_queue_get(queue))) { struct commit_list *parents = c->parents; seen_commits++; if (c->object.flags & best->flag_within) { @@ -316,7 +279,7 @@ static unsigned long finish_depth_computation(struct lazy_queue *queue, repo_parse_commit(the_repository, p); seen = p->object.flags & SEEN; if (!seen) - lazy_queue_put(queue, p); + prio_queue_put(queue, p); flag_before = p->object.flags & best->flag_within; p->object.flags |= c->object.flags; flag_after = p->object.flags & best->flag_within; @@ -364,8 +327,8 @@ static void append_suffix(int depth, const struct object_id *oid, struct strbuf static void describe_commit(struct commit *cmit, struct strbuf *dst) { - struct commit *gave_up_on = NULL; - struct lazy_queue queue = LAZY_QUEUE_INIT; + struct commit *c, *gave_up_on = NULL; + struct prio_queue queue = { compare_commits_by_commit_date }; struct commit_name *n; struct possible_tag all_matches[MAX_TAGS]; unsigned int match_cnt = 0, annotated_cnt = 0, cur_match; @@ -407,9 +370,8 @@ static void describe_commit(struct commit *cmit, struct strbuf *dst) } cmit->object.flags = SEEN; - lazy_queue_put(&queue, cmit); - while (!lazy_queue_empty(&queue)) { - struct commit *c = lazy_queue_get(&queue); + prio_queue_put(&queue, cmit); + while ((c = prio_queue_get(&queue))) { struct commit_list *parents = c->parents; struct commit_name **slot; @@ -443,7 +405,7 @@ static void describe_commit(struct commit *cmit, struct strbuf *dst) t->depth++; } /* Stop if last remaining path already covered by best candidate(s) */ - if (annotated_cnt && lazy_queue_empty(&queue)) { + if (annotated_cnt && !prio_queue_size(&queue)) { int best_depth = INT_MAX; unsigned best_within = 0; for (cur_match = 0; cur_match < match_cnt; cur_match++) { @@ -466,7 +428,7 @@ static void describe_commit(struct commit *cmit, struct strbuf *dst) struct commit *p = parents->item; repo_parse_commit(the_repository, p); if (!(p->object.flags & SEEN)) - lazy_queue_put(&queue, p); + prio_queue_put(&queue, p); p->object.flags |= c->object.flags; parents = parents->next; @@ -481,7 +443,7 @@ static void describe_commit(struct commit *cmit, struct strbuf *dst) strbuf_add_unique_abbrev(dst, cmit_oid, abbrev); if (suffix) strbuf_addstr(dst, suffix); - lazy_queue_clear(&queue); + clear_prio_queue(&queue); return; } if (unannotated_cnt) @@ -497,11 +459,11 @@ static void describe_commit(struct commit *cmit, struct strbuf *dst) QSORT(all_matches, match_cnt, compare_pt); if (gave_up_on) { - lazy_queue_put(&queue, gave_up_on); + prio_queue_put(&queue, gave_up_on); seen_commits--; } seen_commits += finish_depth_computation(&queue, &all_matches[0]); - lazy_queue_clear(&queue); + clear_prio_queue(&queue); if (debug) { static int label_width = -1; @@ -712,13 +674,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/heads/%s", item->string); + for_each_string_list_item(item, &exclude_patterns) + strvec_pushf(&args, "--exclude=refs/heads/%s", item->string); for_each_string_list_item(item, &patterns) - strvec_pushf(&args, "--refs=refs/tags/%s", item->string); + strvec_pushf(&args, "--refs=refs/remotes/%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/remotes/%s", item->string); } + if (argc) strvec_pushv(&args, argv); else @@ -740,6 +714,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); diff --git a/builtin/diff-hunks.c b/builtin/diff-hunks.c new file mode 100644 index 00000000000000..3aea2dad56aa4d --- /dev/null +++ b/builtin/diff-hunks.c @@ -0,0 +1,53 @@ +#include "builtin.h" +#include "config.h" +#include "diff-hunks.h" +#include "gettext.h" +#include "parse-options.h" +#include "repository.h" + +static const char * const diff_hunks_usage[] = { + N_("git diff-hunks verify"), + N_("git diff-hunks clear"), + NULL +}; + +static int cmd_diff_hunks_verify(int argc, const char **argv, + const char *prefix UNUSED, + struct repository *r) +{ + struct option options[] = { OPT_END() }; + + argc = parse_options(argc, argv, NULL, options, diff_hunks_usage, 0); + if (argc) + usage_with_options(diff_hunks_usage, options); + return diff_hunks_verify(r) ? 1 : 0; +} + +static int cmd_diff_hunks_clear(int argc, const char **argv, + const char *prefix UNUSED, + struct repository *r) +{ + struct option options[] = { OPT_END() }; + + argc = parse_options(argc, argv, NULL, options, diff_hunks_usage, 0); + if (argc) + usage_with_options(diff_hunks_usage, options); + return diff_hunks_clear(r) ? 1 : 0; +} + +int cmd_diff_hunks(int argc, const char **argv, const char *prefix, + struct repository *repo) +{ + parse_opt_subcommand_fn *fn = NULL; + struct option options[] = { + OPT_SUBCOMMAND("verify", &fn, cmd_diff_hunks_verify), + OPT_SUBCOMMAND("clear", &fn, cmd_diff_hunks_clear), + OPT_END() + }; + + repo_config(repo, git_default_config, NULL); + + argc = parse_options(argc, argv, prefix, options, diff_hunks_usage, 0); + + return fn(argc, argv, prefix, repo); +} diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index 8b8f8b54e40664..296c6a137e23c6 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -170,6 +170,8 @@ int cmd_diff_tree(int argc, opt->diffopt.rotate_to_strict = 1; + diff_hunks_attach(&opt->diffopt); + /* * NOTE! We expect "a..b" to expand to "^a b" but it is * perfectly valid for revision range parser to yield "b ^a", @@ -234,5 +236,6 @@ int cmd_diff_tree(int argc, diff_free(&opt->diffopt); } + diff_hunks_detach(&opt->diffopt); return diff_result_code(opt); } diff --git a/builtin/diff.c b/builtin/diff.c index 4b46e394cecb8d..a39ffe69a48a5c 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -510,6 +510,7 @@ int cmd_diff(int argc, init_diffstat_widths(&rev.diffopt); rev.diffopt.flags.allow_external = 1; rev.diffopt.flags.allow_textconv = 1; + rev.diffopt.flags.allow_diff_process = 1; /* If this is a no-index diff, just run it and exit there. */ if (no_index) @@ -568,6 +569,15 @@ int cmd_diff(int argc, } } + /* + * The hunk store is keyed by blob pair, so any diff whose + * file pairs carry known blob object IDs (tree-to-tree, + * index-to-tree) can consult the same entries that + * "git log --stat" and "git blame" use; pairs without known + * blobs bypass it at lookup time. + */ + diff_hunks_attach(&rev.diffopt); + symdiff_prepare(&rev, &sdiff); for (i = 0; i < rev.pending.nr; i++) { struct object_array_entry *entry = &rev.pending.objects[i]; @@ -579,9 +589,13 @@ int cmd_diff(int argc, obj = deref_tag(the_repository, obj, NULL, 0); if (!obj) die(_("invalid object '%s' given."), name); - if (obj->type == OBJ_COMMIT) - obj = &repo_get_commit_tree(the_repository, - ((struct commit *)obj))->object; + if (obj->type == OBJ_COMMIT) { + struct tree *tree = repo_get_commit_tree( + the_repository, (struct commit *)obj); + if (!tree) + die(_("unable to read tree object for commit '%s'"), name); + obj = &tree->object; + } if (obj->type == OBJ_TREE) { if (sdiff.skip && bitmap_get(sdiff.skip, i)) @@ -644,6 +658,7 @@ int cmd_diff(int argc, result = diff_result_code(&rev); if (1 < rev.diffopt.skip_stat_unmatch) refresh_index_quietly(); + diff_hunks_detach(&rev.diffopt); release_revisions(&rev); object_array_clear(&ent); symdiff_release(&sdiff); diff --git a/builtin/difftool.c b/builtin/difftool.c index 2a21005f2ee264..26778f8515deef 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -319,7 +319,7 @@ static char *get_symlink(struct repository *repo, data = strbuf_detach(&link, NULL); } else { enum object_type type; - unsigned long size; + size_t size; data = odb_read_object(repo->objects, oid, &type, &size); if (!data) die(_("could not read object %s for symlink %s"), diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 2eb43a28da748e..629d7c591a9d70 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -33,7 +33,7 @@ #include "gpg-interface.h" static const char *const fast_export_usage[] = { - N_("git fast-export []"), + N_("git fast-export [] [] [[--] ...]"), NULL }; @@ -317,7 +317,10 @@ static void export_blob(const struct object_id *oid) object = (struct object *)lookup_blob(the_repository, oid); eaten = 0; } else { - buf = odb_read_object(the_repository->objects, oid, &type, &size); + size_t size_st = 0; + buf = odb_read_object(the_repository->objects, oid, &type, + &size_st); + size = cast_size_t_to_ulong(size_st); if (!buf) die(_("could not read blob %s"), oid_to_hex(oid)); if (check_object_signature(the_repository, oid, buf, size, @@ -880,7 +883,7 @@ static char *anonymize_tag(void) static void handle_tag(const char *name, struct tag *tag) { - unsigned long size; + size_t size; enum object_type type; char *buf; const char *tagger, *tagger_end, *message; diff --git a/builtin/fast-import.c b/builtin/fast-import.c index 82bc6dcc003723..6692f7cd812d0e 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c @@ -965,10 +965,11 @@ 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); - the_hash_algo->init_fn(&c); + git_hash_init(&c, the_hash_algo); git_hash_update(&c, hdr, hdrlen); git_hash_update(&c, dat->buf, dat->len); git_hash_final_oid(&oid, &c); @@ -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. */ @@ -1129,12 +1131,12 @@ static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark) hdrlen = format_object_header((char *)out_buf, out_sz, OBJ_BLOB, len); - the_hash_algo->init_fn(&c); + git_hash_init(&c, the_hash_algo); git_hash_update(&c, out_buf, hdrlen); 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); @@ -1214,6 +1216,7 @@ static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark) out: free(in_buf); free(out_buf); + hashfile_checkpoint_release(&checkpoint); } /* All calls must be guarded by find_object() or find_mark() to @@ -1239,6 +1242,8 @@ static void *gfi_unpack_entry( unsigned long *sizep) { enum object_type type; + size_t size_st = 0; + void *data; struct packed_git *p = all_packs[oe->pack_id]; if (p == pack_data && p->pack_size < (pack_size + the_hash_algo->rawsz)) { /* The object is stored in the packfile we are writing to @@ -1260,7 +1265,10 @@ static void *gfi_unpack_entry( */ p->pack_size = pack_size + the_hash_algo->rawsz; } - return unpack_entry(the_repository, p, oe->idx.offset, &type, sizep); + data = unpack_entry(the_repository, p, oe->idx.offset, &type, &size_st); + if (sizep) + *sizep = cast_size_t_to_ulong(size_st); + return data; } static void load_tree(struct tree_entry *root) @@ -1286,7 +1294,10 @@ static void load_tree(struct tree_entry *root) die(_("can't load tree %s"), oid_to_hex(oid)); } else { enum object_type type; - buf = odb_read_object(the_repository->objects, oid, &type, &size); + size_t size_st = 0; + buf = odb_read_object(the_repository->objects, oid, &type, + &size_st); + size = cast_size_t_to_ulong(size_st); if (!buf || type != OBJ_TREE) die(_("can't load tree %s"), oid_to_hex(oid)); } @@ -2555,7 +2566,7 @@ static void note_change_n(const char *p, struct branch *b, unsigned char *old_fa die(_("mark :%" PRIuMAX " not a commit"), commit_mark); oidcpy(&commit_oid, &commit_oe->idx.oid); } else if (!repo_get_oid(the_repository, p, &commit_oid)) { - unsigned long size; + size_t size; char *buf = odb_read_object_peeled(the_repository->objects, &commit_oid, OBJ_COMMIT, &size, &commit_oid); @@ -2622,10 +2633,12 @@ static void parse_from_existing(struct branch *b) oidclr(&b->branch_tree.versions[1].oid, the_repository->hash_algo); } else { unsigned long size; + size_t size_st = 0; char *buf; buf = odb_read_object_peeled(the_repository->objects, &b->oid, - OBJ_COMMIT, &size, &b->oid); + OBJ_COMMIT, &size_st, &b->oid); + size = cast_size_t_to_ulong(size_st); parse_from_commit(b, buf, size); free(buf); } @@ -2717,7 +2730,7 @@ static struct hash_list *parse_merge(unsigned int *count) die(_("mark :%" PRIuMAX " not a commit"), idnum); oidcpy(&n->oid, &oe->idx.oid); } else if (!repo_get_oid(the_repository, from, &n->oid)) { - unsigned long size; + size_t size; char *buf = odb_read_object_peeled(the_repository->objects, &n->oid, OBJ_COMMIT, &size, &n->oid); @@ -3325,7 +3338,10 @@ static void cat_blob(struct object_entry *oe, struct object_id *oid) char *buf; if (!oe || oe->pack_id == MAX_PACK_ID) { - buf = odb_read_object(the_repository->objects, oid, &type, &size); + size_t size_st = 0; + buf = odb_read_object(the_repository->objects, oid, &type, + &size_st); + size = cast_size_t_to_ulong(size_st); } else { type = oe->type; buf = gfi_unpack_entry(oe, &size); @@ -3433,8 +3449,10 @@ static struct object_entry *dereference(struct object_entry *oe, buf = gfi_unpack_entry(oe, &size); } else { enum object_type unused; + size_t size_st = 0; buf = odb_read_object(the_repository->objects, oid, - &unused, &size); + &unused, &size_st); + size = cast_size_t_to_ulong(size_st); } if (!buf) die(_("can't load object %s"), oid_to_hex(oid)); diff --git a/builtin/fetch.c b/builtin/fetch.c index c1d7c672f4e0d8..775a79707472ae 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -103,6 +103,7 @@ static struct string_list negotiation_include = STRING_LIST_INIT_NODUP; struct fetch_config { enum display_format display_format; + enum follow_remote_head_settings follow_remote_head; int all; int prune; int prune_tags; @@ -171,6 +172,23 @@ static int git_fetch_config(const char *k, const char *v, else die(_("invalid value for '%s': '%s'"), "fetch.output", v); + return 0; + } + + if (!strcmp(k, "fetch.followremotehead")) { + if (!v) + return config_error_nonbool(k); + else if (!strcmp(v, "never")) + fetch_config->follow_remote_head = FOLLOW_REMOTE_NEVER; + else if (!strcmp(v, "create")) + fetch_config->follow_remote_head = FOLLOW_REMOTE_CREATE; + else if (!strcmp(v, "warn")) + fetch_config->follow_remote_head = FOLLOW_REMOTE_WARN; + else if (!strcmp(v, "always")) + fetch_config->follow_remote_head = FOLLOW_REMOTE_ALWAYS; + else + warning(_("unrecognized fetch.followRemoteHEAD value '%s' ignored"), v); + return 0; } return git_default_config(k, v, ctx, cb); @@ -1697,17 +1715,19 @@ static const char *strip_refshead(const char *name){ static void set_head_advice_msg(const char *remote, const char *head_name) { const char message_advice_set_head[] = - N_("Run 'git remote set-head %s %s' to follow the change, or set\n" - "'remote.%s.followRemoteHEAD' configuration option to a different value\n" - "if you do not want to see this message. Specifically running\n" - "'git config set remote.%s.followRemoteHEAD warn-if-not-branch-%s'\n" - "will disable the warning until the remote changes HEAD to something else."); + N_("Run 'git remote set-head %s %s' to follow the change, or modify\n" + "either of the 'remote.%s.followRemoteHEAD' or 'fetch.followRemoteHEAD'\n" + "configuration variables to handle the situation differently.\n\n" + + "Using this specific setting\n\n" + " git config set remote.%s.followRemoteHEAD warn-if-not-%s\n\n" + "will suppress the warning until the remote changes HEAD to something else."); advise_if_enabled(ADVICE_FETCH_SET_HEAD_WARN, _(message_advice_set_head), remote, head_name, remote, remote, head_name); } -static void report_set_head(const char *remote, const char *head_name, +static void warn_set_head(const char *remote, const char *head_name, struct strbuf *buf_prev, int updateres) { struct strbuf buf_prefix = STRBUF_INIT; const char *prev_head = NULL; @@ -1729,12 +1749,12 @@ static void report_set_head(const char *remote, const char *head_name, strbuf_release(&buf_prefix); } -static int set_head(const struct ref *remote_refs, struct remote *remote) +static int set_head(const struct ref *remote_refs, struct remote *remote, + int follow_remote_head) { int result = 0, create_only, baremirror, was_detached; struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT, b_local_head = STRBUF_INIT; - int follow_remote_head = remote->follow_remote_head; const char *no_warn_branch = remote->no_warn_branch; char *head_name = NULL; struct ref *ref, *matches; @@ -1764,7 +1784,7 @@ static int set_head(const struct ref *remote_refs, struct remote *remote) if (!head_name) goto cleanup; - baremirror = is_bare_repository() && remote->mirror; + baremirror = is_bare_repository(the_repository) && remote->mirror; create_only = follow_remote_head == FOLLOW_REMOTE_ALWAYS ? 0 : !baremirror; if (baremirror) { strbuf_addstr(&b_head, "HEAD"); @@ -1773,7 +1793,7 @@ static int set_head(const struct ref *remote_refs, struct remote *remote) strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote->name); strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote->name, head_name); } - /* make sure it's valid */ + /* make sure it's valid */ if (!baremirror && !refs_ref_exists(refs, b_remote_head.buf)) { result = 1; goto cleanup; @@ -1787,7 +1807,7 @@ static int set_head(const struct ref *remote_refs, struct remote *remote) if (verbosity >= 0 && follow_remote_head == FOLLOW_REMOTE_WARN && (!no_warn_branch || strcmp(no_warn_branch, head_name))) - report_set_head(remote->name, head_name, &b_local_head, was_detached); + warn_set_head(remote->name, head_name, &b_local_head, was_detached); cleanup: free(head_name); @@ -1819,7 +1839,7 @@ static void ref_transaction_rejection_handler(const char *refname, { struct ref_rejection_data *data = cb_data; - if (err == REF_TRANSACTION_ERROR_CASE_CONFLICT && ignore_case && + if (err == REF_TRANSACTION_ERROR_CASE_CONFLICT && repo_ignore_case(the_repository) && !data->case_sensitive_msg_shown) { error(_("You're on a case-insensitive filesystem, and the remote you are\n" "trying to fetch from has references that only differ in casing. It\n" @@ -1901,6 +1921,7 @@ static int do_fetch(struct transport *transport, struct ref_update_display_info_array display_array = { 0 }; struct strmap rejected_refs = STRMAP_INIT; int summary_width = 0; + int follow_remote_head; if (tags == TAGS_DEFAULT) { if (transport->remote->fetch_tags == 2) @@ -1916,6 +1937,22 @@ static int do_fetch(struct transport *transport, goto cleanup; } + /* + * NEEDSWORK: By the time this function executes, we have already parsed + * all such followRemoteHEAD values from the external configuration, + * potentially emitting warning messages for bogus values. Ideally, if + * this fetch ends up not needing to consult these values, then git would + * not ever output a value warning. (eg: when pulling from a URL directly - + * rather than a configured remote, or when a remote's followRemoteHEAD + * overrides the fallback fetch setting) + */ + if (transport->remote->follow_remote_head) + follow_remote_head = transport->remote->follow_remote_head; + else if (config->follow_remote_head) + follow_remote_head = config->follow_remote_head; + else + follow_remote_head = BUILTIN_FOLLOW_REMOTE_HEAD_DFLT; + if (rs->nr) { refspec_ref_prefixes(rs, &transport_ls_refs_options.ref_prefixes); } else { @@ -1924,7 +1961,7 @@ static int do_fetch(struct transport *transport, if (transport->remote->fetch.nr) { refspec_ref_prefixes(&transport->remote->fetch, &transport_ls_refs_options.ref_prefixes); - if (transport->remote->follow_remote_head != FOLLOW_REMOTE_NEVER) + if (follow_remote_head != FOLLOW_REMOTE_NEVER) do_set_head = 1; } if (branch && branch_has_merge_config(branch) && @@ -2131,7 +2168,7 @@ static int do_fetch(struct transport *transport, * Way too many cases where this can go wrong so let's just * ignore errors and fail silently for now. */ - set_head(remote_refs, transport->remote); + set_head(remote_refs, transport->remote, follow_remote_head); } cleanup: @@ -2471,6 +2508,7 @@ int cmd_fetch(int argc, { struct fetch_config config = { .display_format = DISPLAY_FORMAT_FULL, + .follow_remote_head = FOLLOW_REMOTE_UNCONFIGURED, .prune = -1, .prune_tags = -1, .show_forced_updates = 1, diff --git a/builtin/fsck.c b/builtin/fsck.c index 248f8ff5a03bc9..76b723f36d3dca 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -724,7 +724,7 @@ static int fsck_loose(const struct object_id *oid, const char *path, struct for_each_loose_cb *data = cb_data; struct object *obj; enum object_type type = OBJ_NONE; - unsigned long size; + size_t size; void *contents = NULL; int eaten; struct object_info oi = OBJECT_INFO_INIT; diff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c index f920cf3a8202f6..4161dd82825b4c 100644 --- a/builtin/fsmonitor--daemon.c +++ b/builtin/fsmonitor--daemon.c @@ -1418,6 +1418,8 @@ static int fsmonitor_run_daemon(void) err = fsmonitor_run_daemon_1(&state); done: + fsmonitor_free_token_data(state.current_token_data); + state.current_token_data = NULL; pthread_cond_destroy(&state.cookies_cond); pthread_mutex_destroy(&state.main_lock); { diff --git a/builtin/gc.c b/builtin/gc.c index 84a66d32404e4d..d32af422af5e58 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; @@ -902,7 +902,7 @@ int cmd_gc(int argc, die(_("failed to parse gc.logExpiry value %s"), cfg.gc_log_expire); if (cfg.pack_refs < 0) - cfg.pack_refs = !is_bare_repository(); + cfg.pack_refs = !is_bare_repository(the_repository); argc = parse_options(argc, argv, prefix, builtin_gc_options, builtin_gc_usage, 0); diff --git a/builtin/grep.c b/builtin/grep.c index 6a09571903cd26..d3d86abe01e032 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -25,12 +25,11 @@ #include "setup.h" #include "submodule.h" #include "submodule-config.h" -#include "object-file.h" #include "object-name.h" #include "odb.h" +#include "odb/source.h" #include "oid-array.h" #include "oidset.h" -#include "packfile.h" #include "pager.h" #include "path.h" #include "promisor-remote.h" @@ -520,7 +519,7 @@ static int grep_submodule(struct grep_opt *opt, enum object_type object_type; struct tree_desc tree; void *data; - unsigned long size; + size_t size; struct strbuf base = STRBUF_INIT; obj_read_lock(); @@ -573,7 +572,7 @@ static int grep_cache(struct grep_opt *opt, enum object_type type; struct tree_desc tree; void *data; - unsigned long size; + size_t size; data = odb_read_object(the_repository->objects, &ce->oid, &type, &size); @@ -666,7 +665,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, enum object_type type; struct tree_desc sub; void *data; - unsigned long size; + size_t size; data = odb_read_object(the_repository->objects, &entry.oid, &type, &size); @@ -730,7 +729,7 @@ static void collect_blob_oids_for_tree(struct repository *repo, enum object_type type; struct tree_desc sub_tree; void *data; - unsigned long size; + size_t size; data = odb_read_object(repo->objects, &entry.oid, &type, &size); @@ -764,7 +763,7 @@ static void collect_blob_oids_for_treeish(struct grep_opt *opt, { struct tree_desc tree; void *data; - unsigned long size; + size_t size; struct strbuf base = STRBUF_INIT; int len; @@ -841,7 +840,7 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) { struct tree_desc tree; void *data; - unsigned long size; + size_t size; struct strbuf base; int hit, len; @@ -1357,15 +1356,8 @@ int cmd_grep(int argc, if (recurse_submodules) repo_read_gitmodules(the_repository, 1); - if (startup_info->have_repository) { - struct odb_source *source; - - odb_prepare_alternates(the_repository->objects); - for (source = the_repository->objects->sources; source; source = source->next) { - struct odb_source_files *files = odb_source_files_downcast(source); - packfile_store_prepare(files->packed); - } - } + if (startup_info->have_repository) + odb_prepare(the_repository->objects, 0); start_threads(&opt); } else { diff --git a/builtin/history.c b/builtin/history.c index 091465a59e2f96..d28c1f08bb66ea 100644 --- a/builtin/history.c +++ b/builtin/history.c @@ -17,13 +17,17 @@ #include "read-cache.h" #include "refs.h" #include "replay.h" +#include "reset.h" #include "revision.h" #include "sequencer.h" #include "strvec.h" #include "tree.h" +#include "tree-walk.h" #include "unpack-trees.h" #include "wt-status.h" +#define GIT_HISTORY_DROP_USAGE \ + N_("git history drop [--dry-run] [--update-refs=(branches|head)] [--empty=(drop|keep|abort)]") #define GIT_HISTORY_FIXUP_USAGE \ N_("git history fixup [--dry-run] [--update-refs=(branches|head)] [--reedit-message] [--empty=(drop|keep|abort)]") #define GIT_HISTORY_REWORD_USAGE \ @@ -52,11 +56,6 @@ static int fill_commit_message(struct repository *repo, " empty message aborts the commit.\n"); struct wt_status s; - strbuf_addstr(out, default_message); - strbuf_addch(out, '\n'); - strbuf_commented_addf(out, comment_line_str, hint, action, comment_line_str); - write_file_buf(path, out->buf, out->len); - wt_status_prepare(repo, &s); FREE_AND_NULL(s.branch); s.ahead_behind_flags = AHEAD_BEHIND_QUICK; @@ -68,14 +67,22 @@ static int fill_commit_message(struct repository *repo, s.whence = FROM_COMMIT; s.committable = 1; - s.fp = fopen(git_path_commit_editmsg(), "a"); + s.fp = fopen(path, "w"); if (!s.fp) - return error_errno(_("could not open '%s'"), git_path_commit_editmsg()); + return error_errno(_("could not open '%s'"), path); + + strbuf_addstr(out, default_message); + strbuf_addch(out, '\n'); + strbuf_commented_addf(out, comment_line_str, hint, action, comment_line_str); + if (fwrite(out->buf, 1, out->len, s.fp) != out->len) + die_errno(_("could not write to '%s'"), path); wt_status_collect_changes_trees(&s, old_tree, new_tree); wt_status_print(&s); wt_status_collect_free_buffers(&s); string_list_clear_func(&s.change, change_data_free); + if (fclose(s.fp)) + die_errno(_("could not write to '%s'"), path); strbuf_reset(out); if (launch_editor(path, out, NULL)) { @@ -333,21 +340,17 @@ static int handle_ref_update(struct ref_transaction *transaction, NULL, NULL, 0, reflog_msg, err); } -static int handle_reference_updates(struct rev_info *revs, - enum ref_action action, - struct commit *original, - struct commit *rewritten, - const char *reflog_msg, - int dry_run, - enum replay_empty_commit_action empty) +static int compute_pending_ref_updates(struct rev_info *revs, + enum ref_action action, + struct commit *original, + struct commit *rewritten, + enum replay_empty_commit_action empty, + struct replay_result *result) { const struct name_decoration *decoration; struct replay_revisions_options opts = { .empty = empty, }; - struct replay_result result = { 0 }; - struct ref_transaction *transaction = NULL; - struct strbuf err = STRBUF_INIT; char hex[GIT_MAX_HEXSZ + 1]; bool detached_head; int head_flags = 0; @@ -359,34 +362,13 @@ static int handle_reference_updates(struct rev_info *revs, opts.onto = oid_to_hex_r(hex, &rewritten->object.oid); - ret = replay_revisions(revs, &opts, &result); + ret = replay_revisions(revs, &opts, result); if (ret) - goto out; + return ret; if (action != REF_ACTION_BRANCHES && action != REF_ACTION_HEAD) BUG("unsupported ref action %d", action); - if (!dry_run) { - transaction = ref_store_transaction_begin(get_main_ref_store(revs->repo), 0, &err); - if (!transaction) { - ret = error(_("failed to begin ref transaction: %s"), err.buf); - goto out; - } - } - - for (size_t i = 0; i < result.updates_nr; i++) { - ret = handle_ref_update(transaction, - result.updates[i].refname, - &result.updates[i].new_oid, - &result.updates[i].old_oid, - reflog_msg, &err); - if (ret) { - ret = error(_("failed to update ref '%s': %s"), - result.updates[i].refname, err.buf); - goto out; - } - } - /* * `replay_revisions()` only updates references that are * ancestors of `rewritten`, so we need to manually @@ -414,14 +396,41 @@ static int handle_reference_updates(struct rev_info *revs, !detached_head) continue; + replay_result_queue_update(result, decoration->name, + &original->object.oid, + &rewritten->object.oid); + } + + return 0; +} + +static int apply_pending_ref_updates(struct repository *repo, + const struct replay_result *result, + const char *reflog_msg, + int dry_run) +{ + struct ref_transaction *transaction = NULL; + struct strbuf err = STRBUF_INIT; + int ret; + + if (!dry_run) { + transaction = ref_store_transaction_begin(get_main_ref_store(repo), + 0, &err); + if (!transaction) { + ret = error(_("failed to begin ref transaction: %s"), err.buf); + goto out; + } + } + + for (size_t i = 0; i < result->updates_nr; i++) { ret = handle_ref_update(transaction, - decoration->name, - &rewritten->object.oid, - &original->object.oid, + result->updates[i].refname, + &result->updates[i].new_oid, + &result->updates[i].old_oid, reflog_msg, &err); if (ret) { ret = error(_("failed to update ref '%s': %s"), - decoration->name, err.buf); + result->updates[i].refname, err.buf); goto out; } } @@ -435,11 +444,33 @@ static int handle_reference_updates(struct rev_info *revs, out: ref_transaction_free(transaction); - replay_result_release(&result); strbuf_release(&err); return ret; } +static int handle_reference_updates(struct rev_info *revs, + enum ref_action action, + struct commit *original, + struct commit *rewritten, + const char *reflog_msg, + int dry_run, + enum replay_empty_commit_action empty) +{ + struct replay_result result = { 0 }; + int ret; + + ret = compute_pending_ref_updates(revs, action, original, rewritten, + empty, &result); + if (ret) + goto out; + + ret = apply_pending_ref_updates(revs->repo, &result, reflog_msg, dry_run); + +out: + replay_result_release(&result); + return ret; +} + static int commit_became_empty(struct repository *repo, struct commit *original, struct tree *result) @@ -525,7 +556,7 @@ static int cmd_history_fixup(int argc, if (action == REF_ACTION_DEFAULT) action = REF_ACTION_BRANCHES; - if (is_bare_repository()) { + if (is_bare_repository(repo)) { ret = error(_("cannot run fixup in a bare repository")); goto out; } @@ -975,12 +1006,191 @@ static int cmd_history_split(int argc, return ret; } +static int update_worktree(struct repository *repo, + const struct commit *old_head, + const struct commit *new_head, + bool dry_run) +{ + struct reset_working_tree_options opts = { + .oid_from = &old_head->object.oid, + .oid = &new_head->object.oid, + }; + if (dry_run) + opts.flags |= RESET_WORKING_TREE_DRY_RUN; + return reset_working_tree(repo, &opts); +} + +static int find_head_tree_change(struct repository *repo, + const struct replay_result *result, + struct commit **old_head, + struct commit **new_head, + bool *changed) +{ + const struct replay_ref_update *head_update = NULL; + struct commit *old_head_commit, *new_head_commit; + struct tree *old_head_tree, *new_head_tree; + const char *head_target; + int head_flags; + + *changed = false; + + head_target = refs_resolve_ref_unsafe(get_main_ref_store(repo), "HEAD", + RESOLVE_REF_NO_RECURSE | RESOLVE_REF_READING, + NULL, &head_flags); + if (!head_target) + return error(_("cannot look up HEAD")); + + for (size_t i = 0; i < result->updates_nr; i++) { + if (!strcmp(result->updates[i].refname, head_target)) { + head_update = &result->updates[i]; + break; + } + } + + if (!head_update) + return 0; + + old_head_commit = lookup_commit_reference(repo, &head_update->old_oid); + new_head_commit = lookup_commit_reference(repo, &head_update->new_oid); + if (!old_head_commit || !new_head_commit) + return error(_("cannot resolve HEAD commit")); + + old_head_tree = repo_get_commit_tree(repo, old_head_commit); + new_head_tree = repo_get_commit_tree(repo, new_head_commit); + if (!old_head_tree || !new_head_tree) + return error(_("cannot resolve tree for HEAD")); + + if (oideq(&old_head_tree->object.oid, &new_head_tree->object.oid)) + return 0; + + *old_head = old_head_commit; + *new_head = new_head_commit; + *changed = true; + + return 0; +} + +static int cmd_history_drop(int argc, + const char **argv, + const char *prefix, + struct repository *repo) +{ + const char * const usage[] = { + GIT_HISTORY_DROP_USAGE, + NULL, + }; + enum replay_empty_commit_action empty = REPLAY_EMPTY_COMMIT_DROP; + enum ref_action action = REF_ACTION_DEFAULT; + 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_CALLBACK_F(0, "empty", &empty, "(drop|keep|abort)", + N_("how to handle descendants that become empty"), + PARSE_OPT_NONEG, parse_opt_empty), + OPT_END(), + }; + struct strbuf reflog_msg = STRBUF_INIT; + struct commit *original, *rewritten; + struct rev_info revs = { 0 }; + struct replay_result result = { 0 }; + struct commit *old_head, *new_head; + bool head_moves = 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; + + original = lookup_commit_reference_by_name(argv[0]); + if (!original) { + ret = error(_("commit cannot be found: %s"), argv[0]); + goto out; + } + + if (!original->parents) { + ret = error(_("cannot drop root commit %s: " + "it has no parent to replay onto"), + argv[0]); + goto out; + } else if (original->parents->next) { + ret = error(_("cannot drop merge commit: %s"), argv[0]); + goto out; + } + + ret = setup_revwalk(repo, action, original, &revs); + if (ret) + goto out; + + rewritten = original->parents->item; + + ret = compute_pending_ref_updates(&revs, action, original, rewritten, + empty, &result); + if (ret) { + ret = error(_("failed replaying descendants")); + goto out; + } + + /* + * If HEAD will move as a result of the rewrite then we'll have to + * merge in the changes into the worktree and index. This merge can of + * course conflict, which will cause the whole operation to abort. + * + * If we had already updated the refs at that point then we'd have an + * inconsistent repository state. So we first perform a dry-run merge + * here before updating refs. + */ + if (!is_bare_repository(repo)) { + ret = find_head_tree_change(repo, &result, &old_head, + &new_head, &head_moves); + if (ret < 0) + goto out; + + if (head_moves && update_worktree(repo, old_head, new_head, true) < 0) { + ret = error(_("dropping this commit would " + "overwrite local changes; aborting")); + goto out; + } + } + + strbuf_addf(&reflog_msg, "drop: dropping %s", argv[0]); + ret = apply_pending_ref_updates(repo, &result, reflog_msg.buf, dry_run); + if (ret < 0) { + ret = error(_("failed to update references")); + goto out; + } + + if (!dry_run && head_moves && update_worktree(repo, old_head, new_head, false) < 0) { + ret = error(_("could not update working tree to new commit %s"), + oid_to_hex(&new_head->object.oid)); + goto out; + } + + ret = 0; + +out: + replay_result_release(&result); + strbuf_release(&reflog_msg); + release_revisions(&revs); + return ret; +} + int cmd_history(int argc, const char **argv, const char *prefix, struct repository *repo) { const char * const usage[] = { + GIT_HISTORY_DROP_USAGE, GIT_HISTORY_FIXUP_USAGE, GIT_HISTORY_REWORD_USAGE, GIT_HISTORY_SPLIT_USAGE, @@ -988,6 +1198,7 @@ int cmd_history(int argc, }; parse_opt_subcommand_fn *fn = NULL; struct option options[] = { + OPT_SUBCOMMAND("drop", &fn, cmd_history_drop), OPT_SUBCOMMAND("fixup", &fn, cmd_history_fixup), OPT_SUBCOMMAND("reword", &fn, cmd_history_reword), OPT_SUBCOMMAND("split", &fn, cmd_history_split), diff --git a/builtin/index-pack.c b/builtin/index-pack.c index cf0bd8280dca83..bc86925ad04340 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -71,7 +71,7 @@ struct base_data { /* Not initialized by make_base(). */ struct list_head list; void *data; - unsigned long size; + size_t size; }; /* @@ -258,7 +258,7 @@ static unsigned check_object(struct object *obj) return 0; if (!(obj->flags & FLAG_CHECKED)) { - unsigned long size; + size_t size; int type = odb_read_object_info(the_repository->objects, &obj->oid, &size); if (type <= 0) @@ -374,7 +374,7 @@ static const char *open_pack_file(const char *pack_name) output_fd = -1; nothread_data.pack_fd = input_fd; } - the_hash_algo->init_fn(&input_ctx); + git_hash_init(&input_ctx, the_hash_algo); return pack_name; } @@ -481,7 +481,7 @@ static void *unpack_entry_data(off_t offset, size_t size, if (!is_delta_type(type)) { hdrlen = format_object_header(hdr, sizeof(hdr), type, size); - the_hash_algo->init_fn(&c); + git_hash_init(&c, the_hash_algo); git_hash_update(&c, hdr, hdrlen); } else oid = NULL; @@ -905,7 +905,7 @@ static void sha1_object(const void *data, struct object_entry *obj_entry, if (collision_test_needed) { void *has_data; enum object_type has_type; - unsigned long has_size; + size_t has_size; read_lock(); has_type = odb_read_object_info(the_repository->objects, oid, &has_size); if (has_type < 0) @@ -1048,7 +1048,7 @@ static struct base_data *resolve_delta(struct object_entry *delta_obj, { void *delta_data, *result_data; struct base_data *result; - unsigned long result_size; + size_t result_size; if (show_stat) { int i = delta_obj - objects; @@ -1212,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 @@ -1292,7 +1291,7 @@ static void parse_pack_objects(unsigned char *hash) /* Check pack integrity */ flush(); - the_hash_algo->init_fn(&tmp_ctx); + git_hash_init(&tmp_ctx, the_hash_algo); git_hash_clone(&tmp_ctx, &input_ctx); git_hash_final(hash, &tmp_ctx); if (!hasheq(fill(the_hash_algo->rawsz), hash, the_repository->hash_algo)) @@ -1417,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; @@ -1515,7 +1515,7 @@ static void fix_unresolved_deltas(struct hashfile *f) struct ref_delta_entry *d = sorted_by_pos[i]; enum object_type type; void *data; - unsigned long size; + size_t size; if (objects[d->obj_no].real_type != OBJ_REF_DELTA) continue; @@ -1825,11 +1825,16 @@ static void repack_local_links(void) oidset_iter_init(&outgoing_links, &iter); while ((oid = oidset_iter_next(&iter))) { - struct object_info info = OBJECT_INFO_INIT; + struct odb_source_info source_info; + struct object_info info = { + .source_infop = &source_info, + }; + if (odb_read_object_info_extended(the_repository->objects, oid, &info, 0)) /* Missing; assume it is a promisor object */ continue; - if (info.whence == OI_PACKED && info.u.packed.pack->pack_promisor) + if (source_info.source->type == ODB_SOURCE_PACKED && + source_info.u.packed.pack->pack_promisor) continue; if (!cmd.args.nr) { diff --git a/builtin/init-db.c b/builtin/init-db.c index c55517ad94d447..e96b1283b7010d 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -81,6 +81,7 @@ int cmd_init_db(int argc, const char *template_dir = NULL; char *template_dir_to_free = NULL; unsigned int flags = 0; + int bare = startup_info->force_bare_repository ? 1 : -1; const char *object_format = NULL; const char *ref_format = NULL; const char *initial_branch = NULL; @@ -90,7 +91,7 @@ int cmd_init_db(int argc, const struct option init_db_options[] = { OPT_STRING(0, "template", &template_dir, N_("template-directory"), N_("directory from which templates will be used")), - OPT_SET_INT(0, "bare", &is_bare_repository_cfg, + OPT_SET_INT(0, "bare", &bare, N_("create a bare repository"), 1), { .type = OPTION_CALLBACK, @@ -116,7 +117,7 @@ int cmd_init_db(int argc, argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0); - if (real_git_dir && is_bare_repository_cfg == 1) + if (real_git_dir && bare == 1) die(_("options '%s' and '%s' cannot be used together"), "--separate-git-dir", "--bare"); if (real_git_dir && !is_absolute_path(real_git_dir)) @@ -160,7 +161,7 @@ int cmd_init_db(int argc, } else if (0 < argc) { usage(init_db_usage[0]); } - if (is_bare_repository_cfg == 1) { + if (bare == 1) { char *cwd = xgetcwd(); setenv(GIT_DIR_ENVIRONMENT, cwd, argc > 0); free(cwd); @@ -187,7 +188,7 @@ int cmd_init_db(int argc, */ git_dir = xstrdup_or_null(getenv(GIT_DIR_ENVIRONMENT)); work_tree = xstrdup_or_null(getenv(GIT_WORK_TREE_ENVIRONMENT)); - if ((!git_dir || is_bare_repository_cfg == 1) && work_tree) + if ((!git_dir || bare == 1) && work_tree) die(_("%s (or --work-tree=) not allowed without " "specifying %s (or --git-dir=)"), GIT_WORK_TREE_ENVIRONMENT, @@ -224,36 +225,31 @@ int cmd_init_db(int argc, strbuf_release(&sb); } - if (is_bare_repository_cfg < 0) - is_bare_repository_cfg = guess_repository_type(git_dir); + if (bare < 0) + bare = guess_repository_type(git_dir); - if (!is_bare_repository_cfg) { + if (!bare) { const char *git_dir_parent = strrchr(git_dir, '/'); - if (git_dir_parent) { - char *rel = xstrndup(git_dir, git_dir_parent - git_dir); - git_work_tree_cfg = real_pathdup(rel, 1); - free(rel); + + if (!work_tree) { + if (git_dir_parent) { + char *rel = xstrndup(git_dir, git_dir_parent - git_dir); + work_tree = real_pathdup(rel, 1); + free(rel); + } else { + work_tree = xgetcwd(); + } } - if (!git_work_tree_cfg) - git_work_tree_cfg = xgetcwd(); - if (work_tree) - set_git_work_tree(the_repository, work_tree); - else - 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)); - } - else { - if (real_git_dir) - die(_("--separate-git-dir incompatible with bare repository")); - if (work_tree) - set_git_work_tree(the_repository, work_tree); + + if (access(work_tree, X_OK)) + die_errno (_("Cannot access work tree '%s'"), work_tree); + } else if (real_git_dir) { + die(_("--separate-git-dir incompatible with bare repository")); } flags |= INIT_DB_EXIST_OK; - ret = init_db(the_repository, git_dir, real_git_dir, template_dir, hash_algo, - ref_storage_format, initial_branch, + ret = init_db(the_repository, git_dir, real_git_dir, work_tree, + template_dir, hash_algo, ref_storage_format, initial_branch, init_shared_repository, flags); free(template_dir_to_free); diff --git a/builtin/last-modified.c b/builtin/last-modified.c index 8900ceece1cdf3..5478182f2e95c2 100644 --- a/builtin/last-modified.c +++ b/builtin/last-modified.c @@ -344,6 +344,7 @@ static void process_parent(struct last_modified *lm, static int last_modified_run(struct last_modified *lm) { int max_count, queue_popped = 0; + struct commit *c, *n; struct prio_queue queue = { compare_commits_by_gen_then_commit_date }; struct prio_queue not_queue = { compare_commits_by_gen_then_commit_date }; struct commit_list *list; @@ -389,10 +390,9 @@ static int last_modified_run(struct last_modified *lm) } } - while (queue.nr) { + while ((c = prio_queue_get(&queue))) { int parent_i; struct commit_list *p; - struct commit *c = prio_queue_get(&queue); struct bitmap *active_c = active_paths_for(lm, c); if ((0 <= max_count && max_count < ++queue_popped) || @@ -416,9 +416,8 @@ static int last_modified_run(struct last_modified *lm) */ repo_parse_commit(lm->rev.repo, c); - while (not_queue.nr) { + while ((n = prio_queue_get(¬_queue))) { struct commit_list *np; - struct commit *n = prio_queue_get(¬_queue); repo_parse_commit(lm->rev.repo, n); diff --git a/builtin/log.c b/builtin/log.c index e464b30af4bcae..59ecd34334b3bd 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -209,6 +209,7 @@ static void cmd_log_init_defaults(struct rev_info *rev, init_diffstat_widths(&rev->diffopt); rev->diffopt.flags.recursive = 1; rev->diffopt.flags.allow_textconv = 1; + rev->diffopt.flags.allow_diff_process = 1; rev->abbrev_commit = cfg->default_abbrev_commit; rev->show_root_diff = cfg->default_show_root; rev->subject_prefix = cfg->fmt_patch_subject_prefix; @@ -613,7 +614,7 @@ static int show_blob_object(const struct object_id *oid, struct rev_info *rev, c static int show_tag_object(const struct object_id *oid, struct rev_info *rev) { - unsigned long size; + size_t size; enum object_type type; char *buf = odb_read_object(the_repository->objects, oid, &type, &size); unsigned long offset = 0; @@ -693,8 +694,11 @@ int cmd_show(int argc, opt.tweak = show_setup_revisions_tweak; cmd_log_init(argc, argv, prefix, &rev, &opt, &cfg); + diff_hunks_attach(&rev.diffopt); + if (!rev.no_walk) { ret = cmd_log_walk(&rev); + diff_hunks_detach(&rev.diffopt); release_revisions(&rev); log_config_release(&cfg); return ret; @@ -767,6 +771,7 @@ int cmd_show(int argc, } rev.diffopt.no_free = 0; + diff_hunks_detach(&rev.diffopt); diff_free(&rev.diffopt); release_revisions(&rev); log_config_release(&cfg); @@ -846,8 +851,11 @@ int cmd_log(int argc, opt.tweak = log_setup_revisions_tweak; cmd_log_init(argc, argv, prefix, &rev, &opt, &cfg); + diff_hunks_attach(&rev.diffopt); + ret = cmd_log_walk(&rev); + diff_hunks_detach(&rev.diffopt); release_revisions(&rev); log_config_release(&cfg); return ret; @@ -1888,6 +1896,7 @@ static void prepare_bases(struct base_tree_info *bases, bases->nr_patch_id++; } clear_commit_base(&commit_base); + release_revisions(&revs); } static void print_bases(struct base_tree_info *bases, FILE *file) @@ -2217,6 +2226,17 @@ int cmd_format_patch(int argc, if (argc > 1) die(_("unrecognized argument: %s"), argv[1]); + /* + * Patches generated by format-patch must be based on the builtin + * diff so recipients without the store or the process can apply + * them, and so the emitted diffstat does not depend on the sender's + * local cache: the precomputed-hunks store is not consulted for the + * diffstat, and the diff process is not consulted even when + * --ext-diff enables the external diff command. + */ + rev.diffopt.flags.no_precomputed_hunks = 1; + rev.diffopt.flags.allow_diff_process = 0; + if (rev.diffopt.output_format & DIFF_FORMAT_NAME) die(_("--name-only does not make sense")); if (rev.diffopt.output_format & DIFF_FORMAT_NAME_STATUS) diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 12d5d828ff581a..b044520f9e3c39 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -256,7 +256,7 @@ static void expand_objectsize(struct repository *repo, struct strbuf *line, size_t len; if (type == OBJ_BLOB) { - unsigned long size; + size_t size; if (odb_read_object_info(repo->objects, oid, &size) < 0) die(_("could not get object info about '%s'"), oid_to_hex(oid)); @@ -453,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); diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 57846911ce443f..46edaffc2e73cd 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -32,7 +32,7 @@ static void expand_objectsize(struct strbuf *line, const struct object_id *oid, size_t len; if (type == OBJ_BLOB) { - unsigned long size; + size_t size; if (odb_read_object_info(the_repository->objects, oid, &size) < 0) die(_("could not get object info about '%s'"), oid_to_hex(oid)); @@ -220,7 +220,7 @@ static int show_tree_long(const struct object_id *oid, struct strbuf *base, return early; if (type == OBJ_BLOB) { - unsigned long size; + size_t size; if (odb_read_object_info(the_repository->objects, oid, &size) == OBJ_BAD) xsnprintf(size_text, sizeof(size_text), "BAD"); else diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c index 264df6259a0411..0993418e630dec 100644 --- a/builtin/mailsplit.c +++ b/builtin/mailsplit.c @@ -225,14 +225,14 @@ static int split_mbox(const char *file, const char *dir, int allow_bare, FILE *f = !strcmp(file, "-") ? stdin : fopen(file, "r"); int file_done = 0; - if (isatty(fileno(f))) - warning(_("reading patches from stdin/tty...")); - if (!f) { error_errno("cannot open mbox %s", file); goto out; } + if (isatty(fileno(f))) + warning(_("reading patches from stdin/tty...")); + do { peek = fgetc(f); if (peek == EOF) { diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index 312b595d1e7ad2..49f41e520f1055 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -69,7 +69,7 @@ static const char *explanation(struct merge_list *entry) return "removed in remote"; } -static void *result(struct merge_list *entry, unsigned long *size) +static void *result(struct merge_list *entry, size_t *size) { enum object_type type; struct blob *base, *our, *their; @@ -96,7 +96,7 @@ static void *result(struct merge_list *entry, unsigned long *size) base, our, their, size); } -static void *origin(struct merge_list *entry, unsigned long *size) +static void *origin(struct merge_list *entry, size_t *size) { enum object_type type; while (entry) { @@ -119,7 +119,7 @@ static int show_outf(void *priv UNUSED, mmbuffer_t *mb, int nbuf) static void show_diff(struct merge_list *entry) { - unsigned long size; + size_t size; mmfile_t src, dst; xpparam_t xpp; xdemitconf_t xecfg; diff --git a/builtin/mktag.c b/builtin/mktag.c index f40264a87876f4..37c17e6beb8d93 100644 --- a/builtin/mktag.c +++ b/builtin/mktag.c @@ -50,7 +50,7 @@ static int verify_object_in_tag(struct object_id *tagged_oid, int *tagged_type) { int ret; enum object_type type; - unsigned long size; + size_t size; void *buffer; const struct object_id *repl; diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c index 00ffb36394d08c..6e73c85cde324f 100644 --- a/builtin/multi-pack-index.c +++ b/builtin/multi-pack-index.c @@ -10,6 +10,7 @@ #include "trace2.h" #include "odb.h" #include "odb/source.h" +#include "odb/source-files.h" #include "replace-object.h" #include "repository.h" @@ -85,12 +86,12 @@ static int parse_object_dir(const struct option *opt, const char *arg, return 0; } -static struct odb_source *handle_object_dir_option(struct repository *repo) +static struct odb_source_files *handle_object_dir_option(struct repository *repo) { struct odb_source *source = odb_find_source(repo->objects, opts.object_dir); if (!source) source = odb_add_to_alternates_memory(repo->objects, opts.object_dir); - return source; + return odb_source_files_downcast(source); } static struct option common_opts[] = { @@ -167,7 +168,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv, N_("refs snapshot for selecting bitmap commits")), OPT_END(), }; - struct odb_source *source; + struct odb_source_files *source; int ret; opts.flags |= MIDX_WRITE_BITMAP_HASH_CACHE; @@ -211,7 +212,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv, read_packs_from_stdin(&packs); - ret = write_midx_file_only(source, &packs, + ret = write_midx_file_only(source->packed, &packs, opts.preferred_pack, opts.refs_snapshot, opts.incremental_base, opts.flags); @@ -223,7 +224,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv, } - ret = write_midx_file(source, opts.preferred_pack, + ret = write_midx_file(source->packed, opts.preferred_pack, opts.refs_snapshot, opts.flags); free(opts.refs_snapshot); @@ -237,7 +238,7 @@ static int cmd_multi_pack_index_compact(int argc, const char **argv, struct multi_pack_index *m, *cur; struct multi_pack_index *from_midx = NULL; struct multi_pack_index *to_midx = NULL; - struct odb_source *source; + struct odb_source_files *source; int ret; struct option *options; @@ -282,7 +283,7 @@ static int cmd_multi_pack_index_compact(int argc, const char **argv, FREE_AND_NULL(options); - m = get_multi_pack_index(source); + m = get_multi_pack_index(source->packed); for (cur = m; cur && !(from_midx && to_midx); cur = cur->base_midx) { const char *midx_csum = midx_get_checksum_hex(cur); @@ -305,7 +306,7 @@ 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, + ret = write_midx_file_compact(source->packed, from_midx, to_midx, opts.incremental_base, opts.flags); return ret; @@ -319,7 +320,7 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv, static struct option builtin_multi_pack_index_verify_options[] = { OPT_END(), }; - struct odb_source *source; + struct odb_source_files *source; options = add_common_options(builtin_multi_pack_index_verify_options); @@ -337,7 +338,7 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv, FREE_AND_NULL(options); - return verify_midx_file(source, opts.flags); + return verify_midx_file(source->packed, opts.flags); } static int cmd_multi_pack_index_expire(int argc, const char **argv, @@ -348,7 +349,7 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv, static struct option builtin_multi_pack_index_expire_options[] = { OPT_END(), }; - struct odb_source *source; + struct odb_source_files *source; options = add_common_options(builtin_multi_pack_index_expire_options); @@ -366,7 +367,7 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv, FREE_AND_NULL(options); - return expire_midx_packs(source, opts.flags); + return expire_midx_packs(source->packed, opts.flags); } static int cmd_multi_pack_index_repack(int argc, const char **argv, @@ -379,7 +380,7 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv, N_("during repack, collect pack-files of smaller size into a batch that is larger than this size")), OPT_END(), }; - struct odb_source *source; + struct odb_source_files *source; options = add_common_options(builtin_multi_pack_index_repack_options); @@ -398,7 +399,7 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv, FREE_AND_NULL(options); - return midx_repack(source, (size_t)opts.batch_size, opts.flags); + return midx_repack(source->packed, (size_t)opts.batch_size, opts.flags); } int cmd_multi_pack_index(int argc, diff --git a/builtin/mv.c b/builtin/mv.c index 948b3306390337..a82fc97a19f6ee 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -419,7 +419,7 @@ int cmd_mv(int argc, goto act_on_entry; } if (lstat(dst, &st) == 0 && - (!ignore_case || strcasecmp(src, dst))) { + (!repo_ignore_case(the_repository) || strcasecmp(src, dst))) { bad = _("destination exists"); if (force) { /* @@ -575,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 ( is out-of-cone and diff --git a/builtin/notes.c b/builtin/notes.c index 9af602bdd7b402..962df867c85843 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -150,7 +150,7 @@ static int list_each_note(const struct object_id *object_oid, static void copy_obj_to_fd(int fd, const struct object_id *oid) { - unsigned long size; + size_t size; enum object_type type; char *buf = odb_read_object(the_repository->objects, oid, &type, &size); if (buf) { @@ -313,7 +313,7 @@ static int parse_reuse_arg(const struct option *opt, const char *arg, int unset) char *value; struct object_id object; enum object_type type; - unsigned long len; + size_t len; BUG_ON_OPT_NEG(unset); @@ -721,7 +721,7 @@ static int append_edit(int argc, const char **argv, const char *prefix, if (note && !edit) { /* Append buf to previous note contents */ - unsigned long size; + size_t size; enum object_type type; struct strbuf buf = STRBUF_INIT; char *prev_buf = odb_read_object(the_repository->objects, note, &type, &size); diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index fe9fbecb30e100..3673b14b89b275 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -66,8 +66,8 @@ static inline struct object_entry *oe_delta( return &pack->objects[e->delta_idx - 1]; } -static inline unsigned long oe_delta_size(struct packing_data *pack, - const struct object_entry *e) +static inline size_t oe_delta_size(struct packing_data *pack, + const struct object_entry *e) { if (e->delta_size_valid) return e->delta_size_; @@ -83,11 +83,11 @@ static inline unsigned long oe_delta_size(struct packing_data *pack, return pack->delta_size[e - pack->objects]; } -unsigned long oe_get_size_slow(struct packing_data *pack, - const struct object_entry *e); +size_t oe_get_size_slow(struct packing_data *pack, + const struct object_entry *e); -static inline unsigned long oe_size(struct packing_data *pack, - const struct object_entry *e) +static inline size_t oe_size(struct packing_data *pack, + const struct object_entry *e) { if (e->size_valid) return e->size_; @@ -145,7 +145,7 @@ static inline void oe_set_delta_sibling(struct packing_data *pack, static inline void oe_set_size(struct packing_data *pack, struct object_entry *e, - unsigned long size) + size_t size) { if (size < pack->oe_size_limit) { e->size_ = size; @@ -159,7 +159,7 @@ static inline void oe_set_size(struct packing_data *pack, static inline void oe_set_delta_size(struct packing_data *pack, struct object_entry *e, - unsigned long size) + size_t size) { if (size < pack->oe_delta_size_limit) { e->delta_size_ = size; @@ -356,14 +356,17 @@ static void *get_delta(struct object_entry *entry) unsigned long size, base_size, delta_size; void *buf, *base_buf, *delta_buf; enum object_type type; + size_t size_st = 0, base_size_st = 0; buf = odb_read_object(the_repository->objects, &entry->idx.oid, - &type, &size); + &type, &size_st); + size = cast_size_t_to_ulong(size_st); if (!buf) die(_("unable to read %s"), oid_to_hex(&entry->idx.oid)); base_buf = odb_read_object(the_repository->objects, &DELTA(entry)->idx.oid, &type, - &base_size); + &base_size_st); + base_size = cast_size_t_to_ulong(base_size_st); if (!base_buf) die("unable to read %s", oid_to_hex(&DELTA(entry)->idx.oid)); @@ -386,8 +389,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 +417,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; @@ -453,7 +458,7 @@ static int check_pack_inflate(struct packed_git *p, struct pack_window **w_curs, off_t offset, off_t len, - unsigned long expect) + size_t expect) { git_zstream stream; unsigned char fakebuf[4096], *in; @@ -496,7 +501,7 @@ static void copy_pack_data(struct hashfile *f, static inline int oe_size_greater_than(struct packing_data *pack, const struct object_entry *lhs, - unsigned long rhs) + size_t rhs) { if (lhs->size_valid) return lhs->size_ > rhs; @@ -528,9 +533,11 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent type = st->type; size = st->size; } else { + size_t size_st = 0; buf = odb_read_object(the_repository->objects, &entry->idx.oid, &type, - &size); + &size_st); + size = cast_size_t_to_ulong(size_st); if (!buf) die(_("unable to read %s"), oid_to_hex(&entry->idx.oid)); @@ -671,8 +678,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry, datalen -= entry->in_pack_header_size; if (!pack_to_stdout && p->index_version == 1 && - check_pack_inflate(p, &w_curs, offset, datalen, - cast_size_t_to_ulong(entry_size))) { + check_pack_inflate(p, &w_curs, offset, datalen, entry_size)) { error(_("corrupt packed object for %s"), oid_to_hex(&entry->idx.oid)); unuse_pack(&w_curs); @@ -1349,7 +1355,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 +1756,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; + } } /* @@ -1773,7 +1781,8 @@ static int want_object_in_pack_mtime(const struct object_id *oid, odb_prepare_alternates(the_repository->objects); for (source = the_repository->objects->sources; source; source = source->next) { - struct multi_pack_index *m = get_multi_pack_index(source); + struct odb_source_files *files = odb_source_files_downcast(source); + struct multi_pack_index *m = get_multi_pack_index(files->packed); struct pack_entry e; if (m && fill_midx_entry(m, oid, &e)) { @@ -1858,8 +1867,8 @@ static const char no_closure_warning[] = N_( "disabling bitmap writing, as some objects are not being packed" ); -static int add_object_entry(const struct object_id *oid, enum object_type type, - const char *name, int exclude) +static void add_object_entry(const struct object_id *oid, enum object_type type, + const char *name, int exclude) { struct packed_git *found_pack = NULL; off_t found_offset = 0; @@ -1867,7 +1876,7 @@ static int add_object_entry(const struct object_id *oid, enum object_type type, display_progress(progress_state, ++nr_seen); if (have_duplicate_entry(oid, exclude)) - return 0; + return; if (!want_object_in_pack(oid, exclude, &found_pack, &found_offset)) { /* The pack is missing an object, so it will not have closure */ @@ -1876,13 +1885,12 @@ static int add_object_entry(const struct object_id *oid, enum object_type type, warning(_(no_closure_warning)); write_bitmap_index = 0; } - return 0; + return; } create_object_entry(oid, type, pack_name_hash_fn(name), exclude, name && no_try_delta(name), found_pack, found_offset); - return 1; } static int add_object_entry_from_bitmap(const struct object_id *oid, @@ -1900,7 +1908,7 @@ static int add_object_entry_from_bitmap(const struct object_id *oid, return 0; create_object_entry(oid, type, name_hash, 0, 0, pack, offset); - return 1; + return 0; } struct pbase_tree_cache { @@ -1936,6 +1944,7 @@ static struct pbase_tree_cache *pbase_tree_get(const struct object_id *oid) struct pbase_tree_cache *ent, *nent; void *data; unsigned long size; + size_t size_st = 0; enum object_type type; int neigh; int my_ix = pbase_tree_cache_ix(oid); @@ -1963,7 +1972,8 @@ static struct pbase_tree_cache *pbase_tree_get(const struct object_id *oid) /* Did not find one. Either we got a bogus request or * we need to read and perhaps cache. */ - data = odb_read_object(the_repository->objects, oid, &type, &size); + data = odb_read_object(the_repository->objects, oid, &type, &size_st); + size = cast_size_t_to_ulong(size_st); if (!data) return NULL; if (type != OBJ_TREE) { @@ -2118,13 +2128,15 @@ static void add_preferred_base(struct object_id *oid) struct pbase_tree *it; void *data; unsigned long size; + size_t size_st = 0; struct object_id tree_oid; if (window <= num_preferred_base++) return; data = odb_read_object_peeled(the_repository->objects, oid, - OBJ_TREE, &size, &tree_oid); + OBJ_TREE, &size_st, &tree_oid); + size = cast_size_t_to_ulong(size_st); if (!data) return; @@ -2236,7 +2248,7 @@ static void prefetch_to_pack(uint32_t object_index_start) { static void check_object(struct object_entry *entry, uint32_t object_index) { - unsigned long canonical_size; + size_t canonical_size; enum object_type type; struct object_info oi = {.typep = &type, .sizep = &canonical_size}; @@ -2278,7 +2290,7 @@ static void check_object(struct object_entry *entry, uint32_t object_index) default: /* Not a delta hence we've already got all we need. */ oe_set_type(entry, entry->in_pack_type); - SET_SIZE(entry, cast_size_t_to_ulong(in_pack_size)); + SET_SIZE(entry, in_pack_size); entry->in_pack_header_size = used; if (oe_type(entry) < OBJ_COMMIT || oe_type(entry) > OBJ_BLOB) goto give_up; @@ -2332,8 +2344,8 @@ static void check_object(struct object_entry *entry, uint32_t object_index) if (have_base && can_reuse_delta(&base_ref, entry, &base_entry)) { oe_set_type(entry, entry->in_pack_type); - SET_SIZE(entry, cast_size_t_to_ulong(in_pack_size)); /* delta size */ - SET_DELTA_SIZE(entry, cast_size_t_to_ulong(in_pack_size)); + SET_SIZE(entry, in_pack_size); /* delta size */ + SET_DELTA_SIZE(entry, in_pack_size); if (base_entry) { SET_DELTA(entry, base_entry); @@ -2356,7 +2368,8 @@ static void check_object(struct object_entry *entry, uint32_t object_index) * object size from the delta header. */ delta_pos = entry->in_pack_offset + entry->in_pack_header_size; - canonical_size = get_size_from_delta(p, &w_curs, delta_pos); + canonical_size = get_size_from_delta(p, &w_curs, + delta_pos); if (canonical_size == 0) goto give_up; SET_SIZE(entry, canonical_size); @@ -2434,7 +2447,7 @@ static void drop_reused_delta(struct object_entry *entry) unsigned *idx = &to_pack.objects[entry->delta_idx - 1].delta_child_idx; struct object_info oi = OBJECT_INFO_INIT; enum object_type type; - unsigned long size; + size_t size; while (*idx) { struct object_entry *oe = &to_pack.objects[*idx - 1]; @@ -2449,7 +2462,7 @@ static void drop_reused_delta(struct object_entry *entry) oi.sizep = &size; oi.typep = &type; - if (packed_object_info(IN_PACK(entry), entry->in_pack_offset, &oi) < 0) { + if (packed_object_info(NULL, IN_PACK(entry), entry->in_pack_offset, &oi) < 0) { /* * We failed to get the info from this pack for some reason; * fall back to odb_read_object_info, which may find another copy. @@ -2712,7 +2725,7 @@ static pthread_mutex_t progress_mutex; static inline int oe_size_less_than(struct packing_data *pack, const struct object_entry *lhs, - unsigned long rhs) + size_t rhs) { if (lhs->size_valid) return lhs->size_ < rhs; @@ -2730,13 +2743,29 @@ static inline void oe_set_tree_depth(struct packing_data *pack, pack->tree_depth[e - pack->objects] = tree_depth; } +static void record_tree_depth(const struct object_id *oid, const char *name) +{ + const char *p; + unsigned depth; + struct object_entry *ent; + + /* the empty string is a root tree, which is depth 0 */ + depth = *name ? 1 : 0; + for (p = strchr(name, '/'); p; p = strchr(p + 1, '/')) + depth++; + + ent = packlist_find(&to_pack, oid); + if (ent && depth > oe_tree_depth(&to_pack, ent)) + oe_set_tree_depth(&to_pack, ent, depth); +} + /* * Return the size of the object without doing any delta * reconstruction (so non-deltas are true object sizes, but deltas * return the size of the delta data). */ -unsigned long oe_get_size_slow(struct packing_data *pack, - const struct object_entry *e) +size_t oe_get_size_slow(struct packing_data *pack, + const struct object_entry *e) { struct packed_git *p; struct pack_window *w_curs; @@ -2746,7 +2775,7 @@ unsigned long oe_get_size_slow(struct packing_data *pack, size_t size; if (e->type_ != OBJ_OFS_DELTA && e->type_ != OBJ_REF_DELTA) { - unsigned long sz; + size_t sz; packing_data_lock(&to_pack); if (odb_read_object_info(the_repository->objects, &e->idx.oid, &sz) < 0) @@ -2770,7 +2799,7 @@ unsigned long oe_get_size_slow(struct packing_data *pack, unuse_pack(&w_curs); packing_data_unlock(&to_pack); - return cast_size_t_to_ulong(size); + return size; } static int try_delta(struct unpacked *trg, struct unpacked *src, @@ -2831,10 +2860,12 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, /* Load data if not already done */ if (!trg->data) { + size_t sz_st = 0; packing_data_lock(&to_pack); trg->data = odb_read_object(the_repository->objects, &trg_entry->idx.oid, &type, - &sz); + &sz_st); + sz = cast_size_t_to_ulong(sz_st); packing_data_unlock(&to_pack); if (!trg->data) die(_("object %s cannot be read"), @@ -2846,10 +2877,12 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, *mem_usage += sz; } if (!src->data) { + size_t sz_st = 0; packing_data_lock(&to_pack); src->data = odb_read_object(the_repository->objects, &src_entry->idx.oid, &type, - &sz); + &sz_st); + sz = cast_size_t_to_ulong(sz_st); packing_data_unlock(&to_pack); if (!src->data) { if (src_entry->preferred_base) { @@ -3786,7 +3819,7 @@ static int add_object_entry_from_pack(const struct object_id *oid, ofs = nth_packed_object_offset(p, pos); oi.typep = &type; - if (packed_object_info(p, ofs, &oi) < 0) { + if (packed_object_info(NULL, p, ofs, &oi) < 0) { die(_("could not get type of object %s in pack %s"), oid_to_hex(oid), p->pack_name); } else if (type == OBJ_COMMIT) { @@ -4135,9 +4168,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 +4310,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) @@ -4385,20 +4421,8 @@ static void show_object(struct object *obj, const char *name, add_preferred_base_object(name); add_object_entry(&obj->oid, obj->type, name, 0); - if (use_delta_islands) { - const char *p; - unsigned depth; - struct object_entry *ent; - - /* the empty string is a root tree, which is depth 0 */ - depth = *name ? 1 : 0; - for (p = strchr(name, '/'); p; p = strchr(p + 1, '/')) - depth++; - - ent = packlist_find(&to_pack, &obj->oid); - if (ent && depth > oe_tree_depth(&to_pack, ent)) - oe_set_tree_depth(&to_pack, ent, depth); - } + if (use_delta_islands) + record_tree_depth(&obj->oid, name); } static void show_object__ma_allow_any(struct object *obj, const char *name, void *data) @@ -4470,8 +4494,9 @@ static int add_object_in_unpacked_pack(const struct object_id *oid, void *data UNUSED) { if (cruft) { - add_cruft_object_entry(oid, OBJ_NONE, oi->u.packed.pack, - oi->u.packed.offset, NULL, *oi->mtimep); + add_cruft_object_entry(oid, OBJ_NONE, oi->source_infop->u.packed.pack, + oi->source_infop->u.packed.offset, NULL, + *oi->mtimep); } else { add_object_entry(oid, OBJ_NONE, "", 0); } @@ -4488,8 +4513,10 @@ static void add_objects_in_unpacked_packs(void) ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS | ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS, }; + struct odb_source_info source_info; struct object_info oi = { .mtimep = &mtime, + .source_infop = &source_info, }; odb_prepare_alternates(to_pack.repo->objects); @@ -4499,8 +4526,8 @@ static void add_objects_in_unpacked_packs(void) if (!source->local) continue; - if (packfile_store_for_each_object(files->packed, &oi, - add_object_in_unpacked_pack, NULL, &opts)) + if (odb_source_for_each_object(&files->packed->base, &oi, + add_object_in_unpacked_pack, NULL, &opts)) die(_("cannot open pack index")); } } @@ -4742,6 +4769,31 @@ static int add_objects_by_path(const char *path, continue; add_object_entry(oid, type, path, exclude); + + if (type == OBJ_COMMIT) { + struct commit *commit; + + if (!write_bitmap_index && !use_delta_islands) + continue; + + commit = lookup_commit(the_repository, oid); + if (!commit) + die(_("could not find commit %s"), oid_to_hex(oid)); + if (write_bitmap_index) + index_commit_for_bitmap(commit); + /* + * Skip island propagation for boundary commits. + * The regular traversal's show_commit() is only + * called for interesting commits; matching that + * here keeps path-walk from doing extra work that + * would only be a no-op anyway (boundary commits + * are not in island_marks). + */ + if (use_delta_islands && !exclude) + propagate_island_marks(the_repository, commit); + } else if (type == OBJ_TREE && use_delta_islands) { + record_tree_depth(oid, path); + } } oe_end = to_pack.nr_objects; @@ -4774,6 +4826,13 @@ static int get_object_list_path_walk(struct rev_info *revs) info.path_fn = add_objects_by_path; info.path_fn_data = &processed; + /* + * Path-walk needs boundary commits to discover thin-pack bases, but + * bitmap traversal does not understand the boundary state. Set it + * here so any prior bitmap attempt sees the usual non-boundary walk. + */ + revs->boundary = 1; + /* * Allow the --[no-]sparse option to be interesting here, if only * for testing purposes. Paths with no interesting objects will not @@ -4797,6 +4856,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; @@ -4807,8 +4867,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); @@ -4836,7 +4896,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; @@ -4978,10 +5038,14 @@ static int option_parse_cruft_expiration(const struct option *opt UNUSED, static int is_not_in_promisor_pack_obj(struct object *obj, void *data UNUSED) { - struct object_info info = OBJECT_INFO_INIT; + struct odb_source_info source_info; + struct object_info info = { + .source_infop = &source_info, + }; + if (odb_read_object_info_extended(the_repository->objects, &obj->oid, &info, 0)) BUG("should_include_obj should only be called on existing objects"); - return info.whence != OI_PACKED || !info.u.packed.pack->pack_promisor; + return source_info.source->type != ODB_SOURCE_PACKED || !source_info.u.packed.pack->pack_promisor; } static int is_not_in_promisor_pack(struct commit *commit, void *data) { @@ -5019,6 +5083,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, @@ -5100,7 +5165,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")), @@ -5195,8 +5260,6 @@ int cmd_pack_objects(int argc, const char *option = NULL; if (!path_walk_filter_compatible(&filter_options)) option = "--filter"; - else if (use_delta_islands) - option = "--delta-islands"; if (option) { warning(_("cannot use %s with %s"), @@ -5205,9 +5268,7 @@ int cmd_pack_objects(int argc, } } if (path_walk) { - strvec_push(&rp, "--boundary"); strvec_push(&rp, "--objects"); - use_bitmap_index = 0; } else if (thin) { use_internal_rev_list = 1; strvec_push(&rp, shallow @@ -5256,10 +5317,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/patch-id.c b/builtin/patch-id.c index 2781598ede6ea0..22f36ecf80c824 100644 --- a/builtin/patch-id.c +++ b/builtin/patch-id.c @@ -73,7 +73,7 @@ static size_t get_one_patchid(struct object_id *next_oid, struct object_id *resu char pre_oid_str[GIT_MAX_HEXSZ + 1], post_oid_str[GIT_MAX_HEXSZ + 1]; struct git_hash_ctx ctx; - the_hash_algo->init_fn(&ctx); + git_hash_init(&ctx, the_hash_algo); oidclr(result, the_repository->hash_algo); while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) { @@ -173,6 +173,7 @@ static size_t get_one_patchid(struct object_id *next_oid, struct object_id *resu oidclr(next_oid, the_repository->hash_algo); flush_one_hunk(result, &ctx); + git_hash_discard(&ctx); return patchlen; } diff --git a/builtin/push.c b/builtin/push.c index 6021b71d668455..1b2ad3b8df7c55 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -8,6 +8,7 @@ #include "advice.h" #include "branch.h" #include "config.h" +#include "dir.h" #include "environment.h" #include "gettext.h" #include "hex.h" @@ -662,6 +663,29 @@ static int push_multiple(struct string_list *list, return result; } +static void die_if_repo_looks_like_ref(const char *repo) +{ + const char *slash = strchr(repo, '/'); + struct strbuf name = STRBUF_INIT; + int code; + + if (!slash || !slash[1] || file_exists(repo)) + return; + + strbuf_add(&name, repo, slash - repo); + if (!remote_is_configured(remote_get(name.buf), 0)) { + strbuf_release(&name); + return; + } + + code = die_message(_("'%s' is not a valid push target"), repo); + advise_if_enabled(ADVICE_PUSH_REPO_LOOKS_LIKE_REF, + _("Did you mean to use: git push %s %s?"), + name.buf, slash + 1); + strbuf_release(&name); + exit(code); +} + int cmd_push(int argc, const char **argv, const char *prefix, @@ -744,6 +768,17 @@ int cmd_push(int argc, if (repo) { if (!add_remote_or_group(repo, &remote_group)) { + struct remote *r; + + /* + * Check the advice up front to avoid the remote + * lookup when the hint is off. The helper still + * calls advise_if_enabled() so the hint carries the + * standard "disable this message" instructions. + */ + if (advice_enabled(ADVICE_PUSH_REPO_LOOKS_LIKE_REF)) + die_if_repo_looks_like_ref(repo); + /* * Not a configured remote name or group name. * Try treating it as a direct URL or path, e.g. @@ -753,7 +788,7 @@ int cmd_push(int argc, * from the URL so the loop below can handle it * identically to a named remote. */ - struct remote *r = pushremote_get(repo); + r = pushremote_get(repo); if (!r) die(_("bad repository '%s'"), repo); string_list_append(&remote_group, r->name); diff --git a/builtin/rebase.c b/builtin/rebase.c index fa4f5d9306b856..10a306310cd439 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -592,7 +592,7 @@ static int finish_rebase(struct rebase_options *opts) static int move_to_original_branch(struct rebase_options *opts) { struct strbuf branch_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT; - struct reset_head_opts ropts = { 0 }; + struct reset_working_tree_options ropts = { 0 }; int ret; if (!opts->head_name) @@ -607,10 +607,11 @@ static int move_to_original_branch(struct rebase_options *opts) strbuf_addf(&head_reflog, "%s (finish): returning to %s", opts->reflog_action, opts->head_name); ropts.branch = opts->head_name; - ropts.flags = RESET_HEAD_REFS_ONLY; + ropts.flags = RESET_WORKING_TREE_REFS_ONLY | + RESET_WORKING_TREE_UPDATE_HEAD; ropts.branch_msg = branch_reflog.buf; ropts.head_msg = head_reflog.buf; - ret = reset_head(the_repository, &ropts); + ret = reset_working_tree(the_repository, &ropts); strbuf_release(&branch_reflog); strbuf_release(&head_reflog); @@ -685,7 +686,7 @@ static int run_am(struct rebase_options *opts) status = run_command(&format_patch); if (status) { - struct reset_head_opts ropts = { 0 }; + struct reset_working_tree_options ropts = { 0 }; unlink(rebased_patches); free(rebased_patches); child_process_clear(&am); @@ -693,7 +694,8 @@ static int run_am(struct rebase_options *opts) ropts.oid = &opts->orig_head->object.oid; ropts.branch = opts->head_name; ropts.default_reflog_action = opts->reflog_action; - reset_head(the_repository, &ropts); + ropts.flags = RESET_WORKING_TREE_UPDATE_HEAD; + reset_working_tree(the_repository, &ropts); error(_("\ngit encountered an error while preparing the " "patches to replay\n" "these revisions:\n" @@ -855,18 +857,19 @@ static int rebase_config(const char *var, const char *value, static int checkout_up_to_date(struct rebase_options *options) { struct strbuf buf = STRBUF_INIT; - struct reset_head_opts ropts = { 0 }; + struct reset_working_tree_options ropts = { 0 }; int ret = 0; strbuf_addf(&buf, "%s: checkout %s", options->reflog_action, options->switch_to); ropts.oid = &options->orig_head->object.oid; ropts.branch = options->head_name; - ropts.flags = RESET_HEAD_RUN_POST_CHECKOUT_HOOK; + ropts.flags = RESET_WORKING_TREE_RUN_POST_CHECKOUT_HOOK | + RESET_WORKING_TREE_UPDATE_HEAD; if (!ropts.branch) - ropts.flags |= RESET_HEAD_DETACH; + ropts.flags |= RESET_WORKING_TREE_DETACH; ropts.head_msg = buf.buf; - if (reset_head(the_repository, &ropts) < 0) + if (reset_working_tree(the_repository, &ropts) < 0) ret = error(_("could not switch to %s"), options->switch_to); strbuf_release(&buf); @@ -1116,7 +1119,7 @@ int cmd_rebase(int argc, int reschedule_failed_exec = -1; int allow_preemptive_ff = 1; int preserve_merges_selected = 0; - struct reset_head_opts ropts = { 0 }; + struct reset_working_tree_options ropts = { 0 }; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -1384,8 +1387,9 @@ int cmd_rebase(int argc, rerere_clear(the_repository, &merge_rr); string_list_clear(&merge_rr, 1); - ropts.flags = RESET_HEAD_HARD; - if (reset_head(the_repository, &ropts) < 0) + ropts.flags = RESET_WORKING_TREE_HARD | + RESET_WORKING_TREE_UPDATE_HEAD; + if (reset_working_tree(the_repository, &ropts) < 0) die(_("could not discard worktree changes")); remove_branch_state(the_repository, 0); if (read_basic_state(&options)) @@ -1409,8 +1413,9 @@ int cmd_rebase(int argc, ropts.oid = &options.orig_head->object.oid; ropts.head_msg = head_msg.buf; ropts.branch = options.head_name; - ropts.flags = RESET_HEAD_HARD; - if (reset_head(the_repository, &ropts) < 0) + ropts.flags = RESET_WORKING_TREE_HARD | + RESET_WORKING_TREE_UPDATE_HEAD; + if (reset_working_tree(the_repository, &ropts) < 0) die(_("could not move back to %s"), oid_to_hex(&options.orig_head->object.oid)); strbuf_release(&head_msg); @@ -1876,11 +1881,13 @@ int cmd_rebase(int argc, options.reflog_action, options.onto_name); ropts.oid = &options.onto->object.oid; ropts.orig_head = &options.orig_head->object.oid; - ropts.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD | - RESET_HEAD_RUN_POST_CHECKOUT_HOOK; + ropts.flags = RESET_WORKING_TREE_DETACH | + RESET_WORKING_TREE_UPDATE_HEAD | + RESET_WORKING_TREE_UPDATE_ORIG_HEAD | + RESET_WORKING_TREE_RUN_POST_CHECKOUT_HOOK; ropts.head_msg = msg.buf; ropts.default_reflog_action = options.reflog_action; - if (reset_head(the_repository, &ropts)) { + if (reset_working_tree(the_repository, &ropts)) { ret = error(_("Could not detach HEAD")); goto cleanup_autostash; } diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 19eb6a1b61c3a7..7190d7f43063c9 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -37,7 +37,6 @@ #include "sigchain.h" #include "string-list.h" #include "strvec.h" -#include "tmp-objdir.h" #include "trace.h" #include "trace2.h" #include "version.h" @@ -112,8 +111,6 @@ static enum { } use_keepalive; static int keepalive_in_sec = 5; -static struct tmp_objdir *tmp_objdir; - static struct proc_receive_ref { unsigned int want_add:1, want_delete:1, @@ -615,7 +612,7 @@ static void hmac_hash(unsigned char *out, /* RFC 2104 2. (1) */ memset(key, '\0', GIT_MAX_BLKSZ); if (the_hash_algo->blksz < key_len) { - the_hash_algo->init_fn(&ctx); + git_hash_init(&ctx, the_hash_algo); git_hash_update(&ctx, key_in, key_len); git_hash_final(key, &ctx); } else { @@ -629,13 +626,13 @@ static void hmac_hash(unsigned char *out, } /* RFC 2104 2. (3) & (4) */ - the_hash_algo->init_fn(&ctx); + git_hash_init(&ctx, the_hash_algo); git_hash_update(&ctx, k_ipad, sizeof(k_ipad)); git_hash_update(&ctx, text, text_len); git_hash_final(out, &ctx); /* RFC 2104 2. (6) & (7) */ - the_hash_algo->init_fn(&ctx); + git_hash_init(&ctx, the_hash_algo); git_hash_update(&ctx, k_opad, sizeof(k_opad)); git_hash_update(&ctx, out, the_hash_algo->rawsz); git_hash_final(out, &ctx); @@ -926,6 +923,7 @@ static void receive_hook_feed_state_free(void *data) static int run_receive_hook(struct command *commands, const char *hook_name, int skip_broken, + struct odb_transaction *transaction, const struct string_list *push_options) { struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; @@ -959,8 +957,8 @@ static int run_receive_hook(struct command *commands, strvec_push(&opt.env, "GIT_PUSH_OPTION_COUNT"); } - if (tmp_objdir) - strvec_pushv(&opt.env, tmp_objdir_env(tmp_objdir)); + if (transaction) + odb_transaction_env(transaction, &opt.env); prepare_push_cert_sha1(&opt); @@ -1363,7 +1361,6 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si) !delayed_reachability_test(si, i)) oid_array_append(&extra, &si->shallow->oid[i]); - opt.env = tmp_objdir_env(tmp_objdir); setup_alternate_shallow(&shallow_lock, &opt.shallow_file, &extra); if (check_connected(command_singleton_iterator, cmd, &opt)) { rollback_shallow_file(the_repository, &shallow_lock); @@ -1790,24 +1787,30 @@ static const struct object_id *command_singleton_iterator(void *cb_data) } static void set_connectivity_errors(struct command *commands, - struct shallow_info *si) + struct shallow_info *si, + struct odb_transaction *transaction) { struct command *cmd; for (cmd = commands; cmd; cmd = cmd->next) { struct command *singleton = cmd; struct check_connected_options opt = CHECK_CONNECTED_INIT; + struct strvec env = STRVEC_INIT; if (shallow_update && si->shallow_ref[cmd->index]) /* to be checked in update_shallow_ref() */ continue; - opt.env = tmp_objdir_env(tmp_objdir); + odb_transaction_env(transaction, &env); + opt.env = env.v; + if (!check_connected(command_singleton_iterator, &singleton, &opt)) continue; cmd->error_string = "missing necessary objects"; + + strvec_clear(&env); } } @@ -2028,6 +2031,7 @@ static void execute_commands_atomic(struct command *commands, static void execute_commands(struct command *commands, const char *unpacker_error, struct shallow_info *si, + struct odb_transaction *transaction, const struct string_list *push_options) { struct check_connected_options opt = CHECK_CONNECTED_INIT; @@ -2044,6 +2048,8 @@ static void execute_commands(struct command *commands, } if (!skip_connectivity_check) { + struct strvec env = STRVEC_INIT; + if (use_sideband) { memset(&muxer, 0, sizeof(muxer)); muxer.proc = copy_to_sideband; @@ -2057,14 +2063,17 @@ static void execute_commands(struct command *commands, data.si = si; opt.err_fd = err_fd; opt.progress = err_fd && !quiet; - opt.env = tmp_objdir_env(tmp_objdir); + odb_transaction_env(transaction, &env); + opt.env = env.v; opt.exclude_hidden_refs_section = "receive"; if (check_connected(iterate_receive_command_list, &data, &opt)) - set_connectivity_errors(commands, si); + set_connectivity_errors(commands, si, transaction); if (use_sideband) finish_async(&muxer); + + strvec_clear(&env); } reject_updates_to_hidden(commands); @@ -2085,7 +2094,7 @@ static void execute_commands(struct command *commands, } } - if (run_receive_hook(commands, "pre-receive", 0, push_options)) { + if (run_receive_hook(commands, "pre-receive", 0, transaction, push_options)) { for (cmd = commands; cmd; cmd = cmd->next) { if (!cmd->error_string) cmd->error_string = "pre-receive hook declined"; @@ -2106,14 +2115,13 @@ static void execute_commands(struct command *commands, * Now we'll start writing out refs, which means the objects need * to be in their final positions so that other processes can see them. */ - if (tmp_objdir_migrate(tmp_objdir) < 0) { + if (odb_transaction_commit(transaction)) { for (cmd = commands; cmd; cmd = cmd->next) { if (!cmd->error_string) cmd->error_string = "unable to migrate objects to permanent storage"; } return; } - tmp_objdir = NULL; check_aliased_updates(commands); @@ -2326,7 +2334,8 @@ static void push_header_arg(struct strvec *args, struct pack_header *hdr) ntohl(hdr->hdr_version), ntohl(hdr->hdr_entries)); } -static const char *unpack(int err_fd, struct shallow_info *si) +static const char *unpack(int err_fd, struct shallow_info *si, + struct odb_transaction *transaction) { struct pack_header hdr; const char *hdr_err; @@ -2351,20 +2360,7 @@ static const char *unpack(int err_fd, struct shallow_info *si) strvec_push(&child.args, alt_shallow_file); } - tmp_objdir = tmp_objdir_create(the_repository, "incoming"); - if (!tmp_objdir) { - if (err_fd > 0) - close(err_fd); - return "unable to create temporary object directory"; - } - strvec_pushv(&child.env, tmp_objdir_env(tmp_objdir)); - - /* - * Normally we just pass the tmp_objdir environment to the child - * processes that do the heavy lifting, but we may need to see these - * objects ourselves to set up shallow information. - */ - tmp_objdir_add_as_alternate(tmp_objdir); + odb_transaction_env(transaction, &child.env); if (ntohl(hdr.hdr_entries) < unpack_limit) { strvec_push(&child.args, "unpack-objects"); @@ -2431,13 +2427,14 @@ static const char *unpack(int err_fd, struct shallow_info *si) return NULL; } -static const char *unpack_with_sideband(struct shallow_info *si) +static const char *unpack_with_sideband(struct shallow_info *si, + struct odb_transaction *transaction) { struct async muxer; const char *ret; if (!use_sideband) - return unpack(0, si); + return unpack(0, si, transaction); use_keepalive = KEEPALIVE_AFTER_NUL; memset(&muxer, 0, sizeof(muxer)); @@ -2446,7 +2443,7 @@ static const char *unpack_with_sideband(struct shallow_info *si) if (start_async(&muxer)) return NULL; - ret = unpack(muxer.in, si); + ret = unpack(muxer.in, si, transaction); finish_async(&muxer); return ret; @@ -2623,6 +2620,7 @@ int cmd_receive_pack(int argc, struct oid_array ref = OID_ARRAY_INIT; struct shallow_info si; struct packet_reader reader; + struct odb_transaction *transaction = NULL; struct option options[] = { OPT__QUIET(&quiet, N_("quiet")), @@ -2707,11 +2705,14 @@ int cmd_receive_pack(int argc, if (!si.nr_ours && !si.nr_theirs) shallow_update = 0; if (!delete_only(commands)) { - unpack_status = unpack_with_sideband(&si); + if (odb_transaction_begin(the_repository->objects, &transaction, ODB_TRANSACTION_RECEIVE)) + unpack_status = "unable to start object transaction"; + else + unpack_status = unpack_with_sideband(&si, transaction); update_shallow_info(commands, &si, &ref); } use_keepalive = KEEPALIVE_ALWAYS; - execute_commands(commands, unpack_status, &si, + execute_commands(commands, unpack_status, &si, transaction, &push_options); delete_tempfile(&pack_lockfile); sigchain_push(SIGPIPE, SIG_IGN); @@ -2720,7 +2721,7 @@ int cmd_receive_pack(int argc, else if (report_status) report(commands, unpack_status); sigchain_pop(SIGPIPE); - run_receive_hook(commands, "post-receive", 1, + run_receive_hook(commands, "post-receive", 1, NULL, &push_options); run_update_post_hook(commands); free_commands(commands); diff --git a/builtin/refs.c b/builtin/refs.c index e3125bc61b20e0..a9ca2058eeb55c 100644 --- a/builtin/refs.c +++ b/builtin/refs.c @@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #include "builtin.h" #include "config.h" #include "fsck.h" @@ -22,8 +21,20 @@ #define REFS_OPTIMIZE_USAGE \ N_("git refs optimize " PACK_REFS_OPTS) +#define REFS_CREATE_USAGE \ + N_("git refs create [--message=] [--no-deref] [--create-reflog] ") + +#define REFS_DELETE_USAGE \ + N_("git refs delete [--message=] [--no-deref] []") + +#define REFS_UPDATE_USAGE \ + N_("git refs update [--message=] [--no-deref] [--create-reflog] []") + +#define REFS_RENAME_USAGE \ + N_("git refs rename [--message=] ") + static int cmd_refs_migrate(int argc, const char **argv, const char *prefix, - struct repository *repo UNUSED) + struct repository *repo) { const char * const migrate_usage[] = { REFS_MIGRATE_USAGE, @@ -59,13 +70,13 @@ static int cmd_refs_migrate(int argc, const char **argv, const char *prefix, goto out; } - if (the_repository->ref_storage_format == format) { + if (repo->ref_storage_format == format) { err = error(_("repository already uses '%s' format"), ref_storage_format_to_name(format)); goto out; } - if (repo_migrate_ref_storage_format(the_repository, format, flags, &errbuf) < 0) { + if (repo_migrate_ref_storage_format(repo, format, flags, &errbuf) < 0) { err = error("%s", errbuf.buf); goto out; } @@ -99,8 +110,8 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix, if (argc) usage(_("'git refs verify' takes no arguments")); - repo_config(the_repository, git_fsck_config, &fsck_refs_options); - prepare_repo_settings(the_repository); + repo_config(repo, git_fsck_config, &fsck_refs_options); + prepare_repo_settings(repo); worktrees = get_worktrees_without_reading_head(); for (size_t i = 0; worktrees[i]; i++) @@ -124,7 +135,7 @@ static int cmd_refs_list(int argc, const char **argv, const char *prefix, } static int cmd_refs_exists(int argc, const char **argv, const char *prefix, - struct repository *repo UNUSED) + struct repository *repo) { struct strbuf unused_referent = STRBUF_INIT; struct object_id unused_oid; @@ -145,7 +156,7 @@ static int cmd_refs_exists(int argc, const char **argv, const char *prefix, die(_("'git refs exists' requires a reference")); ref = *argv++; - if (refs_read_raw_ref(get_main_ref_store(the_repository), ref, + if (refs_read_raw_ref(get_main_ref_store(repo), ref, &unused_oid, &unused_referent, &unused_type, &failure_errno)) { if (failure_errno == ENOENT || failure_errno == EISDIR) { @@ -176,6 +187,193 @@ static int cmd_refs_optimize(int argc, const char **argv, const char *prefix, return pack_refs_core(argc, argv, prefix, repo, refs_optimize_usage); } +static int cmd_refs_create(int argc, const char **argv, const char *prefix, + struct repository *repo) +{ + static char const * const refs_create_usage[] = { + REFS_CREATE_USAGE, + NULL + }; + const char *message = NULL; + unsigned flags = 0; + struct option opts[] = { + OPT_STRING(0, "message", &message, N_("reason"), + N_("reason of the update")), + OPT_BIT(0 ,"no-deref", &flags, + N_("update not the one it points to"), + REF_NO_DEREF), + OPT_BIT(0, "create-reflog", &flags, N_("create a reflog"), + REF_FORCE_CREATE_REFLOG), + OPT_END(), + }; + struct object_id newoid; + const char *refname; + int ret; + + argc = parse_options(argc, argv, prefix, opts, refs_create_usage, 0); + if (argc != 2) + usage(_("create requires reference name and an object ID")); + + if (message && !*message) + die(_("refusing to perform update with empty message")); + + repo_config(repo, git_default_config, NULL); + + refname = argv[0]; + if (repo_get_oid_with_flags(repo, argv[1], &newoid, GET_OID_SKIP_AMBIGUITY_CHECK)) + die(_("invalid object ID: '%s'"), argv[1]); + if (is_null_oid(&newoid)) + die(_("cannot create reference with null new object ID")); + + ret = refs_update_ref(get_main_ref_store(repo), message, refname, + &newoid, null_oid(repo->hash_algo), flags, + UPDATE_REFS_MSG_ON_ERR); + + if (ret < 0) + ret = 1; + return ret; +} + +static int cmd_refs_delete(int argc, const char **argv, const char *prefix, + struct repository *repo) +{ + static char const * const refs_delete_usage[] = { + REFS_DELETE_USAGE, + NULL + }; + const char *message = NULL; + unsigned flags = 0; + struct option opts[] = { + OPT_STRING(0, "message", &message, N_("reason"), + N_("reason of the update")), + OPT_BIT(0 ,"no-deref", &flags, + N_("update not the one it points to"), + REF_NO_DEREF), + OPT_END(), + }; + struct object_id oldoid; + const char *refname; + int ret; + + argc = parse_options(argc, argv, prefix, opts, refs_delete_usage, 0); + if (argc < 1 || argc > 2) + usage(_("delete requires reference name and an optional old object ID")); + + if (message && !*message) + die(_("refusing to perform update with empty message")); + + repo_config(repo, git_default_config, NULL); + + refname = argv[0]; + if (argc == 2) { + if (repo_get_oid_with_flags(repo, argv[1], &oldoid, GET_OID_SKIP_AMBIGUITY_CHECK)) + die(_("invalid old object ID: '%s'"), argv[1]); + if (is_null_oid(&oldoid)) + die(_("cannot delete reference with null old object ID")); + } + + ret = refs_delete_ref(get_main_ref_store(repo), message, refname, + argc == 2 ? &oldoid : NULL, flags); + + if (ret < 0) + ret = 1; + return ret; +} + +static int cmd_refs_update(int argc, const char **argv, const char *prefix, + struct repository *repo) +{ + static char const * const refs_update_usage[] = { + REFS_UPDATE_USAGE, + NULL + }; + const char *message = NULL; + unsigned flags = 0; + struct option opts[] = { + OPT_STRING(0, "message", &message, N_("reason"), + N_("reason of the update")), + OPT_BIT(0 ,"no-deref", &flags, + N_("update not the one it points to"), + REF_NO_DEREF), + OPT_BIT(0, "create-reflog", &flags, N_("create a reflog"), + REF_FORCE_CREATE_REFLOG), + OPT_END(), + }; + struct object_id newoid, oldoid; + const char *refname; + int ret; + + argc = parse_options(argc, argv, prefix, opts, refs_update_usage, 0); + if (argc < 2 || argc > 3) + usage(_("update requires reference name, new value and an optional old value")); + + if (message && !*message) + die(_("refusing to perform update with empty message")); + + repo_config(repo, git_default_config, NULL); + + refname = argv[0]; + if (repo_get_oid_with_flags(repo, argv[1], &newoid, + GET_OID_SKIP_AMBIGUITY_CHECK)) + die(_("invalid new object ID: '%s'"), argv[1]); + if (argc == 3 && + repo_get_oid_with_flags(repo, argv[2], &oldoid, + GET_OID_SKIP_AMBIGUITY_CHECK)) + die(_("invalid old object ID: '%s'"), argv[2]); + + ret = refs_update_ref(get_main_ref_store(repo), message, refname, + &newoid, argc == 3 ? &oldoid : NULL, flags, + UPDATE_REFS_MSG_ON_ERR); + + if (ret < 0) + ret = 1; + return ret; +} + +static int cmd_refs_rename(int argc, const char **argv, const char *prefix, + struct repository *repo) +{ + static char const * const refs_rename_usage[] = { + REFS_RENAME_USAGE, + NULL + }; + const char *message = NULL; + struct option opts[] = { + OPT_STRING(0, "message", &message, N_("reason"), + N_("reason of the update")), + OPT_END(), + }; + const char *oldref, *newref; + int ret; + + argc = parse_options(argc, argv, prefix, opts, refs_rename_usage, 0); + if (argc != 2) + usage(_("rename requires old and new reference name")); + if (message && !*message) + die(_("refusing to perform update with empty message")); + + repo_config(repo, git_default_config, NULL); + + oldref = argv[0]; + newref = argv[1]; + + if (check_refname_format(oldref, 0)) + die(_("invalid ref format: '%s'"), oldref); + if (check_refname_format(newref, 0)) + die(_("invalid ref format: '%s'"), newref); + + if (!refs_ref_exists(get_main_ref_store(repo), oldref)) + die(_("reference does not exist: '%s'"), oldref); + if (refs_ref_exists(get_main_ref_store(repo), newref)) + die(_("reference already exists: '%s'"), newref); + + ret = refs_rename_ref(get_main_ref_store(repo), oldref, newref, message); + + if (ret < 0) + ret = 1; + return ret; +} + int cmd_refs(int argc, const char **argv, const char *prefix, @@ -187,6 +385,10 @@ int cmd_refs(int argc, "git refs list " COMMON_USAGE_FOR_EACH_REF, REFS_EXISTS_USAGE, REFS_OPTIMIZE_USAGE, + REFS_CREATE_USAGE, + REFS_DELETE_USAGE, + REFS_UPDATE_USAGE, + REFS_RENAME_USAGE, NULL, }; parse_opt_subcommand_fn *fn = NULL; @@ -196,6 +398,10 @@ int cmd_refs(int argc, OPT_SUBCOMMAND("list", &fn, cmd_refs_list), OPT_SUBCOMMAND("exists", &fn, cmd_refs_exists), OPT_SUBCOMMAND("optimize", &fn, cmd_refs_optimize), + OPT_SUBCOMMAND("create", &fn, cmd_refs_create), + OPT_SUBCOMMAND("delete", &fn, cmd_refs_delete), + OPT_SUBCOMMAND("update", &fn, cmd_refs_update), + OPT_SUBCOMMAND("rename", &fn, cmd_refs_rename), OPT_END(), }; diff --git a/builtin/repack.c b/builtin/repack.c index 1524a9c13ad5b8..db504d673fcf52 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "builtin.h" @@ -265,7 +264,7 @@ int cmd_repack(int argc, if (write_bitmaps < 0) { if (write_midx == REPACK_WRITE_MIDX_NONE && - (!(pack_everything & ALL_INTO_ONE) || !is_bare_repository())) + (!(pack_everything & ALL_INTO_ONE) || !is_bare_repository(repo))) write_bitmaps = 0; } if (po_args.pack_kept_objects < 0) @@ -458,6 +457,8 @@ int cmd_repack(int argc, } if (!names.nr) { + struct odb_source_files *files = odb_source_files_downcast(existing.source); + if (!po_args.quiet) printf_ln(_("Nothing new to pack.")); /* @@ -473,7 +474,7 @@ int cmd_repack(int argc, * midx_has_unknown_packs() will make the decision for * us. */ - if (!get_multi_pack_index(existing.source)) + if (!get_multi_pack_index(files->packed)) midx_must_contain_cruft = 1; } @@ -626,10 +627,12 @@ int cmd_repack(int argc, update_server_info(repo, 0); if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0)) { + struct odb_source_files *files = odb_source_files_downcast(existing.source); unsigned flags = 0; + if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL, 0)) flags |= MIDX_WRITE_INCREMENTAL; - write_midx_file(existing.source, NULL, NULL, flags); + write_midx_file(files->packed, NULL, NULL, flags); } cleanup: diff --git a/builtin/repo.c b/builtin/repo.c index 71a5c1c29c05fe..84e012f83f65ae 100644 --- a/builtin/repo.c +++ b/builtin/repo.c @@ -7,12 +7,14 @@ #include "hex.h" #include "odb.h" #include "parse-options.h" +#include "path.h" #include "path-walk.h" #include "progress.h" #include "quote.h" #include "ref-filter.h" #include "refs.h" #include "revision.h" +#include "setup.h" #include "strbuf.h" #include "string-list.h" #include "shallow.h" @@ -58,7 +60,7 @@ struct repo_info_field { static int get_layout_bare(struct repository *repo UNUSED, struct strbuf *buf) { - strbuf_addstr(buf, is_bare_repository() ? "true" : "false"); + strbuf_addstr(buf, is_bare_repository(the_repository) ? "true" : "false"); return 0; } @@ -75,6 +77,50 @@ static int get_object_format(struct repository *repo, struct strbuf *buf) return 0; } +static int get_path_commondir_absolute(struct repository *repo, struct strbuf *buf) +{ + const char *common_dir = repo_get_common_dir(repo); + + if (!common_dir) + return error(_("unable to get common directory")); + + format_path(buf, common_dir, repo->prefix, PATH_FORMAT_CANONICAL); + return 0; +} + +static int get_path_commondir_relative(struct repository *repo, struct strbuf *buf) +{ + const char *common_dir = repo_get_common_dir(repo); + + if (!common_dir) + return error(_("unable to get common directory")); + + format_path(buf, common_dir, repo->prefix, PATH_FORMAT_RELATIVE); + return 0; +} + +static int get_path_gitdir_absolute(struct repository *repo, struct strbuf *buf) +{ + const char *git_dir = repo_get_git_dir(repo); + + if (!git_dir) + return error(_("unable to get git directory")); + + format_path(buf, git_dir, repo->prefix, PATH_FORMAT_CANONICAL); + return 0; +} + +static int get_path_gitdir_relative(struct repository *repo, struct strbuf *buf) +{ + const char *git_dir = repo_get_git_dir(repo); + + if (!git_dir) + return error(_("unable to get git directory")); + + format_path(buf, git_dir, repo->prefix, PATH_FORMAT_RELATIVE); + return 0; +} + static int get_references_format(struct repository *repo, struct strbuf *buf) { strbuf_addstr(buf, @@ -87,6 +133,10 @@ static const struct repo_info_field repo_info_field[] = { { "layout.bare", get_layout_bare }, { "layout.shallow", get_layout_shallow }, { "object.format", get_object_format }, + { "path.commondir.absolute", get_path_commondir_absolute }, + { "path.commondir.relative", get_path_commondir_relative }, + { "path.gitdir.absolute", get_path_gitdir_absolute }, + { "path.gitdir.relative", get_path_gitdir_relative }, { "references.format", get_references_format }, }; @@ -784,13 +834,14 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids, for (size_t i = 0; i < oids->nr; i++) { struct object_info oi = OBJECT_INFO_INIT; unsigned long inflated; + size_t inflated_st = 0; struct commit *commit; struct object *obj; void *content; off_t disk; int eaten; - oi.sizep = &inflated; + oi.sizep = &inflated_st; oi.disk_sizep = &disk; oi.contentp = &content; @@ -798,6 +849,7 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids, OBJECT_INFO_SKIP_FETCH_OBJECT | OBJECT_INFO_QUICK) < 0) continue; + inflated = cast_size_t_to_ulong(inflated_st); obj = parse_object_buffer(the_repository, &oids->oid[i], type, inflated, content, &eaten); diff --git a/builtin/reset.c b/builtin/reset.c index 3be6bd0121afe5..78e69bd84ba2c3 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -470,7 +470,7 @@ int cmd_reset(int argc, if (reset_type != SOFT && (reset_type != MIXED || repo_get_work_tree(the_repository))) setup_work_tree(the_repository); - if (reset_type == MIXED && is_bare_repository()) + if (reset_type == MIXED && is_bare_repository(the_repository)) die(_("%s reset is not allowed in a bare repository"), _(reset_type_names[reset_type])); diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 8f63003709242e..02818b81c63fd7 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -486,7 +486,7 @@ static int show_object_fast( void *payload UNUSED) { fprintf(stdout, "%s\n", oid_to_hex(oid)); - return 1; + return 0; } static void print_disk_usage(off_t size) diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index bb882678fe2a9e..43693454d53f0c 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -255,7 +255,7 @@ static int show_file(const char *arg, int output_prefix) show_default(); if ((filter & (DO_NONFLAGS|DO_NOREV)) == (DO_NONFLAGS|DO_NOREV)) { if (output_prefix) { - const char *prefix = startup_info->prefix; + const char *prefix = the_repository->prefix; char *fname = prefix_filename(prefix, arg); show(fname); free(fname); @@ -653,53 +653,46 @@ enum default_type { DEFAULT_UNMODIFIED, }; -static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def) +static void print_path(const char *path, const char *prefix, + enum format_type format, enum default_type def) { - char *cwd = NULL; - /* - * We don't ever produce a relative path if prefix is NULL, so set the - * prefix to the current directory so that we can produce a relative - * path whenever possible. If we're using RELATIVE_IF_SHARED mode, then - * we want an absolute path unless the two share a common prefix, so don't - * set it in that case, since doing so causes a relative path to always - * be produced if possible. - */ - if (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED)) - prefix = cwd = xgetcwd(); - if (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) { - puts(path); - } else if (format == FORMAT_RELATIVE || - (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) { - /* - * In order for relative_path to work as expected, we need to - * make sure that both paths are absolute paths. If we don't, - * we can end up with an unexpected absolute path that the user - * didn't want. - */ - struct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT; - if (!is_absolute_path(path)) { - strbuf_realpath_forgiving(&realbuf, path, 1); - path = realbuf.buf; - } - if (!is_absolute_path(prefix)) { - strbuf_realpath_forgiving(&prefixbuf, prefix, 1); - prefix = prefixbuf.buf; + struct strbuf sb = STRBUF_INIT; + enum path_format fmt; + + if (format == FORMAT_DEFAULT) { + switch (def) { + case DEFAULT_RELATIVE: + fmt = PATH_FORMAT_RELATIVE; + break; + case DEFAULT_RELATIVE_IF_SHARED: + fmt = PATH_FORMAT_RELATIVE_IF_SHARED; + break; + case DEFAULT_CANONICAL: + fmt = PATH_FORMAT_CANONICAL; + break; + case DEFAULT_UNMODIFIED: + default: + fmt = PATH_FORMAT_UNMODIFIED; + break; } - puts(relative_path(path, prefix, &buf)); - strbuf_release(&buf); - strbuf_release(&realbuf); - strbuf_release(&prefixbuf); - } else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) { - struct strbuf buf = STRBUF_INIT; - puts(relative_path(path, prefix, &buf)); - strbuf_release(&buf); } else { - struct strbuf buf = STRBUF_INIT; - strbuf_realpath_forgiving(&buf, path, 1); - puts(buf.buf); - strbuf_release(&buf); + switch (format) { + case FORMAT_RELATIVE: + fmt = PATH_FORMAT_RELATIVE; + break; + case FORMAT_CANONICAL: + fmt = PATH_FORMAT_CANONICAL; + break; + default: + fmt = PATH_FORMAT_UNMODIFIED; + break; + } } - free(cwd); + + format_path(&sb, path, prefix, fmt); + puts(sb.buf); + + strbuf_release(&sb); } int cmd_rev_parse(int argc, @@ -839,7 +832,8 @@ int cmd_rev_parse(int argc, prefix = argv[++i]; if (!prefix) die(_("--prefix requires an argument")); - startup_info->prefix = prefix; + FREE_AND_NULL(the_repository->prefix); + the_repository->prefix = xstrdup(prefix); output_prefix = 1; continue; } @@ -1084,7 +1078,7 @@ int cmd_rev_parse(int argc, continue; } if (!strcmp(arg, "--is-bare-repository")) { - printf("%s\n", is_bare_repository() ? "true" + printf("%s\n", is_bare_repository(the_repository) ? "true" : "false"); continue; } diff --git a/builtin/shortlog.c b/builtin/shortlog.c index 6b2a0b93b5992e..4c78d2e5badb82 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -433,6 +433,8 @@ int cmd_shortlog(int argc, case PARSE_OPT_UNKNOWN: break; case PARSE_OPT_HELP: + exit(0); + case PARSE_OPT_HELP_ERROR: case PARSE_OPT_ERROR: case PARSE_OPT_SUBCOMMAND: exit(129); diff --git a/builtin/show-branch.c b/builtin/show-branch.c index f02831b08500c4..2435e8aeda40ef 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -62,11 +62,10 @@ static const char *get_color_reset_code(void) static struct commit *interesting(struct prio_queue *queue) { - for (size_t i = 0; i < queue->nr; i++) { - struct commit *commit = queue->array[i].data; - if (commit->object.flags & UNINTERESTING) - continue; - return commit; + struct commit *commit; + prio_queue_for_each(queue, commit) { + if (!(commit->object.flags & UNINTERESTING)) + return commit; } return NULL; } @@ -228,17 +227,18 @@ static void join_revs(struct prio_queue *queue, { int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1); int all_revs = all_mask & ~((1u << REV_SHIFT) - 1); + struct commit *commit; - while (queue->nr) { + while ((commit = prio_queue_peek(queue))) { struct commit_list *parents; int still_interesting = !!interesting(queue); - struct commit *commit = prio_queue_peek(queue); - bool get_pending = true; int flags = commit->object.flags & all_mask; if (!still_interesting && extra <= 0) break; + prio_queue_get(queue); + mark_seen(commit, seen_p); if ((flags & all_revs) == all_revs) flags |= UNINTERESTING; @@ -254,14 +254,8 @@ static void join_revs(struct prio_queue *queue, if (mark_seen(p, seen_p) && !still_interesting) extra--; p->object.flags |= flags; - if (get_pending) - prio_queue_replace(queue, p); - else - prio_queue_put(queue, p); - get_pending = false; + prio_queue_put(queue, p); } - if (get_pending) - prio_queue_get(queue); } /* diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index d89acbeb533bd8..0863d0fb460cf8 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -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); @@ -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; } @@ -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(). @@ -745,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)")); @@ -770,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]); @@ -837,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}; @@ -874,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 { @@ -978,7 +984,7 @@ static int sparse_checkout_clean(int argc, const char **argv, 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, @@ -1142,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; @@ -1153,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/submodule--helper.c b/builtin/submodule--helper.c index 1cc82a134db22e..2b3c9762afec0a 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -550,11 +550,11 @@ static void create_default_gitdir_config(const char *submodule_name) /* Case 2.4: If all the above failed, try a hash of the name as a last resort */ header_len = snprintf(header, sizeof(header), "blob %zu", strlen(submodule_name)); - the_hash_algo->init_fn(&ctx); - the_hash_algo->update_fn(&ctx, header, header_len); - the_hash_algo->update_fn(&ctx, "\0", 1); - the_hash_algo->update_fn(&ctx, submodule_name, strlen(submodule_name)); - the_hash_algo->final_fn(raw_name_hash, &ctx); + git_hash_init(&ctx, the_hash_algo); + git_hash_update(&ctx, header, header_len); + git_hash_update(&ctx, "\0", 1); + git_hash_update(&ctx, submodule_name, strlen(submodule_name)); + git_hash_final(raw_name_hash, &ctx); hash_to_hex_algop_r(hex_name_hash, raw_name_hash, the_hash_algo); strbuf_reset(&gitdir_path); repo_git_path_append(the_repository, &gitdir_path, "modules/%s", hex_name_hash); @@ -2990,7 +2990,7 @@ static int module_update(int argc, const char **argv, const char *prefix, struct option module_update_options[] = { OPT__SUPER_PREFIX(&opt.super_prefix), OPT__FORCE(&opt.force, N_("force checkout updates"), 0), - OPT_BOOL(0, "init", &opt.init, + OPT_BOOL('i', "init", &opt.init, N_("initialize uninitialized submodules before update")), OPT_BOOL(0, "remote", &opt.remote, N_("use SHA-1 of submodule's remote tracking branch")), diff --git a/builtin/tag.c b/builtin/tag.c index d51c2e33495295..06c125b53c88e8 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -238,7 +238,7 @@ static int git_tag_config(const char *var, const char *value, static void write_tag_body(int fd, const struct object_id *oid) { - unsigned long size; + size_t size; enum object_type type; char *buf, *sp, *orig; struct strbuf payload = STRBUF_INIT; @@ -388,7 +388,7 @@ static void create_reflog_msg(const struct object_id *oid, struct strbuf *sb) enum object_type type; struct commit *c; char *buf; - unsigned long size; + size_t size; int subject_len = 0; const char *subject_start; diff --git a/builtin/unpack-file.c b/builtin/unpack-file.c index 87877a9fabc6f6..387389ed491d33 100644 --- a/builtin/unpack-file.c +++ b/builtin/unpack-file.c @@ -12,7 +12,7 @@ static char *create_temp_file(struct object_id *oid) static char path[50]; void *buf; enum object_type type; - unsigned long size; + size_t size; int fd; buf = odb_read_object(the_repository->objects, oid, &type, &size); diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index 59e9b8711e3c2b..4263edfbecdd39 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -231,7 +231,7 @@ static int check_object(struct object *obj, enum object_type type, die("object type mismatch"); if (!(obj->flags & FLAG_OPEN)) { - unsigned long size; + size_t size; int type = odb_read_object_info(the_repository->objects, &obj->oid, &size); if (type != obj->type || type <= 0) die("object of unexpected type"); @@ -314,7 +314,7 @@ static void resolve_delta(unsigned nr, enum object_type type, void *delta, unsigned long delta_size) { void *result; - unsigned long result_size; + size_t result_size; result = patch_delta(base, base_size, delta, delta_size, @@ -436,6 +436,7 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size, { void *delta_data, *base; unsigned long base_size; + size_t base_size_st = 0; struct object_id base_oid; if (type == OBJ_REF_DELTA) { @@ -512,7 +513,8 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size, return; base = odb_read_object(the_repository->objects, &base_oid, - &type, &base_size); + &type, &base_size_st); + base_size = cast_size_t_to_ulong(base_size_st); if (!base) { error("failed to read delta-pack base object %s", oid_to_hex(&base_oid)); @@ -596,7 +598,7 @@ static void unpack_all(void) progress = start_progress(the_repository, _("Unpacking objects"), nr_objects); CALLOC_ARRAY(obj_list, nr_objects); - transaction = odb_transaction_begin(the_repository->objects); + odb_transaction_begin_or_die(the_repository->objects, &transaction, 0); for (i = 0; i < nr_objects; i++) { unpack_one(i); display_progress(progress, i + 1); @@ -668,10 +670,10 @@ int cmd_unpack_objects(int argc, /* We don't take any non-flag arguments now.. Maybe some day */ usage(unpack_usage); } - the_hash_algo->init_fn(&ctx); + git_hash_init(&ctx, the_hash_algo); unpack_all(); git_hash_update(&ctx, buffer, offset); - the_hash_algo->init_fn(&tmp_ctx); + git_hash_init(&tmp_ctx, the_hash_algo); git_hash_clone(&tmp_ctx, &ctx); git_hash_final_oid(&oid, &tmp_ctx); if (strict) { diff --git a/builtin/update-index.c b/builtin/update-index.c index 3d6646c318b98e..4c4b39a1575a38 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -875,7 +875,7 @@ static enum parse_opt_result unresolve_callback( const char *arg, int unset) { int *has_errors = opt->value; - const char *prefix = startup_info->prefix; + const char *prefix = the_repository->prefix; BUG_ON_OPT_NEG(unset); BUG_ON_OPT_ARG(arg); @@ -896,7 +896,7 @@ static enum parse_opt_result reupdate_callback( const char *arg, int unset) { int *has_errors = opt->value; - const char *prefix = startup_info->prefix; + const char *prefix = the_repository->prefix; BUG_ON_OPT_NEG(unset); BUG_ON_OPT_ARG(arg); @@ -1124,7 +1124,7 @@ int cmd_update_index(int argc, * Allow the object layer to optimize adding multiple objects in * a batch. */ - transaction = odb_transaction_begin(the_repository->objects); + odb_transaction_begin_or_die(the_repository->objects, &transaction, 0); while (ctx.argc) { if (parseopt_state != PARSE_OPT_DONE) parseopt_state = parse_options_step(&ctx, options, @@ -1133,6 +1133,8 @@ int cmd_update_index(int argc, break; switch (parseopt_state) { case PARSE_OPT_HELP: + exit(0); + case PARSE_OPT_HELP_ERROR: case PARSE_OPT_ERROR: exit(129); case PARSE_OPT_COMPLETE: diff --git a/builtin/worktree.c b/builtin/worktree.c index d21c43fde38b5e..4bc7b4f6e7199a 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -945,14 +945,17 @@ static int add(int ac, const char **av, const char *prefix, strvec_push(&cp.args, branch); if (opt_track) strvec_push(&cp.args, opt_track); - if (run_command(&cp)) - return -1; + if (run_command(&cp)) { + ret = -1; + goto cleanup; + } branch = new_branch; } else if (opt_track) { die(_("--[no-]track can only be used if a new branch is created")); } ret = add_worktree(path, branch, &opts); +cleanup: free(path); free(opt_track); free(branch_to_free); diff --git a/bundle-uri.c b/bundle-uri.c index 3b2e347288c3b7..9ca2060e9af9c1 100644 --- a/bundle-uri.c +++ b/bundle-uri.c @@ -378,7 +378,7 @@ static int download_https_uri_to_file(const char *file, const char *uri) if (child_in) fclose(child_in); if (finish_command(&cp)) - return 1; + result = 1; if (child_out) fclose(child_out); return result; @@ -946,8 +946,12 @@ static int config_to_packet_line(const char *key, const char *value, { struct packet_reader *writer = data; - if (starts_with(key, "bundle.")) - packet_write_fmt(writer->fd, "%s=%s", key, value); + if (starts_with(key, "bundle.")) { + if (value && *value) + packet_write_fmt(writer->fd, "%s=%s", key, value); + else + warning(_("config '%s' has no value"), key); + } return 0; } diff --git a/bundle.c b/bundle.c index 42327f9739cc54..fd2db2c837df60 100644 --- a/bundle.c +++ b/bundle.c @@ -296,7 +296,7 @@ int list_bundle_refs(struct bundle_header *header, int argc, const char **argv) static int is_tag_in_date_range(struct object *tag, struct rev_info *revs) { - unsigned long size; + size_t size; enum object_type type; char *buf = NULL, *line, *lineend; timestamp_t date; diff --git a/cache-tree.c b/cache-tree.c index 184f7e2635b9f4..d92f5132865f13 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -162,6 +162,54 @@ void cache_tree_invalidate_path(struct index_state *istate, const char *path) istate->cache_changed |= CACHE_TREE_CHANGED; } +/* + * Check whether this_ce and the next entry in the index form a D/F + * conflict ("path" vs "path/file"). Returns the conflicting "path/..." + * name when one is found, or NULL otherwise. + * + * The cache is sorted, so "path/file" sorts after "path" and the + * conflict is usually visible as adjacent entries. But other entries + * can sort between them -- e.g. "path-internal" sits between "path" + * and "path/file" because '-' (0x2D) precedes '/' (0x2F) -- so when + * the immediately following entry shares our prefix but starts with a + * character that sorts before '/', binary search for "path/" instead. + */ +static const char *find_df_conflict(struct index_state *istate, + const struct cache_entry *this_ce, + const struct cache_entry *next_ce) +{ + const char *this_name = this_ce->name; + const char *next_name = next_ce->name; + int this_len = ce_namelen(this_ce); + const struct cache_entry *other; + struct strbuf probe = STRBUF_INIT; + int pos; + + if (this_len >= ce_namelen(next_ce) || + next_name[this_len] > '/' || + strncmp(this_name, next_name, this_len)) + return NULL; + + if (next_name[this_len] == '/') + return next_name; + + strbuf_add(&probe, this_name, this_len); + strbuf_addch(&probe, '/'); + pos = index_name_pos_sparse(istate, probe.buf, probe.len); + strbuf_release(&probe); + + if (pos < 0) + pos = -pos - 1; + if (pos >= (int)istate->cache_nr) + return NULL; + other = istate->cache[pos]; + if (ce_namelen(other) > this_len && + other->name[this_len] == '/' && + !strncmp(this_name, other->name, this_len)) + return other->name; + return NULL; +} + static int verify_cache(struct index_state *istate, int flags) { unsigned i, funny; @@ -191,24 +239,18 @@ static int verify_cache(struct index_state *istate, int flags) */ funny = 0; for (i = 0; i + 1 < istate->cache_nr; i++) { - /* path/file always comes after path because of the way - * the cache is sorted. Also path can appear only once, - * which means conflicting one would immediately follow. - */ const struct cache_entry *this_ce = istate->cache[i]; const struct cache_entry *next_ce = istate->cache[i + 1]; - const char *this_name = this_ce->name; - const char *next_name = next_ce->name; - int this_len = ce_namelen(this_ce); - if (this_len < ce_namelen(next_ce) && - next_name[this_len] == '/' && - strncmp(this_name, next_name, this_len) == 0) { + const char *conflict_name; + + conflict_name = find_df_conflict(istate, this_ce, next_ce); + if (conflict_name) { if (10 < ++funny) { fprintf(stderr, "...\n"); break; } fprintf(stderr, "You have both %s and %s\n", - this_name, next_name); + this_ce->name, conflict_name); } } if (funny) @@ -474,6 +516,7 @@ static int update_one(struct cache_tree *it, int cache_tree_update(struct index_state *istate, int flags) { + int inflight = !!the_repository->objects->transaction; struct odb_transaction *transaction; int skip, i; @@ -490,10 +533,12 @@ int cache_tree_update(struct index_state *istate, int flags) trace_performance_enter(); trace2_region_enter("cache_tree", "update", istate->repo); - transaction = odb_transaction_begin(the_repository->objects); + if (!inflight) + odb_transaction_begin_or_die(the_repository->objects, &transaction, 0); i = update_one(istate->cache_tree, istate->cache, istate->cache_nr, "", 0, &skip, flags); - odb_transaction_commit(transaction); + if (!inflight) + odb_transaction_commit(transaction); trace2_region_leave("cache_tree", "update", istate->repo); trace_performance_leave("cache_tree_update"); if (i < 0) diff --git a/chdir-notify.c b/chdir-notify.c index f8bfe3cbef9aba..1237a45e2e6492 100644 --- a/chdir-notify.c +++ b/chdir-notify.c @@ -43,32 +43,6 @@ void chdir_notify_unregister(const char *name, chdir_notify_callback cb, } } -static void reparent_cb(const char *name, - const char *old_cwd, - const char *new_cwd, - void *data) -{ - char **path = data; - char *tmp = *path; - - if (!tmp) - return; - - *path = reparent_relative_path(old_cwd, new_cwd, tmp); - free(tmp); - - if (name) { - trace_printf_key(&trace_setup_key, - "setup: reparent %s to '%s'", - name, *path); - } -} - -void chdir_notify_reparent(const char *name, char **path) -{ - chdir_notify_register(name, reparent_cb, path); -} - int chdir_notify(const char *new_cwd) { struct strbuf old_cwd = STRBUF_INIT; diff --git a/chdir-notify.h b/chdir-notify.h index 81eb69d846e45d..36b4114472e31d 100644 --- a/chdir-notify.h +++ b/chdir-notify.h @@ -19,10 +19,7 @@ * chdir_notify_register("description", foo, data); * * In practice most callers will want to move a relative path to the new root; - * they can use the reparent_relative_path() helper for that. If that's all - * you're doing, you can also use the convenience function: - * - * chdir_notify_reparent("description", &my_path); + * they can use the reparent_relative_path() helper for that. * * Whenever a chdir event occurs, that will update my_path (if it's relative) * to adjust for the new cwd by freeing any existing string and allocating a @@ -43,7 +40,6 @@ typedef void (*chdir_notify_callback)(const char *name, void chdir_notify_register(const char *name, chdir_notify_callback cb, void *data); void chdir_notify_unregister(const char *name, chdir_notify_callback cb, void *data); -void chdir_notify_reparent(const char *name, char **path); /* * diff --git a/chunk-format.c b/chunk-format.c index 51b5a2c959a1cf..34ab2750f7f14a 100644 --- a/chunk-format.c +++ b/chunk-format.c @@ -101,12 +101,14 @@ int write_chunkfile(struct chunkfile *cf, void *data) return result; } -int read_table_of_contents(struct chunkfile *cf, - const unsigned char *mfile, - size_t mfile_size, - uint64_t toc_offset, - int toc_length, - unsigned expected_alignment) +static int read_table_of_contents_1(struct chunkfile *cf, + const unsigned char *mfile, + size_t mfile_size, + uint64_t toc_offset, + int toc_length, + unsigned expected_alignment, + const struct git_hash_algo *algo, + int quiet) { int i; uint32_t chunk_id; @@ -121,12 +123,14 @@ int read_table_of_contents(struct chunkfile *cf, chunk_offset = get_be64(table_of_contents + 4); if (!chunk_id) { - error(_("terminating chunk id appears earlier than expected")); + if (!quiet) + error(_("terminating chunk id appears earlier than expected")); return 1; } if (chunk_offset % expected_alignment != 0) { - error(_("chunk id %"PRIx32" not %d-byte aligned"), - chunk_id, expected_alignment); + if (!quiet) + error(_("chunk id %"PRIx32" not %d-byte aligned"), + chunk_id, expected_alignment); return 1; } @@ -134,16 +138,18 @@ int read_table_of_contents(struct chunkfile *cf, next_chunk_offset = get_be64(table_of_contents + 4); if (next_chunk_offset < chunk_offset || - next_chunk_offset > mfile_size - the_hash_algo->rawsz) { - error(_("improper chunk offset(s) %"PRIx64" and %"PRIx64""), - chunk_offset, next_chunk_offset); + next_chunk_offset > mfile_size - algo->rawsz) { + if (!quiet) + error(_("improper chunk offset(s) %"PRIx64" and %"PRIx64""), + chunk_offset, next_chunk_offset); return -1; } for (i = 0; i < cf->chunks_nr; i++) { if (cf->chunks[i].id == chunk_id) { - error(_("duplicate chunk ID %"PRIx32" found"), - chunk_id); + if (!quiet) + error(_("duplicate chunk ID %"PRIx32" found"), + chunk_id); return -1; } } @@ -156,13 +162,39 @@ int read_table_of_contents(struct chunkfile *cf, chunk_id = get_be32(table_of_contents); if (chunk_id) { - error(_("final chunk has non-zero id %"PRIx32""), chunk_id); + if (!quiet) + error(_("final chunk has non-zero id %"PRIx32""), chunk_id); return -1; } return 0; } +int read_table_of_contents(struct chunkfile *cf, + const unsigned char *mfile, + size_t mfile_size, + uint64_t toc_offset, + int toc_length, + unsigned expected_alignment) +{ + return read_table_of_contents_1(cf, mfile, mfile_size, toc_offset, + toc_length, expected_alignment, + the_hash_algo, 0); +} + +int read_table_of_contents_quiet(struct chunkfile *cf, + const unsigned char *mfile, + size_t mfile_size, + uint64_t toc_offset, + int toc_length, + unsigned expected_alignment, + const struct git_hash_algo *algo) +{ + return read_table_of_contents_1(cf, mfile, mfile_size, toc_offset, + toc_length, expected_alignment, + algo, 1); +} + struct pair_chunk_data { const unsigned char **p; size_t *size; diff --git a/chunk-format.h b/chunk-format.h index 212a0a6af18356..bc31302ed09f1b 100644 --- a/chunk-format.h +++ b/chunk-format.h @@ -39,6 +39,20 @@ int read_table_of_contents(struct chunkfile *cf, int toc_length, unsigned expected_alignment); +/* + * Like read_table_of_contents(), for a reader that treats a malformed + * table as an absent file rather than reporting it: nothing is printed + * on failure, and the trailing-checksum bound is computed with the + * given hash algorithm instead of the_hash_algo. + */ +int read_table_of_contents_quiet(struct chunkfile *cf, + const unsigned char *mfile, + size_t mfile_size, + uint64_t toc_offset, + int toc_length, + unsigned expected_alignment, + const struct git_hash_algo *algo); + #define CHUNK_NOT_FOUND (-2) /* diff --git a/ci/install-dependencies.ps1 b/ci/install-dependencies.ps1 new file mode 100755 index 00000000000000..e3b367fa54d042 --- /dev/null +++ b/ci/install-dependencies.ps1 @@ -0,0 +1,55 @@ +param( + [string]$DownloadDirectory = '.dependencies' +) + +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' + +$GitVersion = '2.54.0.windows.1' +$MesonVersion = '1.11.0' +$RustVersion = '1.96.0' + +New-Item -Path $DownloadDirectory -ItemType Directory -Force | Out-Null +New-Item -Path .git/info -ItemType Directory -Force | Out-Null +New-Item -Path .git/info/exclude -ItemType File -Force | Out-Null +Add-Content -Path .git/info/exclude -Value "/$DownloadDirectory" + +function Get-Installer { + param( + [Parameter(Mandatory = $true)][string]$Name, + [Parameter(Mandatory = $true)][string]$Url + ) + + $path = Join-Path $DownloadDirectory $Name + if (-not (Test-Path $path)) { + Write-Host "Downloading $Url" + Invoke-WebRequest $Url -OutFile $path -TimeoutSec 300 + } + return $path +} + +function Invoke-Installer { + param( + [Parameter(Mandatory = $true)][string]$FilePath, + [Parameter(Mandatory = $true)][string[]]$ArgumentList + ) + + Write-Host "Running $FilePath $($ArgumentList -join ' ')" + $process = Start-Process -Wait -PassThru -FilePath $FilePath -ArgumentList $ArgumentList + if ($process.ExitCode -ne 0) { + throw "$FilePath failed with exit code $($process.ExitCode)" + } +} + +$gitAssetVersion = $GitVersion -replace '\.windows\.\d+$', '' +$gitInstaller = Get-Installer "Git-Installer.exe" ` + "https://github.com/git-for-windows/git/releases/download/v$GitVersion/PortableGit-$gitAssetVersion-64-bit.7z.exe" +Invoke-Installer $gitInstaller @('-y', '-o"C:\Program Files\Git"') + +$mesonMsi = Get-Installer "meson.msi" ` + "https://github.com/mesonbuild/meson/releases/download/$MesonVersion/meson-$MesonVersion-64.msi" +Invoke-Installer msiexec.exe @('/i', $mesonMsi, 'INSTALLDIR=C:\Meson', '/quiet', '/norestart') + +$rustMsi = Get-Installer "rust.msi" ` + "https://static.rust-lang.org/dist/rust-$RustVersion-x86_64-pc-windows-msvc.msi" +Invoke-Installer msiexec.exe @('/i', $rustMsi, 'INSTALLDIR=C:\Rust', 'ADDLOCAL=Rustc,Cargo,Std', '/quiet', '/norestart') diff --git a/ci/lib.sh b/ci/lib.sh index 6e3799cfc3ccd5..6c52154eac11e9 100755 --- a/ci/lib.sh +++ b/ci/lib.sh @@ -215,6 +215,7 @@ then test macos != "$CI_OS_NAME" || CI_OS_NAME=osx CI_REPO_SLUG="$GITHUB_REPOSITORY" CI_JOB_ID="$GITHUB_RUN_ID" + CI_EVENT="$GITHUB_EVENT_NAME" CC="${CC_PACKAGE:-${CC:-gcc}}" DONT_SKIP_TAGS=t handle_failed_tests () { @@ -239,6 +240,13 @@ then CI_BRANCH="$CI_COMMIT_REF_NAME" CI_COMMIT="$CI_COMMIT_SHA" + case "$CI_PIPELINE_SOURCE" in + merge_request_event) + CI_EVENT=pull_request;; + *) + CI_EVENT="$CI_PIPELINE_SOURCE";; + esac + case "$OS,$CI_JOB_IMAGE" in Windows_NT,*) CI_OS_NAME=windows @@ -254,7 +262,7 @@ then CI_OS_NAME=osx JOBS=$(nproc) ;; - *,alpine:*|*,fedora:*|*,ubuntu:*|*,i386/ubuntu:*) + *,almalinux:*|*,alpine:*|*,debian:*|*,fedora:*|*,ubuntu:*|*,i386/ubuntu:*) CI_OS_NAME=linux JOBS=$(nproc) ;; @@ -319,9 +327,9 @@ export SKIP_DASHED_BUILT_INS=YesPlease # 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 +case "$CI_EVENT,$CI_BRANCH" in pull_request,*|push,*next*|push,*master*|push,*main*|push,*maint*) - export GIT_TEST_LONG=YesPlease + export GIT_TEST_LONG=${GIT_TEST_LONG:-true} ;; esac diff --git a/ci/run-build-and-minimal-fuzzers.sh b/ci/run-build-and-minimal-fuzzers.sh index e7b97952e7d485..37b24b092de2cd 100755 --- a/ci/run-build-and-minimal-fuzzers.sh +++ b/ci/run-build-and-minimal-fuzzers.sh @@ -21,6 +21,7 @@ date pack-headers pack-idx parse-attr-line +reftable url-decode-mem " diff --git a/combine-diff.c b/combine-diff.c index b7998620687ed7..fb72174918786a 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -325,7 +325,9 @@ static char *grab_blob(struct repository *r, *size = fill_textconv(r, textconv, df, &blob); free_filespec(df); } else { - blob = odb_read_object(r->objects, oid, &type, size); + size_t size_st = 0; + blob = odb_read_object(r->objects, oid, &type, &size_st); + *size = cast_size_t_to_ulong(size_st); if (!blob) die(_("unable to read %s"), oid_to_hex(oid)); if (type != OBJ_BLOB) @@ -666,7 +668,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 21b802c42026b3..e7b241e6ad8498 100644 --- a/command-list.txt +++ b/command-list.txt @@ -95,6 +95,7 @@ git-describe mainporcelain git-diagnose ancillaryinterrogators git-diff mainporcelain info git-diff-files plumbinginterrogators +git-diff-hunks plumbingmanipulators git-diff-index plumbinginterrogators git-diff-pairs plumbinginterrogators git-diff-tree plumbinginterrogators @@ -223,6 +224,7 @@ gitfaq guide gitformat-bundle developerinterfaces gitformat-chunk developerinterfaces gitformat-commit-graph developerinterfaces +gitformat-diff-hunks developerinterfaces gitformat-index developerinterfaces gitformat-pack developerinterfaces gitformat-signature developerinterfaces diff --git a/commit-graph.c b/commit-graph.c index 9abe62bd5a278a..ce2e020759ca25 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) @@ -1538,7 +1538,7 @@ static int add_packed_commits(const struct object_id *oid, struct object_info oi = OBJECT_INFO_INIT; oi.typep = &type; - if (packed_object_info(pack, offset, &oi) < 0) + if (packed_object_info(NULL, pack, offset, &oi) < 0) die(_("unable to get type of object %s"), oid_to_hex(oid)); return add_packed_commits_oi(oid, &oi, data); @@ -1653,6 +1653,7 @@ static void compute_reachable_generation_numbers( { int i; struct commit_list *list = NULL; + intmax_t steps = 0; for (i = 0; i < info->commits->nr; i++) { struct commit *c = info->commits->items[i]; @@ -1669,8 +1670,9 @@ 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; + steps++; for (parent = current->parents; parent; parent = parent->next) { repo_parse_commit(info->r, parent->item); gen = info->get_generation(parent->item, info->data); @@ -1694,6 +1696,9 @@ static void compute_reachable_generation_numbers( } } } + + trace2_data_intmax("commit-graph", info->r, + "generation-dfs-steps", steps); } static timestamp_t get_topo_level(struct commit *c, void *data) @@ -2016,8 +2021,8 @@ static void fill_oids_from_all_packs(struct write_commit_graph_context *ctx) odb_prepare_alternates(ctx->r->objects); for (source = ctx->r->objects->sources; source; source = source->next) { struct odb_source_files *files = odb_source_files_downcast(source); - packfile_store_for_each_object(files->packed, &oi, add_packed_commits_oi, - ctx, &opts); + odb_source_for_each_object(&files->packed->base, &oi, add_packed_commits_oi, + ctx, &opts); } if (ctx->progress_done < ctx->approx_nr_objects) @@ -2605,7 +2610,7 @@ int write_commit_graph(struct odb_source *source, g = prepare_commit_graph(ctx.r); for (struct commit_graph *chain = g; chain; chain = chain->base_graph) - g->topo_levels = &topo_levels; + chain->topo_levels = &topo_levels; if (flags & COMMIT_GRAPH_WRITE_BLOOM_FILTERS) ctx.changed_paths = 1; diff --git a/commit-graph.h b/commit-graph.h index f6a54336415453..13ca4ff010fa18 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 5df471a313cf6b..d7221fe389c046 100644 --- a/commit-reach.c +++ b/commit-reach.c @@ -108,11 +108,14 @@ static int paint_down_to_common(struct repository *r, { compare_commits_by_gen_then_commit_date } }; int i; + int gen_ordered = 1; timestamp_t last_gen = GENERATION_NUMBER_INFINITY; struct commit_list **tail = result; - if (!min_generation && !corrected_commit_dates_enabled(r)) + if (!min_generation && !corrected_commit_dates_enabled(r)) { queue.pq.compare = compare_commits_by_commit_date; + gen_ordered = 0; + } one->object.flags |= PARENT1; if (!n) { @@ -147,11 +150,12 @@ static int paint_down_to_common(struct repository *r, commit->object.flags |= RESULT; tail = commit_list_append(commit, tail); /* - * The queue is generation-ordered; no - * remaining common ancestor can be a + * When the queue is generation-ordered, + * no remaining common ancestor can be a * descendant of this one. */ if (!(mb_flags & MERGE_BASE_FIND_ALL) && + gen_ordered && generation < GENERATION_NUMBER_INFINITY) break; } @@ -1121,6 +1125,7 @@ void ahead_behind(struct repository *r, struct nonstale_queue queue = { { .compare = compare_commits_by_gen_then_commit_date } }; + void *entry; size_t width = DIV_ROUND_UP(commits_nr, BITS_IN_EWORD); if (!commits_nr || !counts_nr) @@ -1186,8 +1191,8 @@ 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.pq.nr; i++) - free_bit_array(queue.pq.array[i].data); + prio_queue_for_each(&queue.pq, entry) + free_bit_array(entry); clear_bit_arrays(&bit_arrays); clear_nonstale_queue(&queue); } @@ -1320,7 +1325,7 @@ int get_branch_base_for_tip(struct repository *r, size_t bases_nr) { int best_index = -1; - struct commit *branch_point = NULL; + struct commit *c, *branch_point = NULL; struct prio_queue queue = { compare_commits_by_gen_then_commit_date }; int found_missing_gen = 0; @@ -1373,8 +1378,7 @@ int get_branch_base_for_tip(struct repository *r, prio_queue_put(&queue, c); } - while (queue.nr) { - struct commit *c = prio_queue_get(&queue); + while ((c = prio_queue_get(&queue))) { int best_for_c = get_best(c); int best_for_p, positive; struct commit *parent; diff --git a/commit.c b/commit.c index fd8723502ed332..ad26f0b40a9c64 100644 --- a/commit.c +++ b/commit.c @@ -395,7 +395,7 @@ const void *repo_get_commit_buffer(struct repository *r, const void *ret = get_cached_commit_buffer(r, commit, sizep); if (!ret) { enum object_type type; - unsigned long size; + size_t size; ret = odb_read_object(r->objects, &commit->object.oid, &type, &size); if (!ret) die("cannot read commit object %s", @@ -404,7 +404,7 @@ const void *repo_get_commit_buffer(struct repository *r, die("expected commit for %s, got %s", oid_to_hex(&commit->object.oid), type_name(type)); if (sizep) - *sizep = size; + *sizep = cast_size_t_to_ulong(size); } return ret; } @@ -437,7 +437,7 @@ static inline void set_commit_tree(struct commit *c, struct tree *t) static void load_tree_from_commit_contents(struct repository *r, struct commit *commit) { enum object_type type; - unsigned long size; + size_t size; char *buf; const char *p; struct object_id tree_oid; @@ -604,7 +604,7 @@ int repo_parse_commit_internal(struct repository *r, { enum object_type type; void *buffer; - unsigned long size; + size_t size; struct object_info oi = { .typep = &type, .sizep = &size, @@ -760,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) { @@ -795,24 +782,17 @@ void commit_list_sort_by_date(struct commit_list **list) struct commit *pop_most_recent_commit(struct prio_queue *queue, unsigned int mark) { - struct commit *ret = prio_queue_peek(queue); - int get_pending = 1; + struct commit *ret = prio_queue_get(queue); struct commit_list *parents = ret->parents; while (parents) { struct commit *commit = parents->item; if (!repo_parse_commit(the_repository, commit) && !(commit->object.flags & mark)) { commit->object.flags |= mark; - if (get_pending) - prio_queue_replace(queue, commit); - else - prio_queue_put(queue, commit); - get_pending = 0; + prio_queue_put(queue, commit); } parents = parents->next; } - if (get_pending) - prio_queue_get(queue); return ret; } @@ -1313,7 +1293,7 @@ static void handle_signed_tag(const struct commit *parent, struct commit_extra_h struct merge_remote_desc *desc; struct commit_extra_header *mergetag; char *buf; - unsigned long size; + size_t size; enum object_type type; struct strbuf payload = STRBUF_INIT; struct strbuf signature = STRBUF_INIT; diff --git a/commit.h b/commit.h index 5352056f87abfa..1061ed791bcad6 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 */ diff --git a/common-init.c b/common-init.c index 5cc73f058c222b..d26c9c1f20239e 100644 --- a/common-init.c +++ b/common-init.c @@ -5,7 +5,10 @@ #include "exec-cmd.h" #include "gettext.h" #include "attr.h" +#include "odb.h" +#include "parse.h" #include "repository.h" +#include "replace-object.h" #include "setup.h" #include "strbuf.h" #include "trace2.h" @@ -31,6 +34,22 @@ static void restore_sigpipe_to_default(void) signal(SIGPIPE, SIG_DFL); } +static void setup_environment(void) +{ + char *git_replace_ref_base; + const char *replace_ref_base; + + if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT)) + disable_replace_refs(); + replace_ref_base = getenv(GIT_REPLACE_REF_BASE_ENVIRONMENT); + git_replace_ref_base = xstrdup(replace_ref_base ? replace_ref_base + : "refs/replace/"); + update_ref_namespace(NAMESPACE_REPLACE, git_replace_ref_base); + + if (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0)) + fetch_if_missing = 0; +} + void init_git(const char **argv) { struct strbuf tmp = STRBUF_INIT; @@ -51,6 +70,7 @@ void init_git(const char **argv) git_setup_gettext(); initialize_repository(the_repository); + setup_environment(); attr_start(); diff --git a/compat/mingw.c b/compat/mingw.c index aa7525f419cb64..3eca3a7f2e87b2 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 @@ -2251,16 +2252,26 @@ 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); - - if (TerminateProcess(h, -1)) { - CloseHandle(h); - return 0; + 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 (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()); + return ret; } else if (pid > 0 && sig == 0) { HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); if (h) { @@ -3392,7 +3403,7 @@ int is_valid_win32_path(const char *path, int allow_literal_nul) const char *p = path; int preceding_space_or_period = 0, i = 0, periods = 0; - if (!protect_ntfs) + if (!repo_protect_ntfs(the_repository)) return 1; skip_dos_drive_prefix((char **)&path); @@ -3610,7 +3621,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 @@ -3647,6 +3665,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/msvc-posix.h b/compat/msvc-posix.h index c500b8b4aaf945..7ce39b8d3f0dd0 100644 --- a/compat/msvc-posix.h +++ b/compat/msvc-posix.h @@ -16,7 +16,6 @@ #define __attribute__(x) #define strcasecmp _stricmp #define strncasecmp _strnicmp -#define ftruncate _chsize #define strtoull _strtoui64 #define strtoll _strtoi64 @@ -30,4 +29,27 @@ typedef int sigset_t; #include "mingw-posix.h" +/* + * MSVC's `_chsize()` takes a 32-bit `long` and silently truncates files + * to 2 GiB. `_chsize_s()` accepts a 64-bit length but returns 0 on + * success or an errno value on failure, rather than the -1/errno + * convention POSIX `ftruncate()` callers expect. Wrap it so callers + * that test the return value as `< 0` or against `-1` keep working. + * + * Note: this declaration must follow `#include "mingw-posix.h"` so + * `off_t` resolves to `off64_t` and the parameter type matches the + * underlying `_chsize_s()` width. + */ +static inline int msvc_ftruncate(int fd, off_t length) +{ + int err = _chsize_s(fd, length); + + if (err) { + errno = err; + return -1; + } + return 0; +} +#define ftruncate msvc_ftruncate + #endif /* COMPAT_MSVC_POSIX_H */ diff --git a/compat/posix.h b/compat/posix.h index faaae1b6555d1b..e2e794cad7d419 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 +#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_GNUC_PREREQ(maj, min) 0 +# define GIT_CLANG_PREREQ(maj, min) 0 #endif /* @@ -35,17 +46,17 @@ * 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__ +#if defined(__MINGW32__) || defined(__MINGW64__) #define _POSIX_C_SOURCE 1 #elif defined(__sun__) /* diff --git a/compat/precompose_utf8.c b/compat/precompose_utf8.c index 43b3be011439ef..8077f6235b0cae 100644 --- a/compat/precompose_utf8.c +++ b/compat/precompose_utf8.c @@ -19,6 +19,11 @@ typedef char *iconv_ibp; static const char *repo_encoding = "UTF-8"; static const char *path_encoding = "UTF-8-MAC"; +static size_t dirent_prec_psx_size(size_t max_name_len) +{ + return st_add(offsetof(dirent_prec_psx, d_name), max_name_len); +} + static size_t has_non_ascii(const char *s, size_t maxlen, size_t *strlen_c) { const uint8_t *ptr = (const uint8_t *)s; @@ -48,16 +53,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 +76,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 +94,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; } @@ -110,8 +119,8 @@ const char *precompose_argv_prefix(int argc, const char **argv, const char *pref PREC_DIR *precompose_utf8_opendir(const char *dirname) { PREC_DIR *prec_dir = xmalloc(sizeof(PREC_DIR)); - prec_dir->dirent_nfc = xmalloc(sizeof(dirent_prec_psx)); - prec_dir->dirent_nfc->max_name_len = sizeof(prec_dir->dirent_nfc->d_name); + prec_dir->dirent_nfc = xmalloc(dirent_prec_psx_size(NAME_MAX + 1)); + prec_dir->dirent_nfc->max_name_len = NAME_MAX + 1; prec_dir->dirp = opendir(dirname); if (!prec_dir->dirp) { @@ -130,7 +139,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 */ @@ -139,8 +150,7 @@ struct dirent_prec_psx *precompose_utf8_readdir(PREC_DIR *prec_dir) int ret_errno = errno; if (new_maxlen > prec_dir->dirent_nfc->max_name_len) { - size_t new_len = sizeof(dirent_prec_psx) + new_maxlen - - sizeof(prec_dir->dirent_nfc->d_name); + size_t new_len = dirent_prec_psx_size(new_maxlen); prec_dir->dirent_nfc = xrealloc(prec_dir->dirent_nfc, new_len); prec_dir->dirent_nfc->max_name_len = new_maxlen; @@ -149,7 +159,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/precompose_utf8.h b/compat/precompose_utf8.h index fea06cf28a52df..c7c3cc211e5031 100644 --- a/compat/precompose_utf8.h +++ b/compat/precompose_utf8.h @@ -14,11 +14,12 @@ typedef struct dirent_prec_psx { /* * See http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/dirent.h.html - * NAME_MAX + 1 should be enough, but some systems have - * NAME_MAX=255 and strlen(d_name) may return 508 or 510 - * Solution: allocate more when needed, see precompose_utf8_readdir() + * Start with room for NAME_MAX + 1 bytes, but keep d_name as a + * flexible array. Some systems have NAME_MAX=255 while strlen(d_name) + * from readdir() may return 508 or 510 bytes. Grow the allocation as + * needed in precompose_utf8_readdir(). */ - char d_name[NAME_MAX+1]; + char d_name[FLEX_ARRAY]; } dirent_prec_psx; diff --git a/compat/win32/exit-process.h b/compat/win32/exit-process.h new file mode 100644 index 00000000000000..26004161bcbdc3 --- /dev/null +++ b/compat/win32/exit-process.h @@ -0,0 +1,166 @@ +#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 + +/* + * 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); + } + + CloseHandle(process); + return 0; +} + +#endif diff --git a/compat/win32/path-utils.c b/compat/win32/path-utils.c index 966ef779b9ca9b..f779f367cfc359 100644 --- a/compat/win32/path-utils.c +++ b/compat/win32/path-utils.c @@ -2,6 +2,7 @@ #include "../../git-compat-util.h" #include "../../environment.h" +#include "../../repository.h" int win32_has_dos_drive_prefix(const char *path) { @@ -75,7 +76,7 @@ int win32_fspathncmp(const char *a, const char *b, size_t count) } else if (is_dir_sep(*b)) return +1; - diff = ignore_case ? + diff = repo_ignore_case(the_repository) ? (unsigned char)tolower(*a) - (int)(unsigned char)tolower(*b) : (unsigned char)*a - (int)(unsigned char)*b; if (diff) diff --git a/config.c b/config.c index a1b92fe083cf43..9d6d7872d76c10 100644 --- a/config.c +++ b/config.c @@ -235,23 +235,20 @@ static int prepare_include_condition_pattern(const struct key_value_info *kvi, return 0; } -static int include_by_gitdir(const struct key_value_info *kvi, - const struct config_options *opts, - const char *cond, size_t cond_len, int icase) +static int include_by_path(const struct key_value_info *kvi, + const char *path, + const char *cond, size_t cond_len, int icase) { struct strbuf text = STRBUF_INIT; struct strbuf pattern = STRBUF_INIT; size_t prefix; int ret = 0; - const char *git_dir; int already_tried_absolute = 0; - if (opts->git_dir) - git_dir = opts->git_dir; - else + if (!path) goto done; - strbuf_realpath(&text, git_dir, 1); + strbuf_realpath(&text, path, 1); strbuf_add(&pattern, cond, cond_len); ret = prepare_include_condition_pattern(kvi, &pattern, &prefix); if (ret < 0) @@ -284,7 +281,7 @@ static int include_by_gitdir(const struct key_value_info *kvi, * which'll do the right thing */ strbuf_reset(&text); - strbuf_add_absolute_path(&text, git_dir); + strbuf_add_absolute_path(&text, path); already_tried_absolute = 1; goto again; } @@ -400,9 +397,15 @@ static int include_condition_is_true(const struct key_value_info *kvi, const struct config_options *opts = inc->opts; if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len)) - return include_by_gitdir(kvi, opts, cond, cond_len, 0); + return include_by_path(kvi, opts->git_dir, cond, cond_len, 0); else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len)) - return include_by_gitdir(kvi, opts, cond, cond_len, 1); + return include_by_path(kvi, opts->git_dir, cond, cond_len, 1); + else if (skip_prefix_mem(cond, cond_len, "worktree:", &cond, &cond_len)) + return include_by_path(kvi, inc->repo ? repo_get_work_tree(inc->repo) : NULL, + cond, cond_len, 0); + else if (skip_prefix_mem(cond, cond_len, "worktree/i:", &cond, &cond_len)) + return include_by_path(kvi, inc->repo ? repo_get_work_tree(inc->repo) : NULL, + cond, cond_len, 1); else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len)) return include_by_branch(inc, cond, cond_len); else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond, @@ -536,11 +539,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 +558,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 +576,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 +588,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) @@ -1442,7 +1465,7 @@ int git_config_from_blob_oid(config_fn_t fn, { enum object_type type; char *buf; - unsigned long size; + size_t size; int ret; buf = odb_read_object(repo->objects, oid, &type, &size); diff --git a/config.h b/config.h index bf47fb3afc61bf..31fe3e29611e11 100644 --- a/config.h +++ b/config.h @@ -343,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. diff --git a/config.mak.uname b/config.mak.uname index f9a5ad97209dfd..9ebd240378ca59 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -173,6 +173,19 @@ ifeq ($(uname_S),Darwin) NEEDS_GOOD_LIBICONV = UnfortunatelyYes endif + # ld reports "PROJECT:{ld,ld64,dyld}-NNN", match any of the three. + LD_MAJOR_VERSION = $(shell ld -v 2>&1 | sed -n 's/.*PROJECT:[^ ]*-\([0-9][0-9]*\).*/\1/p') + + # Silence the Xcode 15+ warning about archives listed more than once. + ifeq ($(shell test -n "$(LD_MAJOR_VERSION)" && test "$(LD_MAJOR_VERSION)" -ge 907 && echo 1),1) + BASIC_LDFLAGS += -Wl,-no_warn_duplicate_libraries + endif + + # Silence the Xcode 16.3+ warning about __DATA,__common alignment. + 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 diff --git a/connect.c b/connect.c index 47e39d2a7316ae..1d74c1eda21f93 100644 --- a/connect.c +++ b/connect.c @@ -517,7 +517,7 @@ static void send_capabilities(int fd_out, struct packet_reader *reader) int get_remote_bundle_uri(int fd_out, struct packet_reader *reader, struct bundle_list *bundles, int stateless_rpc) { - int line_nr = 1; + int line_nr = 1, err = 0; /* Assert bundle-uri support */ ensure_server_supports_v2("bundle-uri"); @@ -536,10 +536,19 @@ int get_remote_bundle_uri(int fd_out, struct packet_reader *reader, const char *line = reader->line; line_nr++; + /* + * Do not parse if an error was encountered, but + * continue draining the response so no stale data + * is left in the reader for subsequent protocol + * exchanges. + */ + if (err) + continue; + if (!bundle_uri_parse_line(bundles, line)) continue; - return error(_("error on bundle-uri response line %d: %s"), + err = error(_("error on bundle-uri response line %d: %s"), line_nr, line); } @@ -554,7 +563,7 @@ int get_remote_bundle_uri(int fd_out, struct packet_reader *reader, check_stateless_delimiter(stateless_rpc, reader, _("expected response end packet after ref listing")); - return 0; + return err; } struct ref **get_remote_refs(int fd_out, struct packet_reader *reader, diff --git a/connected.c b/connected.c index 7e269768327238..929b9bd28d6fab 100644 --- a/connected.c +++ b/connected.c @@ -11,6 +11,62 @@ #include "packfile.h" #include "promisor-remote.h" +static int promised_object_cb(const struct object_id *oid UNUSED, + struct object_info *oi UNUSED, + void *payload) +{ + bool *found = payload; + *found = true; + return 1; +} + +/* + * For partial clones, we don't want to have to do a regular connectivity check + * because we have to enumerate and exclude all promisor objects (slow), and + * then the connectivity check itself becomes a no-op because in a partial + * clone every object is a promisor object. Instead, just make sure we + * received, in a promisor packfile, the objects pointed to by each wanted ref. + * + * Before checking for promisor packs, be sure we have the latest pack-files + * loaded into memory. + * + * Returns 1 when all object IDs have been found in promisor packs, in which + * case we're fully connected and thus done. Returns 0 when we have found + * objects in non-promisor packs, in which case we'll have to fall back to the + * rev-list-based connectivity checks. Returns a negative error code on error. + */ +static int check_connected_promisor(oid_iterate_fn fn, + void *cb_data, + const struct object_id **oid) +{ + struct odb_for_each_object_options opts = { + .flags = ODB_FOR_EACH_OBJECT_PROMISOR_ONLY, + .prefix_hex_len = the_repository->hash_algo->hexsz, + }; + int err; + + odb_reprepare(the_repository->objects); + do { + bool found = false; + + opts.prefix = *oid; + + err = odb_for_each_object_ext(the_repository->objects, NULL, + promised_object_cb, &found, &opts); + if (err < 0) + return err; + + /* + * We have found an object that is not part of a promisor pack, + * and thus we cannot skip the full connectivity check. + */ + if (!found) + return 0; + } while ((*oid = fn(cb_data)) != NULL); + + return 1; +} + /* * If we feed all the commits we want to verify to this command * @@ -46,42 +102,16 @@ int check_connected(oid_iterate_fn fn, void *cb_data, } if (repo_has_promisor_remote(the_repository)) { - /* - * For partial clones, we don't want to have to do a regular - * connectivity check because we have to enumerate and exclude - * all promisor objects (slow), and then the connectivity check - * itself becomes a no-op because in a partial clone every - * object is a promisor object. Instead, just make sure we - * received, in a promisor packfile, the objects pointed to by - * each wanted ref. - * - * Before checking for promisor packs, be sure we have the - * latest pack-files loaded into memory. - */ - odb_reprepare(the_repository->objects); - do { - struct packed_git *p; - - repo_for_each_pack(the_repository, p) { - if (!p->pack_promisor) - continue; - if (find_pack_entry_one(oid, p)) - goto promisor_pack_found; - } - /* - * Fallback to rev-list with oid and the rest of the - * object IDs provided by fn. - */ - goto no_promisor_pack_found; -promisor_pack_found: - ; - } while ((oid = fn(cb_data)) != NULL); - if (opt->err_fd) - close(opt->err_fd); - return 0; + err = check_connected_promisor(fn, cb_data, &oid); + if (err) { + if (opt->err_fd) + close(opt->err_fd); + if (err > 0) + err = 0; + return err; + } } -no_promisor_pack_found: if (opt->shallow_file) { strvec_push(&rev_list.args, "--shallow-file"); strvec_push(&rev_list.args, opt->shallow_file); diff --git a/contrib/Makefile b/contrib/Makefile index 787cd07f52ed03..1203c7263d172f 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -1,10 +1,22 @@ +include ../config.mak.uname +-include ../config.mak.autogen +-include ../config.mak + + +ifeq ($(uname_S),Darwin) +OS_CONTRIB += credential/osxkeychain +endif + all:: + $(foreach dir,$(OS_CONTRIB),$(MAKE) -C $(dir) $@;) test:: $(MAKE) -C diff-highlight $@ $(MAKE) -C subtree $@ + $(foreach dir,$(OS_CONTRIB),$(MAKE) -C $(dir) $@;) clean:: $(MAKE) -C contacts $@ $(MAKE) -C diff-highlight $@ $(MAKE) -C subtree $@ + $(foreach dir,$(OS_CONTRIB),$(MAKE) -C $(dir) $@;) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index a8e7c6ddbfb2b1..e8757877104eb9 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -2195,7 +2195,7 @@ __git_log_common_options=" --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/credential/osxkeychain/Makefile b/contrib/credential/osxkeychain/Makefile index 219b0d7f49e016..d9fba07e8dc115 100644 --- a/contrib/credential/osxkeychain/Makefile +++ b/contrib/credential/osxkeychain/Makefile @@ -10,4 +10,6 @@ install: clean: $(MAKE) -C ../../.. clean-git-credential-osxkeychain -.PHONY: all git-credential-osxkeychain install clean +test: git-credential-osxkeychain + +.PHONY: all git-credential-osxkeychain install clean test diff --git a/contrib/credential/wincred/git-credential-wincred.c b/contrib/credential/wincred/git-credential-wincred.c index 73c2b9b72ab53e..22eb27ca31dea0 100644 --- a/contrib/credential/wincred/git-credential-wincred.c +++ b/contrib/credential/wincred/git-credential-wincred.c @@ -121,10 +121,10 @@ static int match_part_last(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim) static int match_cred_password(const CREDENTIALW *cred) { int ret; - WCHAR *cred_password = xmalloc(cred->CredentialBlobSize); - wcsncpy_s(cred_password, cred->CredentialBlobSize, - (LPCWSTR)cred->CredentialBlob, - cred->CredentialBlobSize / sizeof(WCHAR)); + size_t wlen = cred->CredentialBlobSize / sizeof(WCHAR); + WCHAR *cred_password = xmalloc((wlen + 1) * sizeof(WCHAR)); + wcsncpy_s(cred_password, wlen + 1, + (LPCWSTR)cred->CredentialBlob, wlen); ret = !wcscmp(cred_password, password); free(cred_password); return ret; @@ -208,8 +208,8 @@ static void store_credential(void) if (oauth_refresh_token) { wlen = _scwprintf(L"%s\r\noauth_refresh_token=%s", password, oauth_refresh_token); - secret = xmalloc(sizeof(WCHAR) * wlen); - _snwprintf_s(secret, sizeof(WCHAR) * wlen, wlen, L"%s\r\noauth_refresh_token=%s", password, oauth_refresh_token); + secret = xmalloc((wlen + 1) * sizeof(WCHAR)); + _snwprintf_s(secret, wlen + 1, wlen, L"%s\r\noauth_refresh_token=%s", password, oauth_refresh_token); } else { secret = _wcsdup(password); } diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh index 18d2b564487e91..c10f283b38d92d 100755 --- a/contrib/subtree/t/t7900-subtree.sh +++ b/contrib/subtree/t/t7900-subtree.sh @@ -75,7 +75,7 @@ test_create_pre2_32_repo () { # # 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: @@ -99,7 +99,7 @@ test_create_subtree_add () { } test_expect_success 'shows short help text for -h' ' - test_expect_code 129 git subtree -h >out 2>err && + git subtree -h >out 2>err && test_must_be_empty err && grep -e "^ *or: git subtree pull" out && grep -F -e "--[no-]annotate" out diff --git a/csum-file.c b/csum-file.c index d7a682c2b62102..fe18ee1de3cd0c 100644 --- a/csum-file.c +++ b/csum-file.c @@ -57,6 +57,7 @@ void hashflush(struct hashfile *f) void free_hashfile(struct hashfile *f) { + git_hash_discard(&f->ctx); free(f->buffer); free(f->check_buffer); free(f); @@ -101,15 +102,6 @@ int finalize_hashfile(struct hashfile *f, unsigned char *result, return fd; } -void discard_hashfile(struct hashfile *f) -{ - if (0 <= f->check_fd) - close(f->check_fd); - if (0 <= f->fd) - close(f->fd); - free_hashfile(f); -} - void hashwrite(struct hashfile *f, const void *buf, uint32_t count) { while (count) { @@ -176,7 +168,7 @@ struct hashfile *hashfd_ext(const struct git_hash_algo *algop, f->skip_hash = 0; f->algop = unsafe_hash_algo(algop); - f->algop->init_fn(&f->ctx); + git_hash_init(&f->ctx, f->algop); f->buffer_len = opts->buffer_len ? opts->buffer_len : DEFAULT_IO_BUFFER_SIZE; f->buffer = xmalloc(f->buffer_len); @@ -201,7 +193,7 @@ void hashfile_checkpoint_init(struct hashfile *f, struct hashfile_checkpoint *checkpoint) { memset(checkpoint, 0, sizeof(*checkpoint)); - f->algop->init_fn(&checkpoint->ctx); + git_hash_init(&checkpoint->ctx, f->algop); } void hashfile_checkpoint(struct hashfile *f, struct hashfile_checkpoint *checkpoint) @@ -224,6 +216,11 @@ int hashfile_truncate(struct hashfile *f, struct hashfile_checkpoint *checkpoint return 0; } +void hashfile_checkpoint_release(struct hashfile_checkpoint *checkpoint) +{ + git_hash_discard(&checkpoint->ctx); +} + void crc32_begin(struct hashfile *f) { f->crc32 = crc32(0, NULL, 0); @@ -248,7 +245,7 @@ int hashfile_checksum_valid(const struct git_hash_algo *algop, if (total_len < algop->rawsz) return 0; /* say "too short"? */ - algop->init_fn(&ctx); + git_hash_init(&ctx, algop); git_hash_update(&ctx, data, data_len); git_hash_final(got, &ctx); diff --git a/csum-file.h b/csum-file.h index a9b390d3366875..6ed74d1637e8a4 100644 --- a/csum-file.h +++ b/csum-file.h @@ -39,6 +39,7 @@ struct hashfile_checkpoint { void hashfile_checkpoint_init(struct hashfile *, struct hashfile_checkpoint *); void hashfile_checkpoint(struct hashfile *, struct hashfile_checkpoint *); int hashfile_truncate(struct hashfile *, struct hashfile_checkpoint *); +void hashfile_checkpoint_release(struct hashfile_checkpoint *); /* finalize_hashfile flags */ #define CSUM_CLOSE 1 @@ -52,7 +53,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; }; @@ -74,7 +75,6 @@ void free_hashfile(struct hashfile *f); * Finalize the hashfile by flushing data to disk and free'ing it. */ int finalize_hashfile(struct hashfile *, unsigned char *, enum fsync_component, unsigned int); -void discard_hashfile(struct hashfile *); void hashwrite(struct hashfile *, const void *, uint32_t); void hashflush(struct hashfile *f); void crc32_begin(struct hashfile *); diff --git a/date.c b/date.c index 05b78d852f0705..014065b419aee7 100644 --- a/date.c +++ b/date.c @@ -1074,7 +1074,7 @@ void datestamp(struct strbuf *out) * * 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 deref such adjustments until we are sure + * 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) diff --git a/delta-islands.c b/delta-islands.c index f4d2468790ce4f..e71a7e1c055dc8 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/delta.h b/delta.h index fad68cfc45f6f4..eb5c6d2fdb9c51 100644 --- a/delta.h +++ b/delta.h @@ -75,9 +75,9 @@ diff_delta(const void *src_buf, unsigned long src_bufsize, * *trg_bufsize is updated with its size. On failure a NULL pointer is * returned. The returned buffer must be freed by the caller. */ -void *patch_delta(const void *src_buf, unsigned long src_size, - const void *delta_buf, unsigned long delta_size, - unsigned long *dst_size); +void *patch_delta(const void *src_buf, size_t src_size, + const void *delta_buf, size_t delta_size, + size_t *dst_size); /* the smallest possible delta size is 4 bytes */ #define DELTA_SIZE_MIN 4 @@ -86,11 +86,8 @@ void *patch_delta(const void *src_buf, unsigned long src_size, * This must be called twice on the delta data buffer, first to get the * expected source buffer size, and again to get the target buffer size. */ -/* - * Size_t variant that doesn't truncate - use for >4GB objects on Windows. - */ -static inline size_t get_delta_hdr_size_sz(const unsigned char **datap, - const unsigned char *top) +static inline size_t get_delta_hdr_size(const unsigned char **datap, + const unsigned char *top) { const unsigned char *data = *datap; size_t cmd, size = 0; @@ -104,11 +101,4 @@ static inline size_t get_delta_hdr_size_sz(const unsigned char **datap, return size; } -static inline unsigned long get_delta_hdr_size(const unsigned char **datap, - const unsigned char *top) -{ - size_t size = get_delta_hdr_size_sz(datap, top); - return cast_size_t_to_ulong(size); -} - #endif diff --git a/diff-hunks.c b/diff-hunks.c new file mode 100644 index 00000000000000..9830df07fa1aa5 --- /dev/null +++ b/diff-hunks.c @@ -0,0 +1,1034 @@ +/* + * Precomputed diff hunks, keyed by diff input. + * + * A single store at .git/objects/info/diff-hunks maps an (old blob, + * new blob, xdl_opts) key to the hunk coordinates of diffing the pair. + * The key determines the diff result (only trim-stable pairs are + * recorded; see diff-hunks.h), so an entry is valid in any context it + * recurs in, independent of path. Reading is on by default + * (core.diffHunks); writing is off by default and enabled per run or + * by configuration (see diff_hunks_write_enabled), so an ordinary + * command populates the store only during a warming run the + * repository owner opts into. + * + * File layout: + * Header: "DHPF"(4) + version(1) + hash_version(1) + * + num_chunks(1) + reserved(1) + * Table of contents (chunk-format) + * DHIX chunk: sorted entries, each + * old_blob_oid, new_blob_oid, xdl_opts(4), hdat_offset(4) + * DHDT chunk: per entry, num_hunks(4) followed by that many 16-byte hunks + * Trailing hash checksum + */ +#include "git-compat-util.h" +#include "chunk-format.h" +#include "config.h" +#include "csum-file.h" +#include "diff-hunks.h" +#include "diff-provider-internal.h" +#include "diff.h" +#include "gettext.h" +#include "hash.h" +#include "hashmap.h" +#include "lockfile.h" +#include "odb.h" +#include "path.h" +#include "repo-settings.h" +#include "repository.h" +#include "strbuf.h" +#include "wrapper.h" + +#define DIFF_HUNKS_SIGNATURE 0x44485046 /* "DHPF" */ +/* + * Bump when the on-disk format changes, or when xdiff's emitted hunk + * coordinates change for a fixed (blobs, xdl_opts) key: an old store + * would otherwise serve stale hunks and change command output. + */ +#define DIFF_HUNKS_VERSION 1 +#define DIFF_HUNKS_HEADER_SIZE 8 + +#define DIFF_HUNKS_CHUNKID_INDEX 0x44484958 /* "DHIX" */ +#define DIFF_HUNKS_CHUNKID_DATA 0x44484454 /* "DHDT" */ + +/* + * Each hunk is 16 bytes on disk: + * old_start(4) old_count(4) new_start(4) new_count(4) + */ +#define DIFF_HUNKS_HUNK_SIZE (4 * sizeof(uint32_t)) + +/* + * Result of a store lookup: num_hunks records encoded in the store's mmap, + * valid until the store is freed. Read them with nth_precomputed_hunk(). + */ +struct precomputed_entry { + uint32_t num_hunks; + const unsigned char *hunk_data; +}; + +/* Decode a single hunk from the raw on-disk format. */ +static inline void decode_precomputed_hunk(const unsigned char *data, + struct precomputed_hunk *h) +{ + h->old_start = get_be32(data); + h->old_count = get_be32(data + 4); + h->new_start = get_be32(data + 8); + h->new_count = get_be32(data + 12); +} + +/* Decode the nth hunk of a lookup result into *h. */ +static inline void nth_precomputed_hunk(const struct precomputed_entry *e, + uint32_t n, struct precomputed_hunk *h) +{ + decode_precomputed_hunk(e->hunk_data + (size_t)n * DIFF_HUNKS_HUNK_SIZE, h); +} + +/* Byte length of the (old_oid, new_oid, xdl_opts) lookup key. */ +static size_t store_index_key_size(const struct git_hash_algo *algo) +{ + return 2 * algo->rawsz + sizeof(uint32_t); +} + +/* Index entry: the lookup key followed by the 4-byte offset into DHDT. */ +static size_t store_index_entry_size(const struct git_hash_algo *algo) +{ + return store_index_key_size(algo) + sizeof(uint32_t); +} + +/* + * The smallest a valid store file can be: the header, a table of contents + * with one entry per chunk plus a terminating entry, and the trailing + * checksum. + */ +static size_t store_min_size(const struct git_hash_algo *algo, + uint8_t num_chunks) +{ + size_t toc_size = (num_chunks + 1) * CHUNK_TOC_ENTRY_SIZE; + + return DIFF_HUNKS_HEADER_SIZE + toc_size + algo->rawsz; +} + +/* + * Decode an index entry's key into pointers to the two oids and the + * xdl_opts value (on-disk: old_oid, new_oid, then xdl_opts as a + * big-endian uint32). + */ +static void decode_store_index_key(const unsigned char *entry, unsigned int rawsz, + const unsigned char **old_hash, + const unsigned char **new_hash, + uint32_t *xdl_opts) +{ + *old_hash = entry; + *new_hash = entry + rawsz; + *xdl_opts = get_be32(entry + 2 * rawsz); +} + +/* The DHDT offset stored in an index entry, in the field after its key. */ +static uint32_t index_entry_hdat_offset(const unsigned char *entry, size_t keysz) +{ + return get_be32(entry + keysz); +} + +static char *diff_hunks_store_path(struct repository *r) +{ + return xstrfmt("%s/info/diff-hunks", repo_get_object_directory(r)); +} + +struct diff_hunks_store { + const unsigned char *data; + size_t data_len; + const struct git_hash_algo *hash_algo; + const unsigned char *index; + uint32_t num_entries; + const unsigned char *hdat; + size_t hdat_size; + + /* Consultation counters; see diff_hunks_read_stats(). */ + unsigned long read_hits; + unsigned long read_misses; +}; + +static void free_store(struct diff_hunks_store *s) +{ + if (!s) + return; + if (s->data) + munmap((void *)s->data, s->data_len); + free(s); +} + +/* + * Open, mmap, and parse the store at fname. Returns the parsed store + * or NULL on any error. The diff output is unaffected either way; + * corruption is reported by verify, not treated as fatal here. + */ +static struct diff_hunks_store *load_store_at( + const struct git_hash_algo *repo_algo, const char *fname) +{ + struct diff_hunks_store *s; + struct chunkfile *cf; + int fd; + struct stat st; + void *data; + const unsigned char *p; + uint8_t num_chunks; + size_t index_size, entry_size, data_len; + + fd = git_open(fname); + if (fd < 0) + return NULL; + if (fstat(fd, &st) || st.st_size < DIFF_HUNKS_HEADER_SIZE) { + close(fd); + return NULL; + } + data_len = xsize_t(st.st_size); + data = xmmap(NULL, data_len, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); + p = data; + + num_chunks = p[6]; + + /* + * Reject a file that is not a readable store: wrong signature, + * version, or object hash, or too small to hold the table of + * contents that read_table_of_contents() walks (it dereferences + * each entry before range-checking its offset). + */ + if (get_be32(p) != DIFF_HUNKS_SIGNATURE || + p[4] != DIFF_HUNKS_VERSION || + p[5] != oid_version(repo_algo) || + data_len < store_min_size(repo_algo, num_chunks)) { + munmap(data, data_len); + return NULL; + } + + /* + * The trailing checksum is not verified here: the writer fsyncs + * and commits atomically, so a committed file is intact, and + * every record is bounds-checked at read (see precomputed_entry_at). + * The checksum is checked separately, by diff_hunks_verify(). + */ + + CALLOC_ARRAY(s, 1); + s->data = data; + s->data_len = data_len; + s->hash_algo = repo_algo; + + cf = init_chunkfile(NULL); + if (read_table_of_contents_quiet(cf, p, data_len, + DIFF_HUNKS_HEADER_SIZE, num_chunks, 1, + repo_algo) || + pair_chunk(cf, DIFF_HUNKS_CHUNKID_INDEX, &s->index, &index_size) || + pair_chunk(cf, DIFF_HUNKS_CHUNKID_DATA, &s->hdat, &s->hdat_size)) { + free_chunkfile(cf); + goto corrupt; + } + free_chunkfile(cf); + + entry_size = store_index_entry_size(s->hash_algo); + if (index_size % entry_size) + goto corrupt; + s->num_entries = index_size / entry_size; + return s; + +corrupt: + free_store(s); + return NULL; +} + +static struct diff_hunks_store *diff_hunks_store_load(struct repository *r) +{ + struct diff_hunks_store *s; + char *fname; + + prepare_repo_settings(r); + if (!r->settings.core_diff_hunks) + return NULL; + + fname = diff_hunks_store_path(r); + s = load_store_at(r->hash_algo, fname); + free(fname); + return s; +} + +struct diff_hunks_store *repo_diff_hunks_store(struct repository *r) +{ + if (!r->objects) + return NULL; + if (r->objects->diff_hunks_store_attempted) + return r->objects->diff_hunks_store; + r->objects->diff_hunks_store_attempted = 1; + r->objects->diff_hunks_store = diff_hunks_store_load(r); + return r->objects->diff_hunks_store; +} + +void diff_hunks_read_stats(struct repository *r, + unsigned long *hits, unsigned long *misses) +{ + struct diff_hunks_store *s = repo_diff_hunks_store(r); + + *hits = s ? s->read_hits : 0; + *misses = s ? s->read_misses : 0; +} + +void close_diff_hunks_store(struct object_database *o) +{ + if (!o->diff_hunks_store) + return; + free_store(o->diff_hunks_store); + o->diff_hunks_store = NULL; +} + +/* + * Fill *out with the hunk record at offset in the data chunk, and return + * 1 if the record is in bounds, 0 otherwise. The read path does not + * re-verify the checksum, and a valid checksum would not bound the count + * anyway, so a read must call this and use *out only when it returns + * non-zero. + * + * A record is a be32 hunk count followed by that many DIFF_HUNKS_HUNK_SIZE + * hunks. "remaining" tracks the bytes from offset to the end of the data + * chunk: it must hold the count, and after the count is consumed it must + * hold every hunk. The bounds are written as subtraction and division + * (never addition or multiplication) so a crafted offset or count cannot + * overflow them. + */ +static int precomputed_entry_at(const struct diff_hunks_store *s, + uint32_t offset, struct precomputed_entry *out) +{ + size_t remaining; + uint32_t num_hunks; + + if (offset >= s->hdat_size) + return 0; + remaining = s->hdat_size - offset; + if (remaining < sizeof(uint32_t)) + return 0; + + num_hunks = get_be32(s->hdat + offset); + remaining -= sizeof(uint32_t); + if (num_hunks > remaining / DIFF_HUNKS_HUNK_SIZE) + return 0; + + out->num_hunks = num_hunks; + out->hunk_data = s->hdat + offset + sizeof(uint32_t); + return 1; +} + +struct lookup_key { + const struct object_id *old_oid; + const struct object_id *new_oid; + int xdl_opts; + unsigned int rawsz; +}; + +/* + * The store's total order over (old_oid, new_oid, xdl_opts), defined + * once so the write-side sort (writer_entry_cmp) and the read-side + * search (store_bsearch_cmp) order the keys identically. + */ +static int cmp_store_index_key(const unsigned char *old_a, const unsigned char *new_a, + uint32_t opts_a, + const unsigned char *old_b, const unsigned char *new_b, + uint32_t opts_b, unsigned int rawsz) +{ + int cmp = memcmp(old_a, old_b, rawsz); + if (!cmp) + cmp = memcmp(new_a, new_b, rawsz); + if (!cmp) + cmp = (opts_a > opts_b) - (opts_a < opts_b); + return cmp; +} + +static int store_bsearch_cmp(const void *key, const void *entry_ptr) +{ + const struct lookup_key *k = key; + const unsigned char *old_hash, *new_hash; + uint32_t xdl_opts; + + decode_store_index_key(entry_ptr, k->rawsz, &old_hash, &new_hash, + &xdl_opts); + return cmp_store_index_key(k->old_oid->hash, k->new_oid->hash, + (uint32_t)k->xdl_opts, + old_hash, new_hash, xdl_opts, k->rawsz); +} + +static int store_get_one(struct diff_hunks_store *s, const struct lookup_key *key, + struct precomputed_entry *out) +{ + size_t entry_size = store_index_entry_size(s->hash_algo); + const unsigned char *found; + + found = bsearch(key, s->index, s->num_entries, entry_size, + store_bsearch_cmp); + if (!found) + return 0; + return precomputed_entry_at(s, + index_entry_hdat_offset(found, store_index_key_size(s->hash_algo)), + out); +} + +static int diff_hunks_store_get(struct diff_hunks_store *s, + const struct object_id *old_oid, + const struct object_id *new_oid, + int xdl_opts, + struct precomputed_entry *out) +{ + struct lookup_key key; + + if (!s) + return 0; + /* The null OID names no blob and cannot key an entry. */ + if (is_null_oid(old_oid) || is_null_oid(new_oid)) + return 0; + + key.old_oid = old_oid; + key.new_oid = new_oid; + key.xdl_opts = xdl_opts; + key.rawsz = s->hash_algo->rawsz; + + return store_get_one(s, &key, out); +} + +/* + * A recorded hunk sequence must satisfy the provider interface's + * shared check (diff_provider_check_hunk()) before it may be replayed: + * coordinates decode from be32 into long, which is 32-bit on some + * platforms, so a crafted value can decode negative or out of order. + * An entry that fails reads as a miss, so the caller recomputes. + */ +static int replayable_hunks(const struct precomputed_entry *e) +{ + struct diff_provider_hunks_check c = { 0 }; + uint32_t i; + + /* + * Replaying a record with no hunks would assert the blob pair + * equivalent, a claim the store must never make (the writer + * refuses to record one), so such a record is invalid. + */ + if (!e->num_hunks) + return 0; + for (i = 0; i < e->num_hunks; i++) { + struct precomputed_hunk h; + nth_precomputed_hunk(e, i, &h); + if (diff_provider_check_hunk(&c, h.old_start, h.old_count, + h.new_start, h.new_count)) + return 0; + } + return 1; +} + +int diff_hunks_replay(struct diff_hunks_store *s, + const struct object_id *old_oid, + const struct object_id *new_oid, + int xdl_opts, + xdl_emit_hunk_consume_func_t hunk_func, void *cb_data) +{ + struct precomputed_entry e; + uint32_t i; + + if (!s) + return 0; + if (!diff_hunks_store_get(s, old_oid, new_oid, xdl_opts, &e) || + !replayable_hunks(&e)) { + s->read_misses++; + return 0; + } + for (i = 0; i < e.num_hunks; i++) { + struct precomputed_hunk h; + nth_precomputed_hunk(&e, i, &h); + hunk_func(h.old_start, h.old_count, + h.new_start, h.new_count, cb_data); + } + s->read_hits++; + return 1; +} + +/* + * The store's consult implementation. The store is not + * authoritative, so it serves a recorded pair or passes; what the + * recording key cannot express, it excludes here with the + * stop-no-record disposition. None of those legs reaches + * diff_hunks_replay(), so none of them counts as a miss. + */ +static enum diff_provider_disposition +diff_hunks_store_consult(struct diff_provider *provider UNUSED, + const struct diff_provider_request *req, + diff_provider_fill_fn fill UNUSED, + void *fill_data UNUSED, + xdl_emit_hunk_consume_func_t hunk_cb, void *cb_data) +{ + /* + * xpparam_t is the consult's parameter input. Its flags are + * the store key's xdl_opts; ignore_regex (-I) and anchors + * (--anchored) shape the diff outside the key, so such a + * request is neither served nor recorded. + * + * Adding an xpparam_t field fires this assert (its size no + * longer matches the reference struct). To clear it: (1) add + * the field to the reference struct below; then (2) decide how + * it affects the key: make it part of the key, or exclude + * diffs that use it here with the disposition below. The + * assert only tracks size: a same-size reorder or a changed + * field meaning slips past, so re-read the fields when it + * fires. + */ + (void)BUILD_ASSERT_OR_ZERO(sizeof(xpparam_t) == sizeof(struct { + unsigned long flags; + regex_t **ignore_regex; + size_t ignore_regex_nr; + char **anchors; + size_t anchors_nr; + })); + if (req->xpp->ignore_regex_nr || req->xpp->anchors_nr) + return DIFF_PROVIDER_DISP_STOP_NO_RECORD; + /* + * Break detection (-B) rescores the pair outside xpparam_t, so + * it is outside the key for the same reason. + */ + if (req->diffopt && req->diffopt->break_opt != -1) + return DIFF_PROVIDER_DISP_STOP_NO_RECORD; + + if (!req->old_oid || !req->new_oid) + return DIFF_PROVIDER_DISP_PASS; + if (diff_hunks_replay(repo_diff_hunks_store(req->repo), + req->old_oid, req->new_oid, + req->xpp->flags, hunk_cb, cb_data)) + return DIFF_PROVIDER_DISP_ANSWERED; + return DIFF_PROVIDER_DISP_PASS; +} + +/* + * The provider borrows the repository's store through + * repo_diff_hunks_store() per request; the object database owns the + * file and tears it down, so there is nothing to release here. + */ +struct diff_provider *diff_hunks_store_provider_new(void) +{ + struct diff_provider *p = xcalloc(1, sizeof(*p)); + + p->consult = diff_hunks_store_consult; + return p; +} + +/* Validate one store file. Returns 0 if valid or absent, -1 on any error. */ +static int verify_store_at(struct repository *r, const char *fname) +{ + struct diff_hunks_store *s; + size_t entry_size; + uint32_t i; + int fd; + int ret = 0; + + /* + * A file that cannot be opened is not evidence of corruption: + * report the open error, and reserve the corruption diagnostics + * below for a file that was read and failed to parse. + */ + fd = git_open(fname); + if (fd < 0) { + if (errno == ENOENT) + return 0; /* absent is valid */ + return error_errno(_("unable to open diff-hunks store %s"), + fname); + } + close(fd); + s = load_store_at(r->hash_algo, fname); + if (!s) + return error(_("diff-hunks store failed to load (corrupt " + "header or hash mismatch): %s"), fname); + if (!hashfile_checksum_valid(r->hash_algo, s->data, s->data_len)) { + error(_("diff-hunks store has incorrect checksum and is " + "likely corrupt: %s"), fname); + free_store(s); + return -1; + } + + entry_size = store_index_entry_size(s->hash_algo); + for (i = 0; i < s->num_entries; i++) { + const unsigned char *ep = s->index + st_mult(entry_size, i); + size_t keysz = store_index_key_size(s->hash_algo); + uint32_t offset = index_entry_hdat_offset(ep, keysz); + struct precomputed_entry pe; + + /* + * Keyed by (old_oid, new_oid, xdl_opts), increasing. memcmp + * matches cmp_store_index_key's integer comparison of + * xdl_opts because it is non-negative, so its big-endian + * bytes order the same as its value. + */ + if (i > 0 && memcmp(ep - entry_size, ep, keysz) >= 0) { + error(_("diff-hunks entry %u not in sorted order"), i); + ret = -1; + } + if (!precomputed_entry_at(s, offset, &pe)) { + error(_("diff-hunks entry %u has out-of-bounds hunk " + "data"), i); + ret = -1; + } else if (!replayable_hunks(&pe)) { + error(_("diff-hunks entry %u holds an invalid hunk " + "sequence"), i); + ret = -1; + } + } + + free_store(s); + return ret; +} + +int diff_hunks_verify(struct repository *r) +{ + char *fname = diff_hunks_store_path(r); + int ret = 0; + + if (verify_store_at(r, fname)) + ret = -1; + free(fname); + return ret; +} + +int diff_hunks_clear(struct repository *r) +{ + char *fname = diff_hunks_store_path(r); + int ret = 0; + + if (unlink(fname) && errno != ENOENT) + ret = error_errno(_("unable to remove %s"), fname); + free(fname); + return ret; +} + +struct writer_entry { + struct object_id old_oid; + struct object_id new_oid; + int xdl_opts; + uint32_t hdat_offset; +}; + +struct diff_hunks_writer { + struct repository *r; + struct writer_entry *entries; + size_t nr, alloc; + size_t seed_nr; /* nr after seeding; finish skips a no-op flush */ + unsigned force_flush : 1; /* seed pruned: rewrite even a no-op warm */ + struct strbuf hdat; + struct hashmap dedup; /* hunk block content -> offset in hdat */ +}; + +/* A record of one distinct hunk block already present in hdat. */ +struct dedup_entry { + struct hashmap_entry ent; + uint32_t offset; + uint32_t len; +}; + +static int dedup_cmp(const void *cmp_data, + const struct hashmap_entry *a, + const struct hashmap_entry *b, + const void *keydata UNUSED) +{ + const struct diff_hunks_writer *writer = cmp_data; + const struct dedup_entry *ea = container_of(a, const struct dedup_entry, ent); + const struct dedup_entry *eb = container_of(b, const struct dedup_entry, ent); + + if (ea->len != eb->len) + return 1; + return memcmp(writer->hdat.buf + ea->offset, + writer->hdat.buf + eb->offset, ea->len); +} + +static struct diff_hunks_writer *diff_hunks_writer_new(struct repository *r) +{ + struct diff_hunks_writer *w; + + CALLOC_ARRAY(w, 1); + w->r = r; + strbuf_init(&w->hdat, 0); + hashmap_init(&w->dedup, dedup_cmp, w, 0); + return w; +} + +static void strbuf_put_be32(struct strbuf *sb, uint32_t val) +{ + unsigned char buf[4]; + put_be32(buf, val); + strbuf_add(sb, buf, 4); +} + +/* + * The hunk block just appended at `start` is deduplicated: if an + * identical block is already in hdat, this copy is dropped and the + * earlier offset returned; otherwise it is kept and remembered. + * Distinct keys that diff to the same hunks then share one block. + */ +static uint32_t intern_block(struct diff_hunks_writer *w, size_t start) +{ + size_t len = w->hdat.len - start; + struct dedup_entry key, *found, *added; + + hashmap_entry_init(&key.ent, memhash(w->hdat.buf + start, len)); + key.offset = (uint32_t)start; + key.len = (uint32_t)len; + + found = hashmap_get_entry(&w->dedup, &key, ent, NULL); + if (found) { + strbuf_setlen(&w->hdat, start); + return found->offset; + } + + added = xmalloc(sizeof(*added)); + hashmap_entry_init(&added->ent, key.ent.hash); + added->offset = key.offset; + added->len = key.len; + hashmap_add(&w->dedup, &added->ent); + return key.offset; +} + +int diff_hunks_writer_add(struct diff_hunks_writer *w, + const struct object_id *old_oid, + const struct object_id *new_oid, + int xdl_opts, + const struct precomputed_hunk *hunks, + size_t nr_hunks) +{ + struct writer_entry *e; + size_t i, block_start; + + if (!w) + return 0; + /* + * The block appended for this entry is sizeof(uint32_t) + + * nr_hunks * DIFF_HUNKS_HUNK_SIZE bytes. Bound nr_hunks so that + * length fits the uint32_t the dedup index records (and so the + * count itself fits the uint32_t written to the store). + */ + if (!nr_hunks || + nr_hunks > (UINT32_MAX - sizeof(uint32_t)) / DIFF_HUNKS_HUNK_SIZE || + is_null_oid(old_oid) || is_null_oid(new_oid)) + return 0; + if (w->hdat.len > UINT32_MAX) + return 0; + /* + * Coordinates are stored as 32-bit values; a result that cannot + * round-trip is dropped rather than silently truncated. + */ + for (i = 0; i < nr_hunks; i++) + if ((uintmax_t)hunks[i].old_start > (uintmax_t)INT32_MAX || + (uintmax_t)hunks[i].old_count > (uintmax_t)INT32_MAX || + (uintmax_t)hunks[i].new_start > (uintmax_t)INT32_MAX || + (uintmax_t)hunks[i].new_count > (uintmax_t)INT32_MAX) + return 0; + + ALLOC_GROW(w->entries, w->nr + 1, w->alloc); + e = &w->entries[w->nr++]; + oidcpy(&e->old_oid, old_oid); + oidcpy(&e->new_oid, new_oid); + e->xdl_opts = xdl_opts; + + block_start = w->hdat.len; + strbuf_put_be32(&w->hdat, (uint32_t)nr_hunks); + for (i = 0; i < nr_hunks; i++) { + strbuf_put_be32(&w->hdat, hunks[i].old_start); + strbuf_put_be32(&w->hdat, hunks[i].old_count); + strbuf_put_be32(&w->hdat, hunks[i].new_start); + strbuf_put_be32(&w->hdat, hunks[i].new_count); + } + e->hdat_offset = intern_block(w, block_start); + return 1; +} + +void diff_hunks_writer_record_stable(struct diff_hunks_writer *w, + const struct object_id *old_oid, + const struct object_id *new_oid, + int xdl_opts, + const struct precomputed_hunk *trimmed, + size_t nr_trimmed, + const struct precomputed_hunk *full, + size_t nr_full) +{ + size_t i; + + if (!w) + return; + /* + * Record only a trim-stable pair, one whose trimmed and + * untrimmed diffs are identical, so the single entry answers + * any consumer at any context (see the top of this file). A + * pair where the two diffs differ is never recorded and every + * consumer computes it. + */ + if (nr_trimmed != nr_full) + return; + for (i = 0; i < nr_trimmed; i++) + if (trimmed[i].old_start != full[i].old_start || + trimmed[i].old_count != full[i].old_count || + trimmed[i].new_start != full[i].new_start || + trimmed[i].new_count != full[i].new_count) + return; + diff_hunks_writer_add(w, old_oid, new_oid, xdl_opts, + trimmed, nr_trimmed); +} + +/* + * Seed the writer with fname's entries so a rewrite preserves them, + * setting *pruned when the rewrite will not carry the whole file + * forward: the file failed its checksum and was discarded outright, or + * individual entries were dropped because they failed the replayable + * check or the writer refused them (a key naming no blob). A + * rewrite re-checksums, so corruption must not be carried forward: + * that would launder it into a checksum-valid file that verify can no + * longer catch. This path already reads the whole file, so verify the + * checksum here (the reader keeps trusting committed files, without + * re-checksumming); an invalid + * entry reads as a miss anyway, so dropping it heals the store rather + * than losing anything a reader could use. + */ +static void diff_hunks_writer_seed(struct diff_hunks_writer *w, + const char *fname, int *pruned) +{ + struct diff_hunks_store *s = load_store_at(w->r->hash_algo, fname); + unsigned int rawsz; + size_t entry_size, keysz; + struct precomputed_hunk *hunks = NULL; + size_t hunks_alloc = 0; + uint32_t i, dropped = 0; + + if (!s) + return; + if (!hashfile_checksum_valid(w->r->hash_algo, s->data, s->data_len)) { + warning(_("diff-hunks store %s failed its checksum; " + "discarding it"), fname); + free_store(s); + *pruned = 1; + return; + } + rawsz = s->hash_algo->rawsz; + entry_size = store_index_entry_size(s->hash_algo); + keysz = store_index_key_size(s->hash_algo); + + for (i = 0; i < s->num_entries; i++) { + const unsigned char *ep = s->index + st_mult(entry_size, i); + const unsigned char *old_hash, *new_hash; + struct object_id old_oid, new_oid; + uint32_t xdl_opts, j; + struct precomputed_entry pe; + + decode_store_index_key(ep, rawsz, &old_hash, &new_hash, + &xdl_opts); + oidread(&old_oid, old_hash, s->hash_algo); + oidread(&new_oid, new_hash, s->hash_algo); + if (!precomputed_entry_at(s, index_entry_hdat_offset(ep, keysz), &pe) || + !replayable_hunks(&pe)) { + dropped++; + continue; + } + ALLOC_GROW(hunks, pe.num_hunks, hunks_alloc); + for (j = 0; j < pe.num_hunks; j++) + nth_precomputed_hunk(&pe, j, &hunks[j]); + if (!diff_hunks_writer_add(w, &old_oid, &new_oid, + (int)xdl_opts, hunks, pe.num_hunks)) + dropped++; + } + if (dropped) { + warning(Q_("diff-hunks store %s: dropping %u invalid entry", + "diff-hunks store %s: dropping %u invalid entries", + dropped), fname, dropped); + *pruned = 1; + } + free(hunks); + free_store(s); +} + +/* + * Writing is off by default. It is enabled per invocation by the + * GIT_DIFF_HUNKS_WRITE environment variable, or persistently by the + * diffHunks.write config, with the environment variable winning when + * set. Only a warming run (a diff or log the repository owner chooses + * to run with writing on) enables it, so ordinary reads never mutate + * the store. + */ +static int diff_hunks_write_enabled(struct repository *r) +{ + const char *env = getenv("GIT_DIFF_HUNKS_WRITE"); + int val; + + if (env) { + /* + * This is a warming opt-in, so an unparseable value must not + * abort an ordinary read command: treat it as disabled. + */ + val = git_parse_maybe_bool(env); + return val < 0 ? 0 : val; + } + if (!repo_config_get_bool(r, "diffhunks.write", &val)) + return val; + return 0; +} + +struct diff_hunks_writer *diff_hunks_writer_maybe_new(struct repository *r) +{ + struct diff_hunks_writer *w; + char *fname; + int pruned; + + if (!diff_hunks_write_enabled(r)) + return NULL; + /* + * Seed from the existing store so a flush merges with it rather + * than replacing it: a later warm adds newly computed pairs + * without discarding what earlier warms recorded. + */ + w = diff_hunks_writer_new(r); + fname = diff_hunks_store_path(r); + pruned = 0; + diff_hunks_writer_seed(w, fname, &pruned); + free(fname); + w->seed_nr = w->nr; + /* + * A pruning seed means the file on disk holds material the + * rewrite must not preserve; flush even if this warm computes + * nothing new, so the store on disk is repaired rather than + * left serving what the seed refused. + */ + w->force_flush = !!pruned; + return w; +} + +static int writer_entry_cmp(const void *va, const void *vb, void *ctx) +{ + const struct writer_entry *a = va, *b = vb; + unsigned int rawsz = *(const unsigned int *)ctx; + return cmp_store_index_key(a->old_oid.hash, a->new_oid.hash, + (uint32_t)a->xdl_opts, + b->old_oid.hash, b->new_oid.hash, + (uint32_t)b->xdl_opts, + rawsz); +} + +struct write_ctx { + struct diff_hunks_writer *w; + unsigned int rawsz; +}; + +static int write_index_chunk(struct hashfile *f, void *data) +{ + struct write_ctx *ctx = data; + size_t i; + + for (i = 0; i < ctx->w->nr; i++) { + hashwrite(f, ctx->w->entries[i].old_oid.hash, ctx->rawsz); + hashwrite(f, ctx->w->entries[i].new_oid.hash, ctx->rawsz); + hashwrite_be32(f, ctx->w->entries[i].xdl_opts); + hashwrite_be32(f, ctx->w->entries[i].hdat_offset); + } + return 0; +} + +static int write_data_chunk(struct hashfile *f, void *data) +{ + struct write_ctx *ctx = data; + hashwrite(f, ctx->w->hdat.buf, ctx->w->hdat.len); + return 0; +} + +/* Sort, dedup, and write the accumulated entries to the file at fname. */ +static int diff_hunks_writer_flush(struct diff_hunks_writer *w, char *fname) +{ + struct lock_file lk = LOCK_INIT; + struct hashfile *f; + struct chunkfile *cf; + unsigned int rawsz = w->r->hash_algo->rawsz; + struct write_ctx ctx = { w, rawsz }; + size_t entry_size; + + QSORT_S(w->entries, w->nr, writer_entry_cmp, &rawsz); + + /* + * The same blob pair recurs across history (reverts, cherry- + * picks); identical keys carry identical hunks, so keep one of + * each. The index must stay duplicate-free for binary search. + */ + if (w->nr > 1) { + size_t kept = 1, i; + for (i = 1; i < w->nr; i++) + if (writer_entry_cmp(&w->entries[kept - 1], + &w->entries[i], &rawsz)) + w->entries[kept++] = w->entries[i]; + w->nr = kept; + } + + if (safe_create_leading_directories(w->r, fname)) { + error(_("unable to create directory for %s"), fname); + return -1; + } + if (hold_lock_file_for_update(&lk, fname, 0) < 0) { + error_errno(_("unable to lock %s"), fname); + return -1; + } + adjust_shared_perm(w->r, get_lock_file_path(&lk)); + f = hashfd(w->r->hash_algo, get_lock_file_fd(&lk), + get_lock_file_path(&lk)); + + entry_size = store_index_entry_size(w->r->hash_algo); + cf = init_chunkfile(f); + add_chunk(cf, DIFF_HUNKS_CHUNKID_INDEX, w->nr * entry_size, + write_index_chunk); + add_chunk(cf, DIFF_HUNKS_CHUNKID_DATA, w->hdat.len, write_data_chunk); + + hashwrite_be32(f, DIFF_HUNKS_SIGNATURE); + hashwrite_u8(f, DIFF_HUNKS_VERSION); + hashwrite_u8(f, oid_version(w->r->hash_algo)); + hashwrite_u8(f, get_num_chunks(cf)); + hashwrite_u8(f, 0); /* reserved */ + + write_chunkfile(cf, &ctx); + free_chunkfile(cf); + + /* + * fsync per the user's configuration (like commit-graph and the + * multi-pack-index), then commit atomically. Readers trust the + * committed file rather than re-checksumming it; diff_hunks_verify() + * checks the checksum separately. + */ + finalize_hashfile(f, NULL, FSYNC_COMPONENT_DIFF_HUNKS, + CSUM_HASH_IN_STREAM | CSUM_FSYNC); + /* + * This same process may hold the current store mmapped (a warm + * that also reads); the commit below renames over it, which must + * never land on a live mapping (Windows refuses it). Close the + * store and clear the load-attempted flag first, so the next + * read loads the committed file. + */ + if (w->r->objects) { + close_diff_hunks_store(w->r->objects); + w->r->objects->diff_hunks_store_attempted = 0; + } + if (commit_lock_file(&lk)) { + error_errno(_("unable to write %s"), fname); + return -1; + } + return 0; +} + +static void diff_hunks_writer_free(struct diff_hunks_writer *w) +{ + if (!w) + return; + hashmap_clear_and_free(&w->dedup, struct dedup_entry, ent); + free(w->entries); + strbuf_release(&w->hdat); + free(w); +} + +void diff_hunks_writer_finish(struct diff_hunks_writer *w) +{ + if (!w) + return; + /* Skip the flush when the warm recorded nothing beyond its seed. */ + if (w->nr != w->seed_nr || w->force_flush) { + char *fname = diff_hunks_store_path(w->r); + diff_hunks_writer_flush(w, fname); + free(fname); + } + diff_hunks_writer_free(w); +} diff --git a/diff-hunks.h b/diff-hunks.h new file mode 100644 index 00000000000000..c58500d05c0de1 --- /dev/null +++ b/diff-hunks.h @@ -0,0 +1,141 @@ +#ifndef DIFF_HUNKS_H +#define DIFF_HUNKS_H + +#include "hash.h" +#include "xdiff-interface.h" /* xdl_emit_hunk_consume_func_t */ + +struct object_id; +struct repository; +struct object_database; + +/* + * A persistent store of precomputed diff hunk coordinates, at + * .git/objects/info/diff-hunks. Entries are keyed by the two blobs diffed + * and the xdl_opts they were diffed under, so a cached result is valid + * in any context that key recurs in, independent of path. The xdl_opts + * key component mirrors the (always non-negative) diff_options field it + * projects from, and is serialized and compared as a 4-byte big-endian + * integer. + * + * The hunks a pair produces are not unique. They vary with the xdiff + * algorithm and ignore flags (xdl_opts, part of the key), and with + * whether the diff was trimmed: a zero-context diff runs + * trim_common_tail, which can pick a different but equally valid set of + * hunks than an untrimmed diff. The store holds one entry per key, so a + * pair is recorded only when its trimmed and untrimmed diffs are + * identical (the recording caller checks); such an entry serves a + * consumer at any context. The rare pair where the two diffs differ is + * never recorded and is always computed. + * + * The store is a cache: ordinary commands read it and fall back to + * computing the diff when it is absent, stale, or corrupt. It is filled + * as a side effect of diff and log runs, but only when writing is + * enabled (such a write-enabled run is a warming run); writing is off + * by default, so an ordinary command reads the store without recording + * into it. + */ + +/* + * A hunk's coordinates. The type is long to match the xdiff emit + * callback; the values are a diff's line numbers and counts, always + * within the int32 range the on-disk format stores (see + * diff_hunks_writer_add()). + */ +struct precomputed_hunk { + long old_start; + long old_count; + long new_start; + long new_count; +}; + +/* + * The repository's store, loaded once on first use and cached on the + * object database. Returns NULL when reading is disabled + * (core.diffHunks=false), the store is absent, or it fails to parse + * (wrong signature, version, or object hash, or a corrupt structure). + * The lookup functions below accept a NULL store and treat it as + * empty (every lookup misses), so callers need not check for NULL. + * The object database owns the store; callers must not free it. + */ +struct diff_hunks_store *repo_diff_hunks_store(struct repository *r); + +/* Free the repository's cached store, at object-database teardown. */ +void close_diff_hunks_store(struct object_database *o); + +/* + * Consultation counters for the repository's store: pairs the store + * served (hits) and pairs it was consulted for but could not serve + * (misses). Both zero when reading is disabled or no store exists. + */ +void diff_hunks_read_stats(struct repository *r, + unsigned long *hits, unsigned long *misses); + +/* + * Replay the recorded hunks of an (old blob, new blob) pair diffed + * under xdl_opts through hunk_func. The sequence is validated before + * any callback runs: on a hit (return 1) every hunk is emitted, on a + * miss (return 0: absent pair, xdl_opts mismatch, or an entry that + * fails validation) nothing is emitted, so a caller may accumulate + * directly into its result. + */ +int diff_hunks_replay(struct diff_hunks_store *s, + const struct object_id *old_oid, + const struct object_id *new_oid, + int xdl_opts, + xdl_emit_hunk_consume_func_t hunk_func, void *cb_data); + +/* + * A warming run's writer: it accumulates the hunks it computes in memory + * and flushes them to the store in one pass at finish. + */ +struct diff_hunks_writer; + +/* + * Return a writer for a warming run, or NULL when writing is disabled + * (the default). diff_hunks_writer_add() tolerates a NULL writer, so a + * caller may attach the result unconditionally. Pair with + * diff_hunks_writer_finish(). + */ +struct diff_hunks_writer *diff_hunks_writer_maybe_new(struct repository *r); + +/* + * Record a blob pair's hunks as computed under xdl_opts; a later lookup + * with a matching key is served these hunks. The caller must have + * checked that the pair's trimmed and untrimmed diffs are identical + * (see the top of this file), so the entry answers at any context; + * diff_hunks_writer_record_stable() below performs that check. + * NULL-safe. Returns 1 when the entry was recorded, 0 when the writer + * refused it (no hunks, a null object id, or values the on-disk + * 32-bit fields cannot hold). + */ +int diff_hunks_writer_add(struct diff_hunks_writer *w, + const struct object_id *old_oid, + const struct object_id *new_oid, + int xdl_opts, + const struct precomputed_hunk *hunks, + size_t nr_hunks); + +/* + * Record the pair only if it is trim-stable: the recording caller + * hands over both the trimmed (xdi_diff) and untrimmed (xdl_diff) + * zero-context hunk sequences it computed, and the entry is added + * only when the two are identical. NULL-safe. + */ +void diff_hunks_writer_record_stable(struct diff_hunks_writer *w, + const struct object_id *old_oid, + const struct object_id *new_oid, + int xdl_opts, + const struct precomputed_hunk *trimmed, + size_t nr_trimmed, + const struct precomputed_hunk *full, + size_t nr_full); + +/* Flush the accumulated entries to the store and free the writer. NULL-safe. */ +void diff_hunks_writer_finish(struct diff_hunks_writer *w); + +/* Remove the store file. Returns 0 (incl. absent) or -1. */ +int diff_hunks_clear(struct repository *r); +/* Validate the store. Returns 0 if valid/absent, -1 if corrupt. */ +int diff_hunks_verify(struct repository *r); + +#endif /* DIFF_HUNKS_H */ diff --git a/diff-process.c b/diff-process.c new file mode 100644 index 00000000000000..121903a6c80256 --- /dev/null +++ b/diff-process.c @@ -0,0 +1,669 @@ +/* + * The process provider of the hunk provider interface: consult a + * long-running external process via the pkt-line protocol for the + * hunks of a blob pair. The process answers from the pair's object + * names alone: it can serve a persistent cache keyed on the pair, or + * fetch the blobs from the repository itself (e.g. via "git cat-file + * --batch") and compute its own notion of which lines changed. The + * provider sits at the head of its repository's chain and gates + * itself per request; its state is the repository's pool of running + * processes, one per configured command, stopped when the provider + * is released. + * + * Protocol: pkt-line over stdin/stdout, following the pattern of + * the long-running filter process protocol (see convert.c). + * + * Handshake: + * git> git-diff-client / version=1 / flush + * process< git-diff-server / version=1 / flush + * git> capability=hunks-by-oid / flush + * process< capability=hunks-by-oid / flush + * + * Per-pair, when both sides are stored blobs: + * git> command=hunks-by-oid / pathname= + * git> old-oid= / new-oid= / flush + * process< hunk + * process< ... / flush + * process< status=success / flush + * + * No content is sent. Because Git holds no content for the exchange, + * the answer is used as the process sent it: the hunks are not re-run + * through xdiff's compaction, and a status=success response with zero + * hunks asserts that the blobs are equivalent, including their + * trailing newlines. A process that cannot answer from the object names + * (or cannot rule out a trailing-newline-only difference) responds + * status=need-content; the pair then gets the builtin answer, served + * from the diff-hunks store or computed. A later + * protocol extension can define a content-carrying request for such + * processes and for sides that are not stored blobs. + */ + +#include "git-compat-util.h" +#include "diff.h" +#include "diff-provider-internal.h" +#include "gettext.h" +#include "hex.h" +#include "odb.h" +#include "repository.h" +#include "sigchain.h" +#include "userdiff.h" +#include "sub-process.h" +#include "pkt-line.h" +#include "strbuf.h" + +#define CAP_OID_HUNKS (1u << 0) + +/* + * The provider's state: the repository's diff processes, keyed by + * their command string, so drivers that configure the same command + * share one process. An entry whose process failed stays in the + * pool with the failed bit set, so the command is not retried while + * the entry lives; the pool and its entries last until the provider + * is released. + */ +struct diff_process_state { + struct hashmap subprocesses; +}; + +struct diff_subprocess { + struct subprocess_entry subprocess; + /* + * Owns the string subprocess.cmd and the hashmap key borrow: the + * entry outlives the userdiff config a re-read may replace. + */ + char *cmd; + unsigned int supported_capabilities; + unsigned failed : 1; +}; + +static int start_diff_process_fn(struct subprocess_entry *subprocess) +{ + static int versions[] = { 1, 0 }; + static struct subprocess_capability capabilities[] = { + { "hunks-by-oid", CAP_OID_HUNKS }, + { NULL, 0 } + }; + struct diff_subprocess *entry = + container_of(subprocess, struct diff_subprocess, subprocess); + + return subprocess_handshake(subprocess, "git-diff", + versions, NULL, + capabilities, + &entry->supported_capabilities); +} + +/* + * The pool entry for a command, or NULL when its process fails to + * start here: the failure leaves a failed entry in the pool, so only + * the request that observed it maps it to an error and later + * requests pass the provider by. + */ +static struct diff_subprocess *get_or_launch_process( + struct diff_process_state *state, + struct userdiff_driver *drv) +{ + struct subprocess_entry *running; + struct diff_subprocess *entry; + + running = subprocess_find_entry(&state->subprocesses, drv->process); + if (running) { + entry = container_of(running, struct diff_subprocess, + subprocess); + return entry->failed ? NULL : entry; + } + + entry = xcalloc(1, sizeof(*entry)); + entry->cmd = xstrdup(drv->process); + if (subprocess_start_command(&entry->subprocess, entry->cmd, + start_diff_process_fn)) + entry->failed = 1; + hashmap_entry_init(&entry->subprocess.ent, strhash(entry->cmd)); + hashmap_add(&state->subprocesses, &entry->subprocess.ent); + if (entry->failed) { + warning(_("diff process '%s' failed to start;" + " using the builtin diff"), drv->process); + return NULL; + } + return entry; +} + +/* + * A hunk in the diff process's presentation coordinates: the line + * numbering it reports over the protocol. Kept distinct from struct + * xdl_hunk (xdiff's coordinates) so that only translated hunks ever + * reach a consumer; diff_process_hunk_to_xdl() is the single + * crossing point. + */ +struct diff_process_hunk { + long old_start, old_count; + long new_start, new_count; +}; + +/* + * Parse one non-negative decimal field of a hunk line into *out and + * advance *line past it. Fields must be plain decimal with no leading + * whitespace or sign (isdigit() takes an unsigned char to stay defined + * for high-bit bytes). The first three fields are followed by a single + * space; the last (is_last) is followed by end-of-string or a space. + * Trailing space-separated tokens after the last field are allowed and + * ignored, so a future protocol version can append fields (e.g. a + * "moved" marker) without an older Git rejecting the line, mirroring + * the request-side rule that processes ignore unknown keys. + * + * A value that overflows strtol() is not a parse failure: the line is + * well-formed, so the stream stays in protocol sync. It is reported + * through *out_of_range, and the caller skips the pair the same way + * it skips any other out-of-range coordinate. + */ +static int parse_hunk_field(const char **line, long *out, int is_last, + int *out_of_range) +{ + const char *p = *line; + char *end; + + if (!isdigit((unsigned char)*p)) + return -1; + errno = 0; + *out = strtol(p, &end, 10); + if (end == p) + return -1; + if (errno == ERANGE) + *out_of_range = 1; + else if (errno) + return -1; + if (is_last) { + if (*end != '\0' && *end != ' ') + return -1; + } else { + if (*end != ' ') + return -1; + end++; + } + *line = end; + return 0; +} + +static int parse_hunk_line(const char *line, + struct diff_process_hunk *presented, + int *out_of_range) +{ + *out_of_range = 0; + /* Format: "hunk " */ + if (!skip_prefix(line, "hunk ", &line)) + return -1; + if (parse_hunk_field(&line, &presented->old_start, 0, out_of_range) || + parse_hunk_field(&line, &presented->old_count, 0, out_of_range) || + parse_hunk_field(&line, &presented->new_start, 0, out_of_range) || + parse_hunk_field(&line, &presented->new_count, 1, out_of_range)) + return -1; + return 0; +} + +/* + * Translate a hunk from the diff process's presentation coordinates + * into xdiff's. + * + * Protocol starts are already 1-based positions (the line a change + * sits before), the same numbering xdiff uses, so the only adjustment + * is for an empty file side: "git diff" addresses it with a start of 0 + * and a count of 0 (e.g. "0 0 1 5" adds five lines to an empty old + * side), and since xdiff uses start-1 as an array index that 0 becomes + * 1 here. This is NOT the full inverse of xdl_emit_hunk_hdr() + * (xdiff/xutils.c): that emitter shifts a count-0 range to start-1 for + * the displayed "@@" header, but the protocol keeps the unshifted + * 1-based position for a mid-file insert or delete. This is the single + * point where presentation coordinates become xdiff coordinates, so + * any consumer of these coordinates may assume 1-based starts. + * + * Returns -1 for a start of 0 paired with a nonzero count, which names + * no line in either coordinate system. (parse_hunk_line() already + * guarantees non-negative starts and counts.) + */ +static int diff_process_hunk_to_xdl(const struct diff_process_hunk *presented, + struct xdl_hunk *xdl) +{ + long old_start = presented->old_start; + long new_start = presented->new_start; + + if ((!old_start && presented->old_count) || + (!new_start && presented->new_count)) + return -1; + if (!old_start) + old_start = 1; + if (!new_start) + new_start = 1; + + xdl->old_start = old_start; + xdl->old_count = presented->old_count; + xdl->new_start = new_start; + xdl->new_count = presented->new_count; + return 0; +} + +/* + * Validate the process's hunks (already in xdiff coordinates) before they + * bypass the diff algorithm. The content-independent rules (in-order, + * non-overlapping, lockstep-aligned, int32-bounded coordinates) are the + * provider interface's shared rule, diff_provider_check_hunk(); this + * function adds the two checks that need the blobs' line counts (a hunk + * past the end of a file, the run after the last hunk) and the + * per-rule diagnostics naming the process. On a bad response we warn + * and the caller falls back to the builtin diff. Returns 0 if valid, + * -1 (after warning) otherwise. + * + * old_lines/new_lines bound the line count of each side, or are + * negative when no bound is known. An oid-only answer arrives without + * content, so its caller passes upper bounds derived from the blobs' + * byte sizes, which caps coordinate magnitude but cannot support the + * run-after-the-last-hunk check: that one compares exact line counts, + * so it runs only when lines_exact is set, which no caller does today. + * It is kept for a content-carrying request, whose loaded buffers + * would provide exact counts. + */ +static int validate_external_hunks(const struct xdl_hunk *hunks, size_t nr, + long old_lines, long new_lines, + int lines_exact, + const char *process, const char *path) +{ + struct diff_provider_hunks_check c = { 0 }; + size_t i; + + for (i = 0; i < nr; i++) { + const struct xdl_hunk *h = &hunks[i]; + + if (old_lines >= 0 && + (h->old_count > old_lines - h->old_start + 1 || + h->new_count > new_lines - h->new_start + 1)) { + warning(_("diff process '%s' returned a hunk past the " + "end of '%s'; using the builtin diff"), + process, path); + return -1; + } + switch (diff_provider_check_hunk(&c, h->old_start, + h->old_count, h->new_start, + h->new_count)) { + case DIFF_PROVIDER_HUNKS_OK: + break; + case DIFF_PROVIDER_HUNKS_RANGE: + warning(_("diff process '%s' returned out-of-range " + "coordinates for '%s'; using the builtin diff"), + process, path); + return -1; + case DIFF_PROVIDER_HUNKS_OVERLAP: + warning(_("diff process '%s' returned overlapping hunks " + "for '%s'; using the builtin diff"), + process, path); + return -1; + case DIFF_PROVIDER_HUNKS_MISALIGNED: + warning(_("diff process '%s' returned hunks that leave " + "'%s' misaligned; using the builtin diff"), + process, path); + return -1; + } + } + if (lines_exact && + old_lines - c.prev_old_end != new_lines - c.prev_new_end) { + warning(_("diff process '%s' returned hunks that leave '%s' " + "misaligned; using the builtin diff"), + process, path); + return -1; + } + return 0; +} + +/* + * The most lines a blob can hold, from its size alone: every line, + * even an empty one, costs at least one byte, so a blob of N bytes + * holds at most N lines. Returns -1 when the size is unavailable, + * leaving the response bounded only by the shared int32 rule. A size + * beyond INT32_MAX clamps to it, which loses nothing: a coordinate + * that large fails the shared rule anyway. In a partial clone the + * size lookup must not fetch the blob from the promisor remote: + * validating an answer that exists to avoid loading content must not + * itself download that content, so a missing blob reads as size + * unavailable instead. + */ +static long blob_line_cap(struct repository *r, const struct object_id *oid) +{ + unsigned long size; + struct object_info oi = OBJECT_INFO_INIT; + + oi.sizep = &size; + if (odb_read_object_info_extended(r->objects, oid, &oi, + OBJECT_INFO_SKIP_FETCH_OBJECT) < 0) + return -1; + if (size > INT32_MAX) + return INT32_MAX; + return (long)size; +} + +/* + * The driver whose process a consultation for path would ask, or NULL + * when none applies (no driver, process not allowed, or xpp carries + * options the process is never told about). Needs no content, so + * the driver is picked before any blob is loaded. + */ +static struct userdiff_driver *diff_process_driver(struct diff_options *diffopt, + const char *path, + const xpparam_t *xpp) +{ + struct userdiff_driver *drv; + + if (!diffopt || !path) + return NULL; + if (!diffopt->flags.allow_diff_process || diffopt->ignore_driver_algorithm) + return NULL; + /* + * Whitespace-ignoring, regex-ignore (-I) and anchored options + * change which lines count as different, but the process is never + * told about them, so its hunks could not honor them. A forced + * diff algorithm (an option or configured algorithm setting) + * requests a specific builtin computation, which an + * authoritative answer would override. Rather than silently + * override the user's request, fall back to the builtin diff, + * which does honor these flags. Key this off xpp (the + * parameters this diff actually runs with) rather than diffopt, + * so a caller like blame, which keeps its algorithm and + * whitespace flags outside diffopt, is covered without a + * separate guard of its own. + */ + if ((xpp->flags & (XDF_WHITESPACE_FLAGS | XDF_IGNORE_BLANK_LINES | + XDF_DIFF_ALGORITHM_MASK)) || + xpp->ignore_regex_nr || xpp->anchors_nr) + return NULL; + + /* + * A path the protocol cannot carry never selects a process: an + * embedded newline would let the rest of the path forge further + * request keys, and the pathname must fit one packet. Passing + * here keeps the cost local to the path; a failed write would + * instead cost the whole command its process. + */ + if (strchr(path, '\n') || + strlen(path) > LARGE_PACKET_DATA_MAX - strlen("pathname=\n")) + return NULL; + + drv = userdiff_find_by_path(diffopt->repo->index, path); + if (!drv || !drv->process) + return NULL; + return drv; +} + +/* + * Without content there is no size-derived bound on a response, so cap + * accumulation at a constant instead. A response that exceeds the + * cap is a protocol error: the process is disabled for the rest of + * the command and the caller falls back to the builtin diff. + */ +#define OID_HUNKS_MAX (1 << 20) + +enum diff_process_result { + DIFF_PROCESS_ERROR = -1, /* failed; caller falls back to builtin */ + DIFF_PROCESS_OK = 0, /* the process supplied hunks */ + DIFF_PROCESS_SKIP, /* process did not apply: use builtin */ + DIFF_PROCESS_EQUIVALENT, /* process says files are equivalent */ +}; + +/* + * Ask drv's diff process to answer the request from the blob pair's + * object ids alone (the "hunks-by-oid" capability): no content is + * loaded or sent. On DIFF_PROCESS_OK the process's hunks are emitted + * through hunk_cb in 0-based emission coordinates, validated for order, + * overlap, and lockstep alignment first; because Git holds no content, + * the answer is used as the process sent it, without xdiff's compaction. + * DIFF_PROCESS_EQUIVALENT means the process asserts the pair equal. + * DIFF_PROCESS_SKIP covers everything that should fall through to the + * builtin computation: a missing capability, a missing object id, a + * status=need-content answer, or an invalid response. + */ +static enum diff_process_result diff_process_query_hunks( + struct diff_process_state *state, + struct userdiff_driver *drv, + const struct diff_provider_request *req, + xdl_emit_hunk_consume_func_t hunk_cb, + void *cb_data) +{ + const char *path = req->path; + struct diff_subprocess *entry; + struct child_process *process; + int fd_in, fd_out; + struct packet_reader reader; + struct strbuf status = STRBUF_INIT; + struct xdl_hunk *hunks = NULL; + struct diff_process_hunk presented; + struct xdl_hunk hunk; + size_t nr_hunks = 0, alloc_hunks = 0, i; + int bad_coords = 0; + long old_cap, new_cap; + enum diff_process_result res; + + if (!req->old_oid || !req->new_oid) + return DIFF_PROCESS_SKIP; + + entry = get_or_launch_process(state, drv); + if (!entry) + return DIFF_PROCESS_ERROR; + if (!(entry->supported_capabilities & CAP_OID_HUNKS)) + return DIFF_PROCESS_SKIP; + + process = subprocess_get_child_process(&entry->subprocess); + fd_in = process->in; + fd_out = process->out; + + sigchain_push(SIGPIPE, SIG_IGN); + + if (packet_write_fmt_gently(fd_in, "command=hunks-by-oid\n") || + packet_write_fmt_gently(fd_in, "pathname=%s\n", path) || + packet_write_fmt_gently(fd_in, "old-oid=%s\n", + oid_to_hex(req->old_oid)) || + packet_write_fmt_gently(fd_in, "new-oid=%s\n", + oid_to_hex(req->new_oid)) || + packet_flush_gently(fd_in)) + goto comm_error; + + packet_reader_init(&reader, fd_out, NULL, 0, + PACKET_READ_CHOMP_NEWLINE | + PACKET_READ_GENTLE_ON_EOF | + PACKET_READ_GENTLE_ON_READ_ERROR); + for (;;) { + enum packet_read_status rs = packet_reader_read(&reader); + int out_of_range; + + if (rs == PACKET_READ_FLUSH) + break; + /* + * Only a hunk line may precede the flush. EOF and a + * malformed frame end the session; an empty packet, which + * a length-only read cannot tell from a flush, would + * truncate the hunk section here and leave the status + * section to poison the next request, so it is a protocol + * error too. + */ + if (rs != PACKET_READ_NORMAL || !reader.pktlen) + goto comm_error; + if (parse_hunk_line(reader.line, &presented, + &out_of_range) < 0) + goto comm_error; + if (bad_coords) + continue; + if (out_of_range || + diff_process_hunk_to_xdl(&presented, &hunk) < 0) { + /* + * Semantically invalid coordinates in a well-formed + * response: the stream stays in protocol sync, so + * drain the rest and fall back for this file while + * keeping the process alive, the same treatment + * validate_external_hunks() failures receive. + */ + bad_coords = 1; + continue; + } + if (nr_hunks >= OID_HUNKS_MAX) { + warning(_("diff process '%s' sent too many hunks" + " for '%s'; disabling it for the" + " remainder of this command"), + drv->process, path); + goto disable; + } + ALLOC_GROW(hunks, nr_hunks + 1, alloc_hunks); + hunks[nr_hunks++] = hunk; + } + + if (subprocess_read_status_gently(fd_out, &status)) + goto comm_error; + + if (!strcmp(status.buf, "success")) { + if (bad_coords) { + warning(_("diff process '%s' returned out-of-range " + "coordinates for '%s'; using the builtin diff"), + drv->process, path); + res = DIFF_PROCESS_SKIP; + goto out; + } + if (!nr_hunks) { + res = DIFF_PROCESS_EQUIVALENT; + goto out; + } + /* + * Bound the coordinates by the blobs' sizes, read from the + * object database without loading content. Either both + * bounds hold or neither is applied: a partial bound would + * misclassify a response that the other side's size would + * have caught. + */ + old_cap = blob_line_cap(req->repo, req->old_oid); + new_cap = blob_line_cap(req->repo, req->new_oid); + if (old_cap < 0 || new_cap < 0) + old_cap = new_cap = -1; + if (validate_external_hunks(hunks, nr_hunks, old_cap, new_cap, + 0, drv->process, path) < 0) { + res = DIFF_PROCESS_SKIP; + goto out; + } + /* + * Replay in the coordinates a hunk consumer receives from + * xdiff's emission: 0-based starts. The answer is used as + * the process sent it; with no content in hand it cannot be + * re-run through xdiff's compaction. + */ + for (i = 0; i < nr_hunks; i++) + hunk_cb(hunks[i].old_start - 1, hunks[i].old_count, + hunks[i].new_start - 1, hunks[i].new_count, + cb_data); + res = DIFF_PROCESS_OK; + goto out; + } + if (!strcmp(status.buf, "need-content")) { + /* + * The process cannot answer this pair from its object names; + * the caller computes the diff itself. + */ + res = DIFF_PROCESS_SKIP; + goto out; + } + if (!strcmp(status.buf, "abort")) { + /* The process withdrew: stop asking it for this session. */ + entry->supported_capabilities &= ~CAP_OID_HUNKS; + res = DIFF_PROCESS_SKIP; + goto out; + } + /* + * An unrecognized status is a protocol error, not a per-pair + * failure: this Git did not request anything it does not know, + * so the process is answering some other protocol, and asking + * it again would warn on every pair of the traversal. + */ + warning(_("diff process '%s' sent unrecognized status '%s' for " + "'%s'; disabling it for the remainder of this command"), + drv->process, status.buf, path); + goto disable; +out: + free(hunks); + strbuf_release(&status); + sigchain_pop(SIGPIPE); + return res; + +comm_error: + warning(_("diff process '%s' failed for '%s'; disabling it" + " for the remainder of this command"), + drv->process, path); +disable: + subprocess_stop_command(&entry->subprocess); + entry->failed = 1; + free(hunks); + strbuf_release(&status); + sigchain_pop(SIGPIPE); + return DIFF_PROCESS_ERROR; +} + +/* + * The process outranks every later provider through its chain + * position: when it answers, the walk ends, so no later provider + * serves the pair, and an answered pair is never recorded. When it + * does not answer (it defers with need-content, lacks the + * capability, or failed), the caller computes the builtin diff for + * that pair. The store holds builtin results and nothing else, so + * an identity answer for such a pair equals what the caller would + * compute. Every non-answer is therefore a pass: a refusal would + * suppress that equal answer, and would keep a warming run from + * recording the builtin result the caller computes anyway. + */ +static enum diff_provider_disposition +diff_process_consult(struct diff_provider *provider, + const struct diff_provider_request *req, + diff_provider_fill_fn fill UNUSED, void *fill_data UNUSED, + xdl_emit_hunk_consume_func_t hunk_cb, void *cb_data) +{ + struct diff_process_state *state = provider->state; + struct userdiff_driver *drv; + struct subprocess_entry *running; + + drv = diff_process_driver(req->diffopt, req->path, req->xpp); + if (!drv) + return DIFF_PROVIDER_DISP_PASS; + running = subprocess_find_entry(&state->subprocesses, drv->process); + if (running && container_of(running, struct diff_subprocess, + subprocess)->failed) + return DIFF_PROVIDER_DISP_PASS; + + switch (diff_process_query_hunks(state, drv, req, + hunk_cb, cb_data)) { + case DIFF_PROCESS_OK: + case DIFF_PROCESS_EQUIVALENT: + return DIFF_PROVIDER_DISP_ANSWERED; + case DIFF_PROCESS_SKIP: + case DIFF_PROCESS_ERROR: + break; + } + return DIFF_PROVIDER_DISP_PASS; +} + +static void diff_process_release(struct diff_provider *provider) +{ + struct diff_process_state *state = provider->state; + struct hashmap_iter iter; + struct diff_subprocess *entry; + + /* A failed entry's process is already stopped or never ran. */ + hashmap_for_each_entry(&state->subprocesses, &iter, entry, + subprocess.ent) { + if (!entry->failed) + subprocess_stop_command(&entry->subprocess); + free(entry->cmd); + } + hashmap_clear_and_free(&state->subprocesses, + struct diff_subprocess, subprocess.ent); + free(state); +} + +struct diff_provider *diff_process_provider_new(void) +{ + struct diff_process_state *state = xcalloc(1, sizeof(*state)); + struct diff_provider *p = xcalloc(1, sizeof(*p)); + + hashmap_init(&state->subprocesses, cmd2process_cmp, NULL, 0); + p->consult = diff_process_consult; + p->release = diff_process_release; + p->state = state; + return p; +} diff --git a/diff-provider-internal.h b/diff-provider-internal.h new file mode 100644 index 00000000000000..cba1fa271adf6d --- /dev/null +++ b/diff-provider-internal.h @@ -0,0 +1,130 @@ +#ifndef DIFF_PROVIDER_INTERNAL_H +#define DIFF_PROVIDER_INTERNAL_H + +#include "diff-provider.h" + +/* + * The implementor-facing half of the hunk provider interface: the + * provider chain a repository owns, and the rules a provider applies + * to its own answer before any consumer sees it. Provider + * implementations include this header; consumers of the interface + * use only diff-provider.h. + */ + +/* + * A provider's verdict on one request. Only the chain walk + * (diff-provider.c) sees these; it maps the dispositions of a whole + * walk onto the public outcome set. + */ +enum diff_provider_disposition { + /* + * The provider failed to produce the answer it owns. Only + * the computing provider returns this: its compute leg is + * the one part of a consultation that can fail, and the walk + * ends with the public error outcome. + */ + DIFF_PROVIDER_DISP_ERROR = -1, + + /* + * Answered: every hunk of the pair has been emitted through + * the consumer's callback. + */ + DIFF_PROVIDER_DISP_ANSWERED = 0, + + /* Not this provider's request: the walk consults the next one. */ + DIFF_PROVIDER_DISP_PASS, + + /* + * The pair must not be answered from identity nor recorded: + * the request is shaped by parameters the provider's + * recording key cannot express, so a recorded answer would + * not match this request, and this request's result must not + * be recorded under that key. The walk goes on, but consults + * only the computing provider, and its fall-through outcome + * tells the consumer not to record. + */ + DIFF_PROVIDER_DISP_STOP_NO_RECORD, +}; + +/* + * One provider in a repository's chain (repository.h). The chain is + * assembled in diff-provider.c with a fixed composition; whether a + * provider applies to a request is decided by nobody but the + * provider, whose consult gates itself and passes. Chain position + * carries the authority resolution: an earlier provider's answer or + * refusal outranks every provider after it. + */ +struct diff_provider { + /* + * Consult this provider for one request. fill is NULL on a + * consult-only walk; only the computing provider reads it, + * and it must pass when fill is NULL. + */ + enum diff_provider_disposition + (*consult)(struct diff_provider *provider, + const struct diff_provider_request *req, + diff_provider_fill_fn fill, void *fill_data, + xdl_emit_hunk_consume_func_t hunk_cb, + void *cb_data); + + /* + * Tear down the provider's state, or NULL when it owns none. + * Runs when the owning repository is cleared; the chain frees + * the provider itself afterwards. + */ + void (*release)(struct diff_provider *provider); + + void *state; + + /* + * Set on the provider that loads content and computes rather + * than answering from the request's identity. It alone is + * still consulted after a stop-no-record: an identity answer + * may no longer be served, but the computation must still + * run. + */ + unsigned computes:1; + + struct diff_provider *next; +}; + +/* + * The providers Git ships, besides the builtin computation that + * diff-provider.c holds itself. Each call returns a fresh provider + * for one repository's chain. + */ +struct diff_provider *diff_process_provider_new(void); +struct diff_provider *diff_hunks_store_provider_new(void); + +/* + * Incremental well-formedness check for a provider-supplied hunk + * sequence, shared by every provider. Each coordinate, and each + * hunk's end (its start plus count), must fit int32 (a consumer may + * truncate to int, and a provider may serialize as such); hunks must + * be in order and must not overlap; and the unchanged run between + * hunks must be the same length on both sides, or a consumer that + * walks the two files in lockstep desynchronizes. Every rule + * constrains differences between coordinates, so the check applies + * to 0-based and 1-based sequences alike. + * + * Feed the hunks in order to a zero-initialized struct; the first + * nonzero return names the violated rule, and the whole sequence must + * then be discarded unemitted. + */ +struct diff_provider_hunks_check { + int64_t prev_old_end, prev_new_end; +}; + +enum diff_provider_hunks_error { + DIFF_PROVIDER_HUNKS_OK = 0, + DIFF_PROVIDER_HUNKS_RANGE, /* negative or beyond int32 */ + DIFF_PROVIDER_HUNKS_OVERLAP, /* out of order or overlapping */ + DIFF_PROVIDER_HUNKS_MISALIGNED, /* unchanged runs differ in length */ +}; + +enum diff_provider_hunks_error +diff_provider_check_hunk(struct diff_provider_hunks_check *c, + long old_start, long old_count, + long new_start, long new_count); + +#endif /* DIFF_PROVIDER_INTERNAL_H */ diff --git a/diff-provider.c b/diff-provider.c new file mode 100644 index 00000000000000..c8aaf8e857a8c0 --- /dev/null +++ b/diff-provider.c @@ -0,0 +1,190 @@ +#include "git-compat-util.h" +#include "diff.h" +#include "diff-provider-internal.h" +#include "replace-object.h" +#include "repository.h" + +/* + * The terminal provider: the builtin computation. A request that + * carries a fill callback is answered by loading the pair's content + * and running xdiff, so a walk that reaches it never falls through + * to the consumer. On a consult-only walk it passes, and the walk's + * fall-through outcome tells the consumer to compute. + */ +static enum diff_provider_disposition +builtin_consult(struct diff_provider *provider UNUSED, + const struct diff_provider_request *req, + diff_provider_fill_fn fill, void *fill_data, + xdl_emit_hunk_consume_func_t hunk_cb, void *cb_data) +{ + xdemitconf_t xecfg = { .hunk_func = hunk_cb }; + xdemitcb_t ecb = { .priv = cb_data }; + mmfile_t old_file, new_file; + + if (!fill) + return DIFF_PROVIDER_DISP_PASS; + if (fill(fill_data, &old_file, &new_file) < 0) + return DIFF_PROVIDER_DISP_ERROR; + if (xdi_diff(&old_file, &new_file, req->xpp, &xecfg, &ecb) < 0) + return DIFF_PROVIDER_DISP_ERROR; + return DIFF_PROVIDER_DISP_ANSWERED; +} + +static struct diff_provider *builtin_provider_new(void) +{ + struct diff_provider *p = xcalloc(1, sizeof(*p)); + + p->consult = builtin_consult; + p->computes = 1; + return p; +} + +/* + * The repository's chain, assembled on first walk. The composition + * is fixed, and the order is the authority resolution: the process + * outranks the store, and the builtin computation is the terminal + * provider, so the chain always ends in an implementor that can + * answer. Nothing is decided per repository here; each provider + * gates itself per request. + */ +static struct diff_provider *provider_chain(struct repository *r) +{ + struct diff_provider **tail = &r->diff_providers; + + if (*tail) + return *tail; + *tail = diff_process_provider_new(); + tail = &(*tail)->next; + *tail = diff_hunks_store_provider_new(); + tail = &(*tail)->next; + *tail = builtin_provider_new(); + return r->diff_providers; +} + +void diff_providers_clear(struct repository *r) +{ + struct diff_provider *p = r->diff_providers; + + while (p) { + struct diff_provider *next = p->next; + + if (p->release) + p->release(p); + free(p); + p = next; + } + r->diff_providers = NULL; +} + +/* + * The walk shared by diff_provider_consult() and + * diff_provider_emit_hunks(): consult the chain in order and map its + * dispositions onto the outcome set. The first answer ends the + * walk. A stop-no-record disposition (diff-provider-internal.h) + * is a refusal, not a pass: the provider does not answer, but rules + * the pair out of identity service and out of recording, so from + * then on the walk consults only the computing provider, and a walk + * that ends unanswered carries the no-record verdict. With a fill + * callback the terminal provider computes instead of passing, so an + * emit walk returns only answered or error. + */ +static enum diff_provider_outcome +walk_providers(const struct diff_provider_request *req, + diff_provider_fill_fn fill, void *fill_data, + xdl_emit_hunk_consume_func_t hunk_cb, void *cb_data) +{ + struct diff_provider *p; + int no_record = 0; + + if (req->diffopt && req->diffopt->repo != req->repo) + BUG("diff provider request walks one repository's chain " + "with another repository's diff options"); + + /* + * An object replacement redirects a blob's content + * (OBJECT_INFO_LOOKUP_REPLACE) while leaving the id that names it + * unchanged, so an answer keyed on the raw id would be the + * pre-replacement diff. A replacement is therefore a parameter + * outside the recording key: no provider may serve a replaced pair + * from its identity, and a result computed for it must not be + * recorded under the raw id. Mark the walk no-record so the + * identity providers step aside and the builtin computes from the + * replaced content. The check is a no-op when the repository has + * no replace refs. + */ + if ((req->old_oid && + lookup_replace_object(req->repo, req->old_oid) != req->old_oid) || + (req->new_oid && + lookup_replace_object(req->repo, req->new_oid) != req->new_oid)) + no_record = 1; + + for (p = provider_chain(req->repo); p; p = p->next) { + enum diff_provider_disposition disp; + + if (no_record && !p->computes) + continue; + disp = p->consult(p, req, fill, fill_data, + hunk_cb, cb_data); + if (disp == DIFF_PROVIDER_DISP_ERROR && !p->computes) + BUG("only the computing provider may return the " + "error disposition"); + if (p->computes && !fill && disp != DIFF_PROVIDER_DISP_PASS) + BUG("the computing provider must pass on a " + "fill-less walk"); + switch (disp) { + case DIFF_PROVIDER_DISP_ANSWERED: + return DIFF_PROVIDER_ANSWERED; + case DIFF_PROVIDER_DISP_PASS: + continue; + case DIFF_PROVIDER_DISP_STOP_NO_RECORD: + no_record = 1; + continue; + case DIFF_PROVIDER_DISP_ERROR: + return DIFF_PROVIDER_ERROR; + } + } + return no_record ? DIFF_PROVIDER_UNANSWERED_NO_RECORD : + DIFF_PROVIDER_UNANSWERED; +} + +enum diff_provider_outcome +diff_provider_consult(const struct diff_provider_request *req, + xdl_emit_hunk_consume_func_t hunk_cb, void *cb_data) +{ + return walk_providers(req, NULL, NULL, hunk_cb, cb_data); +} + +enum diff_provider_hunks_error +diff_provider_check_hunk(struct diff_provider_hunks_check *c, + long old_start, long old_count, + long new_start, long new_count) +{ + if (old_start < 0 || old_count < 0 || + new_start < 0 || new_count < 0 || + old_start > INT32_MAX || old_count > INT32_MAX || + new_start > INT32_MAX || new_count > INT32_MAX || + (int64_t)old_start + old_count > INT32_MAX || + (int64_t)new_start + new_count > INT32_MAX) + return DIFF_PROVIDER_HUNKS_RANGE; + if (old_start < c->prev_old_end || new_start < c->prev_new_end) + return DIFF_PROVIDER_HUNKS_OVERLAP; + if (old_start - c->prev_old_end != new_start - c->prev_new_end) + return DIFF_PROVIDER_HUNKS_MISALIGNED; + /* + * With each field bounded to int32 above, the int64 sums cannot + * overflow even where long is 32-bit, and the range rule has + * already capped them at INT32_MAX. + */ + c->prev_old_end = (int64_t)old_start + old_count; + c->prev_new_end = (int64_t)new_start + new_count; + return DIFF_PROVIDER_HUNKS_OK; +} + +enum diff_provider_outcome +diff_provider_emit_hunks(const struct diff_provider_request *req, + diff_provider_fill_fn fill, void *fill_data, + xdl_emit_hunk_consume_func_t hunk_cb, + void *cb_data) +{ + return walk_providers(req, fill, fill_data, hunk_cb, cb_data); +} diff --git a/diff-provider.h b/diff-provider.h new file mode 100644 index 00000000000000..061e1c2f5c2772 --- /dev/null +++ b/diff-provider.h @@ -0,0 +1,159 @@ +#ifndef DIFF_PROVIDER_H +#define DIFF_PROVIDER_H + +#include "xdiff-interface.h" + +/* + * The hunk provider interface sits between naming a pair of file + * versions to diff and computing their changed line ranges. + * Consumers that operate on hunk coordinates route their diff + * through here, so that a provider can answer for the pair before + * its content is loaded. + * + * A hunk provider answers a consumer's request from the pair's + * identity, its blob object ids and the settings that determine the + * diff, before any content is loaded; a request no provider answers + * falls through to the consumer's own computation. Two providers implement this + * interface with different authority. The diff-hunks store + * (diff-hunks.h) is in-process and not authoritative: it may only + * reproduce the builtin result, so it never asserts a pair + * equivalent, and it stands aside wherever a process outranks it. A + * process configured in diff..process (diff-process.c) is + * authoritative for its paths: its answer may deliberately differ + * from the builtin diff, including asserting a pair equivalent. The + * interface resolves that authority through a provider chain owned + * by the repository, built on first consultation and released by + * repo_clear(): chain order is the resolution, and the builtin + * computation itself is the chain's terminal provider. A consumer + * never names a provider; it reads the outcome below. Every answer a + * provider serves from identity passes the shared coordinate check + * (diff-provider-internal.h) before any consumer sees it. + */ + +struct diff_options; +struct object_id; +struct repository; + +/* + * The result of a consultation: two dependent axes flattened into + * their four valid points. The first axis is the state of the + * response: the pair was answered, no provider answered, or (from + * diff_provider_emit_hunks() alone) the attempt failed. The second + * axis exists only in the unanswered state: whether what the caller + * computes for this request may be recorded, the one rule the + * interface imposes on an otherwise free caller. The rule travels + * in the outcome because the knowledge is a provider's while the + * recording is the caller's, and it shares the enum with the state, + * rather than riding a separate flag, so that no meaningless + * combination is representable and -Wswitch forces every consumer + * that switches to place the no-record arm. + * + * These values describe consultations, not providers: the set does + * not grow when a provider is added; a new provider maps onto these + * values inside the interface, so consumer code is written once. + * Each entry point returns a subrange of the set (stated at its + * declaration); a switch over this enum should list every value and + * omit "default:" so -Wswitch keeps it exhaustive, and a caller for + * whom only one value is actionable may compare against that value + * alone. + */ +enum diff_provider_outcome { + /* + * Loading or diffing the pair failed. Returned only by + * diff_provider_emit_hunks(), whose compute leg is the only + * part of a consultation that can fail. + */ + DIFF_PROVIDER_ERROR = -1, + + /* + * The request is answered: every hunk of the pair has been + * emitted through the callback. An authoritative provider + * that finds the pair equivalent answers with no hunks at + * all, so a callback that never fired is an answer, not an + * accident. + */ + DIFF_PROVIDER_ANSWERED = 0, + + /* + * No provider answered. What happens next is the caller's + * business, typically computing the diff itself; a result it + * computes for this request may be recorded. + */ + DIFF_PROVIDER_UNANSWERED, + + /* + * No provider answered, and what the caller computes for + * this request must not be recorded: either an authoritative + * provider owns the pair and declined this request, or the + * request is shaped by parameters outside the recording key, + * the key a recorded result is later served by. + */ + DIFF_PROVIDER_UNANSWERED_NO_RECORD, +}; + +/* + * A consultation request. The interface consults providers from + * these fields alone; no content is loaded before an answer. + * + * repo owns the provider chain the request walks. old_oid/new_oid + * name the blobs whose bytes are diffed; pass NULL for a side whose + * bytes are not a stored blob (a working-tree file, textconv output, + * a gitlink), so no provider answers from an id it cannot look up. + * path names the file the pair is diffed as; a provider selected by + * path applies only where it is set. diffopt carries the diff + * settings that live outside xpp; xpp carries the parameters the + * diff runs with. Each provider gates itself on the fields that + * concern it. + */ +struct diff_provider_request { + struct repository *repo; + const struct object_id *old_oid; + const struct object_id *new_oid; + const char *path; + struct diff_options *diffopt; + const xpparam_t *xpp; +}; + +/* + * Consult the providers for the request's pair without computing. + * On DIFF_PROVIDER_ANSWERED the hunks were emitted through hunk_cb + * (0-based emission coordinates, context 0) and were validated + * before the first callback ran, so a consumer may accumulate + * directly into its result. Never returns DIFF_PROVIDER_ERROR. + * The callback's return value is not consulted: emission of a + * validated answer has no error leg, so the callback must return 0. + */ +enum diff_provider_outcome +diff_provider_consult(const struct diff_provider_request *req, + xdl_emit_hunk_consume_func_t hunk_cb, void *cb_data); + +/* + * Load the pair's content. Called at most once per request, only + * when the ranges are computed rather than provided. The buffers + * borrow storage owned by the callback's owner. + */ +typedef int (*diff_provider_fill_fn)(void *data, mmfile_t *old_file, + mmfile_t *new_file); + +/* + * Consult the providers and, when no identity answer serves the + * request, load the pair's content through fill and compute its + * exact changed ranges (context 0). Emits to hunk_cb either way and + * returns DIFF_PROVIDER_ANSWERED, or DIFF_PROVIDER_ERROR when fill + * or the diff fails. The unanswered outcomes are never returned: a + * pair no provider answers is computed here instead of in the caller. + */ +enum diff_provider_outcome +diff_provider_emit_hunks(const struct diff_provider_request *req, + diff_provider_fill_fn fill, void *fill_data, + xdl_emit_hunk_consume_func_t hunk_cb, + void *cb_data); + +/* + * Release the repository's provider chain: stop any provider-owned + * processes and free the providers. Called by repo_clear(); the + * chain builds again on the next consultation. + */ +void diff_providers_clear(struct repository *r); + +#endif /* DIFF_PROVIDER_H */ diff --git a/diff.c b/diff.c index 5a584fa1d569e7..ea79d80c26eb07 100644 --- a/diff.c +++ b/diff.c @@ -16,6 +16,8 @@ #include "revision.h" #include "quote.h" #include "diff.h" +#include "diff-hunks.h" +#include "diff-provider.h" #include "diffcore.h" #include "delta.h" #include "hex.h" @@ -34,6 +36,7 @@ #include "tmp-objdir.h" #include "graph.h" #include "oid-array.h" +#include "trace2.h" #include "packfile.h" #include "pager.h" #include "parse-options.h" @@ -610,28 +613,74 @@ struct emit_callback { }; /* - * State for the line-range callback wrappers that sit between - * xdi_diff_outf() and fn_out_consume(). xdiff produces a normal, - * unfiltered diff; the wrappers intercept each hunk header and line, - * track post-image position, and forward only lines that fall within - * the requested ranges. Contiguous in-range lines are collected into - * range hunks and flushed with a synthetic @@ header so that - * fn_out_consume() sees well-formed unified-diff fragments. + * Line-range filter: scopes "git log -L" output to the tracked ranges. * - * Removal lines ('-') cannot be classified by post-image position, so - * they are buffered in pending_rm until the next '+' or ' ' line - * reveals whether they precede an in-range line (flush into range hunk) or - * an out-of-range line (discard). + * It sits between xdi_diff_outf() and an output callback (fn_out_consume, + * diffstat_consume, checkdiff_consume). xdiff produces a normal diff; the + * filter forwards only the lines inside the requested ranges, collecting + * contiguous in-range lines into a "range hunk" emitted with a synthetic + * @@ header so the callback sees well-formed unified-diff fragments. + * + * A diff describes the change from a pre-image to a post-image. Each + * line is context (' ', in both), a removal ('-', pre-image only), or + * an addition ('+', post-image only). -L tracks ranges in the + * post-image, so a line is in range by its post-image position. + * + * Two 1-based cursors track the next line in each image, named as in + * struct emit_callback and seeded from the xdiff hunk header: + * + * lno_in_postimage advances on '+' and ' ' (lines in the post-image) + * lno_in_preimage advances on '-' and ' ' (lines in the pre-image) + * + * Ranges are 0-based half-open [start, end), so a line is tested at the + * 0-based index idx_in_postimage = lno_in_postimage - 1. + * + * A '-' is not present in the post-image, so it has no post-image line + * number of its own. Since it does not advance lno_in_postimage, it is + * classified at the idx_in_postimage that the following '+'/' ' will + * occupy. xdiff emits a change's removals before its additions, so that + * index is already known when the '-' arrives. + * + * The synthetic "@@ - + @@" header has two sides, old (the + * pre-image) and new (the post-image), matching the xdiff_emit_hunk_fn + * callback; the hunk.old_begin / hunk.new_begin fields below hold those + * begins, and flush_range_hunk() derives the counts from the buffered + * lines. + * + * Example, tracking post-image line 2 (range [1, 2)) of: + * + * pre-image post-image + * 1 a 1 a + * 2 b 2 X (b -> X) + * 3 c 3 c + * + * classify each line by idx_in_postimage. The pre and post columns + * are each cursor's value while that line is classified, i.e. before + * the line advances them (pre = lno_in_preimage, + * post = lno_in_postimage, idx = idx_in_postimage): + * ' a' pre 1 post 1 idx 0 -> before start, skip + * '-b' pre 2 post 2 idx 1 -> keep (removal) + * '+X' pre 3 post 2 idx 1 -> keep (addition) + * ' c' pre 3 post 3 idx 2 -> past end, flush + * + * -b and +X share idx = 1 because -b did not advance lno_in_postimage; + * both land in the range hunk, flushed when ' c' crosses the range end. */ -struct line_range_callback { +struct line_range_filter { xdiff_emit_line_fn orig_line_fn; + /* + * Optional; consumers that report file line numbers (e.g. + * checkdiff) need the synthetic hunk header to set their + * post-image position before in-range lines are replayed. + */ + xdiff_emit_hunk_fn orig_hunk_fn; void *orig_cb_data; const struct range_set *ranges; /* 0-based [start, end) */ unsigned int cur_range; /* index into the range_set */ /* Post/pre-image line counters (1-based, set from hunk headers) */ - long lno_post; - long lno_pre; + long lno_in_postimage; + long lno_in_preimage; /* * Function name from most recent xdiff hunk header; @@ -640,17 +689,17 @@ struct line_range_callback { char func[80]; long funclen; - /* Range hunk being accumulated for the current range */ - struct strbuf rhunk; - long rhunk_old_begin, rhunk_old_count; - long rhunk_new_begin, rhunk_new_count; - int rhunk_active; - int rhunk_has_changes; /* any '+' or '-' lines? */ - - /* Removal lines not yet known to be in-range */ - struct strbuf pending_rm; - int pending_rm_count; - long pending_rm_pre_begin; /* pre-image line of first pending */ + /* + * The range hunk being accumulated. At most one is live at a time: + * it is flushed and reset as the cursor leaves each range (and once + * more at end of diff), then reused for the next range. + */ + struct { + struct strbuf lines; /* buffered in-range diff lines */ + long old_begin; + long new_begin; + int active; + } hunk; int ret; /* latched error from orig_line_fn */ }; @@ -2540,26 +2589,60 @@ static int quick_consume(void *priv, char *line UNUSED, unsigned long len UNUSED return 1; } -static void discard_pending_rm(struct line_range_callback *s) +static void line_range_filter_init(struct line_range_filter *filter, + const struct range_set *ranges, + xdiff_emit_line_fn line_fn, + void *cb_data) { - strbuf_reset(&s->pending_rm); - s->pending_rm_count = 0; + memset(filter, 0, sizeof(*filter)); + filter->orig_line_fn = line_fn; + filter->orig_cb_data = cb_data; + filter->ranges = ranges; + strbuf_init(&filter->hunk.lines, 0); } -static void flush_rhunk(struct line_range_callback *s) +/* + * Begin a range hunk at the first in-range line. Its position fixes the + * hunk's begins, taken from the two image cursors before they advance: + * new_begin from the post-image, old_begin from the pre-image. The line + * counts are not tracked here; flush_range_hunk() derives them from the + * buffered lines. + */ +static void begin_range_hunk(struct line_range_filter *filter) +{ + filter->hunk.active = 1; + filter->hunk.new_begin = filter->lno_in_postimage; + filter->hunk.old_begin = filter->lno_in_preimage; + strbuf_reset(&filter->hunk.lines); +} + +static void flush_range_hunk(struct line_range_filter *filter) { struct strbuf hdr = STRBUF_INIT; const char *p, *end; + long old_count = 0, new_count = 0; + int has_changes = 0; - if (!s->rhunk_active || s->ret) + if (!filter->hunk.active || filter->ret) return; - /* Drain any pending removal lines into the range hunk */ - if (s->pending_rm_count) { - strbuf_addbuf(&s->rhunk, &s->pending_rm); - s->rhunk_old_count += s->pending_rm_count; - s->rhunk_has_changes = 1; - discard_pending_rm(s); + /* + * Derive the hunk's geometry from the buffered lines: a ' ' + * counts on both sides, a '-' on the old side, a '+' on the new. + * A '-' or '+' marks a real change; the "\ No newline at end of + * file" marker (line[0] == '\\') counts on neither side. + */ + p = filter->hunk.lines.buf; + end = p + filter->hunk.lines.len; + while (p < end) { + const char *eol = memchr(p, '\n', end - p); + if (*p == ' ' || *p == '-') + old_count++; + if (*p == ' ' || *p == '+') + new_count++; + if (*p == '-' || *p == '+') + has_changes = 1; + p = eol ? eol + 1 : end; } /* @@ -2568,22 +2651,28 @@ static void flush_rhunk(struct line_range_callback *s) * ctxlen causes xdiff to emit context covering a range that * has no changes in this commit. */ - if (!s->rhunk_has_changes) { - s->rhunk_active = 0; - strbuf_reset(&s->rhunk); + if (!has_changes) { + filter->hunk.active = 0; + strbuf_reset(&filter->hunk.lines); return; } - strbuf_addf(&hdr, "@@ -%ld,%ld +%ld,%ld @@", - s->rhunk_old_begin, s->rhunk_old_count, - s->rhunk_new_begin, s->rhunk_new_count); - if (s->funclen > 0) { - strbuf_addch(&hdr, ' '); - strbuf_add(&hdr, s->func, s->funclen); - } - strbuf_addch(&hdr, '\n'); + xdiff_emit_hunk_header(&hdr, filter->hunk.old_begin, old_count, + filter->hunk.new_begin, new_count, + filter->func, filter->funclen); + + /* + * Inform a line-numbering consumer of the post-image position + * before replaying lines, mirroring the hunk callback xdiff + * would have issued for a non-scoped diff. + */ + if (filter->orig_hunk_fn) + filter->orig_hunk_fn(filter->orig_cb_data, + filter->hunk.old_begin, old_count, + filter->hunk.new_begin, new_count, + filter->func, filter->funclen); - s->ret = s->orig_line_fn(s->orig_cb_data, hdr.buf, hdr.len); + filter->ret = filter->orig_line_fn(filter->orig_cb_data, hdr.buf, hdr.len); strbuf_release(&hdr); /* @@ -2591,135 +2680,159 @@ static void flush_rhunk(struct line_range_callback *s) * The cast discards const because xdiff_emit_line_fn takes * char *, though fn_out_consume does not modify the buffer. */ - p = s->rhunk.buf; - end = p + s->rhunk.len; - while (!s->ret && p < end) { + p = filter->hunk.lines.buf; + end = p + filter->hunk.lines.len; + while (!filter->ret && p < end) { const char *eol = memchr(p, '\n', end - p); unsigned long line_len = eol ? (unsigned long)(eol - p + 1) : (unsigned long)(end - p); - s->ret = s->orig_line_fn(s->orig_cb_data, (char *)p, line_len); + filter->ret = filter->orig_line_fn(filter->orig_cb_data, (char *)p, line_len); p += line_len; } - s->rhunk_active = 0; - strbuf_reset(&s->rhunk); + filter->hunk.active = 0; + strbuf_reset(&filter->hunk.lines); } static void line_range_hunk_fn(void *data, - long old_begin, long old_nr UNUSED, - long new_begin, long new_nr UNUSED, + long old_begin, long old_nr, + long new_begin, long new_nr, const char *func, long funclen) { - struct line_range_callback *s = data; + struct line_range_filter *filter = data; /* - * When count > 0, begin is 1-based. When count == 0, begin is - * adjusted down by 1 by xdl_emit_hunk_hdr(), but no lines of - * that type will arrive, so the value is unused. - * - * Any pending removal lines from the previous xdiff hunk are - * intentionally left in pending_rm: the line callback will - * flush or discard them when the next content line reveals - * whether the removals precede in-range content. + * Seed the per-image line cursors from the hunk header's begins. For + * a side with no lines (count 0), xdiff's callback has already moved + * its begin to the line before the change, so add one back to recover + * the true 1-based start. xdiff_emit_hunk_header() reapplies that -1 + * when the clipped hunk is emitted. */ - s->lno_post = new_begin; - s->lno_pre = old_begin; + filter->lno_in_postimage = new_nr ? new_begin : new_begin + 1; + filter->lno_in_preimage = old_nr ? old_begin : old_begin + 1; if (funclen > 0) { - if (funclen > (long)sizeof(s->func)) - funclen = sizeof(s->func); - memcpy(s->func, func, funclen); + if (funclen > (long)sizeof(filter->func)) + funclen = sizeof(filter->func); + memcpy(filter->func, func, funclen); } - s->funclen = funclen; + filter->funclen = funclen; } static int line_range_line_fn(void *priv, char *line, unsigned long len) { - struct line_range_callback *s = priv; - const struct range *cur; - long lno_0, cur_pre; + struct line_range_filter *filter = priv; + long idx_in_postimage; + int in_range; - if (s->ret) - return s->ret; - - if (line[0] == '-') { - if (!s->pending_rm_count) - s->pending_rm_pre_begin = s->lno_pre; - s->lno_pre++; - strbuf_add(&s->pending_rm, line, len); - s->pending_rm_count++; - return s->ret; - } + if (filter->ret) + return filter->ret; if (line[0] == '\\') { - if (s->pending_rm_count) - strbuf_add(&s->pending_rm, line, len); - else if (s->rhunk_active) - strbuf_add(&s->rhunk, line, len); - /* otherwise outside tracked range; drop silently */ - return s->ret; + if (filter->hunk.active) + strbuf_add(&filter->hunk.lines, line, len); + return filter->ret; } - if (line[0] != '+' && line[0] != ' ') + if (line[0] != '+' && line[0] != ' ' && line[0] != '-') BUG("unexpected diff line type '%c'", line[0]); - lno_0 = s->lno_post - 1; - cur_pre = s->lno_pre; /* save before advancing for context lines */ - s->lno_post++; - if (line[0] == ' ') - s->lno_pre++; + /* + * idx_in_postimage is this line's 0-based post-image index (see the model on + * struct line_range_filter). The cursors are advanced only after + * the line is classified, so a '-' is tested at the same idx_in_postimage as + * the '+'/' ' that follows it. + */ + idx_in_postimage = filter->lno_in_postimage - 1; - /* Advance past ranges we've passed */ - while (s->cur_range < s->ranges->nr && - lno_0 >= s->ranges->ranges[s->cur_range].end) { - if (s->rhunk_active) - flush_rhunk(s); - discard_pending_rm(s); - s->cur_range++; + /* Retire ranges we have passed, flushing the one we leave. */ + while (filter->cur_range < filter->ranges->nr && + idx_in_postimage >= filter->ranges->ranges[filter->cur_range].end) { + if (filter->hunk.active) + flush_range_hunk(filter); + filter->cur_range++; } - /* Past all ranges */ - if (s->cur_range >= s->ranges->nr) { - discard_pending_rm(s); - return s->ret; - } + in_range = filter->cur_range < filter->ranges->nr && + idx_in_postimage >= filter->ranges->ranges[filter->cur_range].start && + idx_in_postimage < filter->ranges->ranges[filter->cur_range].end; - cur = &s->ranges->ranges[s->cur_range]; + if (in_range) { + if (!filter->hunk.active) + begin_range_hunk(filter); - /* Before current range */ - if (lno_0 < cur->start) { - discard_pending_rm(s); - return s->ret; + strbuf_add(&filter->hunk.lines, line, len); } - /* In range so start a new range hunk if needed */ - if (!s->rhunk_active) { - s->rhunk_active = 1; - s->rhunk_has_changes = 0; - s->rhunk_new_begin = lno_0 + 1; - s->rhunk_old_begin = s->pending_rm_count - ? s->pending_rm_pre_begin : cur_pre; - s->rhunk_old_count = 0; - s->rhunk_new_count = 0; - strbuf_reset(&s->rhunk); + /* + * Advance each image's cursor: a line present in that image (see + * the model) consumes one of its line numbers. + */ + if (line[0] != '-') + filter->lno_in_postimage++; + if (line[0] != '+') + filter->lno_in_preimage++; + + return filter->ret; +} + +/* + * Run an xdiff pass through an initialized line-range filter, flush the + * final range hunk, and release the filter. Inflates ctxlen to the largest + * range span first, so that every change within a single range lands in one + * xdiff hunk and the inter-change context is emitted; the filter then clips + * back to range boundaries. The optimal ctxlen depends on where changes fall + * within the range, which is only known after xdiff runs, so the max span is + * the upper bound that guarantees correctness in a single pass. Every + * consumer (patch, diffstat, check) relies on one xdiff hunk per range, so + * this lives here rather than at each call site. Also clears + * XDL_EMIT_NO_HUNK_HDR: the filter seeds its per-image position from the hunk + * headers, so a consumer that otherwise suppresses them (diffstat) still gets + * them here. Returns non-zero if xdiff or any forwarded callback failed. + */ +static int line_range_filter_diff(struct line_range_filter *filter, + mmfile_t *mf1, mmfile_t *mf2, + xpparam_t *xpp, xdemitconf_t *xecfg) +{ + const struct range_set *ranges = filter->ranges; + long max_span = 0; + unsigned int i; + int ret; + + for (i = 0; i < ranges->nr; i++) { + long span = ranges->ranges[i].end - ranges->ranges[i].start; + if (span > max_span) + max_span = span; } + if (max_span > xecfg->ctxlen) + xecfg->ctxlen = max_span; - /* Flush pending removals into range hunk */ - if (s->pending_rm_count) { - strbuf_addbuf(&s->rhunk, &s->pending_rm); - s->rhunk_old_count += s->pending_rm_count; - s->rhunk_has_changes = 1; - discard_pending_rm(s); + /* the filter seeds its per-image position from hunk headers */ + xecfg->flags &= ~XDL_EMIT_NO_HUNK_HDR; + + ret = xdi_diff_outf(mf1, mf2, line_range_hunk_fn, + line_range_line_fn, filter, xpp, xecfg); + if (!ret) { + flush_range_hunk(filter); + ret = filter->ret; } + strbuf_release(&filter->hunk.lines); + return ret; +} - strbuf_add(&s->rhunk, line, len); - s->rhunk_new_count++; - if (line[0] == '+') - s->rhunk_has_changes = 1; - else - s->rhunk_old_count++; +/* + * Expose the in-file line-range filter to callers outside diff.c (e.g. + * pickaxe -G); see xdiff-interface.h for the contract. + */ +int diff_emit_line_ranges(mmfile_t *one, mmfile_t *two, + const struct range_set *ranges, + xdiff_emit_line_fn line_fn, void *cb_data, + xpparam_t *xpp, xdemitconf_t *xecfg) +{ + struct line_range_filter filter; - return s->ret; + line_range_filter_init(&filter, ranges, line_fn, cb_data); + return line_range_filter_diff(&filter, one, two, xpp, xecfg); } static void pprint_rename(struct strbuf *name, const char *a, const char *b) @@ -2819,6 +2932,77 @@ static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat, return x; } +struct diffstat_hunk_cb_data { + struct precomputed_hunk **h; + size_t *nr, *alloc; +}; + +/* + * Hunk callback that appends each hunk's coordinates to a growable + * array, so one xdiff pass can both sum a diffstat and record hunks for + * the store. + */ +static int diffstat_hunk_cb(long start_a, long count_a, + long start_b, long count_b, + void *cb_data) +{ + struct diffstat_hunk_cb_data *d = cb_data; + + ALLOC_GROW(*d->h, *d->nr + 1, *d->alloc); + (*d->h)[*d->nr].old_start = start_a; + (*d->h)[*d->nr].old_count = count_a; + (*d->h)[*d->nr].new_start = start_b; + (*d->h)[*d->nr].new_count = count_b; + (*d->nr)++; + return 0; +} + +/* + * Collect the hunks of the two files at zero context. diff_fn chooses + * whether trimming runs: xdi_diff applies trim_common_tail, yielding the + * zero-context hunks blame reads; xdl_diff does not, yielding the + * untrimmed hunks. Both run at zero context, so the untrimmed hunks are + * not grouped the way a nonzero context would group them; diffstat only + * sums their counts, which grouping does not change. Sets *ph (caller + * frees) and *ph_nr. + */ +typedef int (*xdiff_fn)(mmfile_t *, mmfile_t *, xpparam_t const *, + xdemitconf_t const *, xdemitcb_t *); +static int collect_hunks(xdiff_fn diff_fn, mmfile_t *mf1, mmfile_t *mf2, + xpparam_t *xpp, struct precomputed_hunk **ph, + size_t *ph_nr) +{ + size_t ph_alloc = 0; + xdemitcb_t ecb = { 0 }; + xdemitconf_t xecfg = { 0 }; + struct diffstat_hunk_cb_data cd = { ph, ph_nr, &ph_alloc }; + + *ph = NULL; + *ph_nr = 0; + xecfg.hunk_func = diffstat_hunk_cb; + ecb.priv = &cd; + return diff_fn(mf1, mf2, xpp, &xecfg, &ecb); +} + +void diff_hunks_attach(struct diff_options *o) +{ + if (!(o->output_format & + (DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_NUMSTAT))) + return; + o->hunks_writer = diff_hunks_writer_maybe_new(o->repo); +} + +void diff_hunks_detach(struct diff_options *o) +{ + unsigned long hits, misses; + + diff_hunks_read_stats(o->repo, &hits, &misses); + if (hits) + trace2_data_intmax("diff-hunks", o->repo, "read-hits", hits); + diff_hunks_writer_finish(o->hunks_writer); + o->hunks_writer = NULL; +} + static int diffstat_consume(void *priv, char *line, unsigned long len) { struct diffstat_t *diffstat = priv; @@ -3612,8 +3796,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; @@ -4086,51 +4271,15 @@ static void builtin_diff(const char *name_a, xdi_diff_outf(&mf1, &mf2, NULL, quick_consume, &ecbdata, &xpp, &xecfg); } else if (line_ranges) { - struct line_range_callback lr_state; - unsigned int i; - long max_span = 0; + struct line_range_filter lr_filter; - memset(&lr_state, 0, sizeof(lr_state)); - lr_state.orig_line_fn = fn_out_consume; - lr_state.orig_cb_data = &ecbdata; - lr_state.ranges = line_ranges; - strbuf_init(&lr_state.rhunk, 0); - strbuf_init(&lr_state.pending_rm, 0); + line_range_filter_init(&lr_filter, line_ranges, + fn_out_consume, &ecbdata); - /* - * Inflate ctxlen so that all changes within - * any single range are merged into one xdiff - * hunk and the inter-change context is emitted. - * The callback clips back to range boundaries. - * - * The optimal ctxlen depends on where changes - * fall within the range, which is only known - * after xdiff runs; the max range span is the - * upper bound that guarantees correctness in a - * single pass. - */ - for (i = 0; i < line_ranges->nr; i++) { - long span = line_ranges->ranges[i].end - - line_ranges->ranges[i].start; - if (span > max_span) - max_span = span; - } - if (max_span > xecfg.ctxlen) - xecfg.ctxlen = max_span; - - if (xdi_diff_outf(&mf1, &mf2, - line_range_hunk_fn, - line_range_line_fn, - &lr_state, &xpp, &xecfg)) + if (line_range_filter_diff(&lr_filter, &mf1, &mf2, + &xpp, &xecfg)) die("unable to generate diff for %s", one->path); - - flush_rhunk(&lr_state); - if (lr_state.ret) - die("unable to generate diff for %s", - one->path); - strbuf_release(&lr_state.rhunk); - strbuf_release(&lr_state.pending_rm); } else if (xdi_diff_outf(&mf1, &mf2, NULL, fn_out_consume, &ecbdata, &xpp, &xecfg)) die("unable to generate diff for %s", one->path); @@ -4178,6 +4327,125 @@ static const char *get_compact_summary(const struct diff_filepair *p, int is_ren return NULL; } +/* + * Hunk callback for the provider interface: sum counts into a + * diffstat entry. + */ +static int diffstat_sum_hunk_cb(long start_a UNUSED, long count_a, + long start_b UNUSED, long count_b, + void *cb_data) +{ + struct diffstat_file *data = cb_data; + + data->added += count_b; + data->deleted += count_a; + return 0; +} + +/* + * Fill data->added/deleted for a modified pair through the hunk provider + * interface: on an answer, sum the provided counts; on a warming run, + * compute and record them. Returns 1 when it produced the counts, 0 when + * the caller must compute the diffstat itself. + * + * The providers own the exclusions the request can express (-B, -I, + * and --anchored are outside the store key). This consumer additionally + * excludes --ignore-blank-lines before consulting: that flag is part of + * the key, but it coalesces hunks differently between the emit and + * hunk-callback paths, so a served answer would not match a store-less + * run's --stat output. (--inter-hunk-context is not excluded: it only + * groups hunks, and diffstat sums their counts, which grouping does not + * change.) Recording requires both sides to be valid regular files whose + * blobs the key can name. + */ +static int diffstat_from_hunks(struct diff_options *o, + struct diff_filespec *one, + struct diff_filespec *two, + struct diffstat_file *data) +{ + struct precomputed_hunk *ph_trim, *ph_full, *counts; + size_t n_trim, n_full, n_counts, k; + mmfile_t mf1, mf2; + xpparam_t xpp = { .flags = o->xdl_opts, + .ignore_regex = o->ignore_regex, + .ignore_regex_nr = o->ignore_regex_nr, + .anchors = o->anchors, + .anchors_nr = o->anchors_nr }; + struct diff_provider_request req = { + .repo = o->repo, + .old_oid = (one->oid_valid && !S_ISGITLINK(one->mode)) ? + &one->oid : NULL, + .new_oid = (two->oid_valid && !S_ISGITLINK(two->mode)) ? + &two->oid : NULL, + /* + * Attribute lookup and the process protocol need the + * repo-relative path; the display name a caller passes + * around may be stripped of o->prefix and would miss a + * driver scoped to a directory. + */ + .path = one->path, + .diffopt = o, + .xpp = &xpp, + }; + + if (o->xdl_opts & XDF_IGNORE_BLANK_LINES) + return 0; + /* format-patch keeps its diffstat off the store (see the flag). */ + if (o->flags.no_precomputed_hunks) + return 0; + + switch (diff_provider_consult(&req, diffstat_sum_hunk_cb, data)) { + case DIFF_PROVIDER_ANSWERED: + return 1; + case DIFF_PROVIDER_UNANSWERED: + break; + case DIFF_PROVIDER_ERROR: /* not returned by a consult */ + case DIFF_PROVIDER_UNANSWERED_NO_RECORD: + return 0; + } + + /* A miss on a read-only run: let the caller compute the diffstat. */ + if (!o->hunks_writer) + return 0; + /* Recording needs blobs the key can name, on both sides. */ + if (!req.old_oid || !req.new_oid || + !DIFF_FILE_VALID(one) || !DIFF_FILE_VALID(two) || + !S_ISREG(one->mode) || !S_ISREG(two->mode)) + return 0; + + if (fill_mmfile(o->repo, &mf1, one) < 0 || + fill_mmfile(o->repo, &mf2, two) < 0) + die("unable to read files to diff"); + + /* + * Compute the zero-context trimmed diff (what blame reads) and the + * untrimmed diff (whose counts a nonzero-context stat matches). + * xdi_diff runs first: it enforces the size limit, so the xdl_diff + * call is already bounded. + */ + if (collect_hunks(xdi_diff, &mf1, &mf2, &xpp, &ph_trim, &n_trim) || + collect_hunks(xdl_diff, &mf1, &mf2, &xpp, &ph_full, &n_full)) + die("unable to generate diffstat for %s", one->path); + + /* + * Match a store-less run: at zero context xdi_diff trims, so sum the + * trimmed diff; otherwise sum the untrimmed one. + */ + counts = o->context ? ph_full : ph_trim; + n_counts = o->context ? n_full : n_trim; + for (k = 0; k < n_counts; k++) { + data->added += counts[k].new_count; + data->deleted += counts[k].old_count; + } + + diff_hunks_writer_record_stable(o->hunks_writer, &one->oid, &two->oid, + o->xdl_opts, ph_trim, n_trim, + ph_full, n_full); + free(ph_trim); + free(ph_full); + return 1; +} + static void builtin_diffstat(const char *name_a, const char *name_b, struct diff_filespec *one, struct diff_filespec *two, @@ -4229,27 +4497,52 @@ static void builtin_diffstat(const char *name_a, const char *name_b, } else if (may_differ) { - /* Crazy xdl interfaces.. */ - xpparam_t xpp; - xdemitconf_t xecfg; - - if (fill_mmfile(o->repo, &mf1, one) < 0 || - fill_mmfile(o->repo, &mf2, two) < 0) - die("unable to read files to diff"); + /* + * Serve from a hunk provider (the process, then the store), + * or record into the store on a warming run. A "log -L" + * range-scoped stat is not the whole-pair diff the store + * keys, so it neither reads nor records. Otherwise diff + * normally. + */ + if (p->line_ranges || + !diffstat_from_hunks(o, one, two, data)) { + /* Crazy xdl interfaces.. */ + xpparam_t xpp; + xdemitconf_t xecfg; + + if (fill_mmfile(o->repo, &mf1, one) < 0 || + fill_mmfile(o->repo, &mf2, two) < 0) + die("unable to read files to diff"); + + memset(&xpp, 0, sizeof(xpp)); + memset(&xecfg, 0, sizeof(xecfg)); + xpp.flags = o->xdl_opts; + xpp.ignore_regex = o->ignore_regex; + xpp.ignore_regex_nr = o->ignore_regex_nr; + xpp.anchors = o->anchors; + xpp.anchors_nr = o->anchors_nr; + xecfg.ctxlen = o->context; + xecfg.interhunkctxlen = o->interhunkcontext; + xecfg.flags = XDL_EMIT_NO_HUNK_HDR; - memset(&xpp, 0, sizeof(xpp)); - memset(&xecfg, 0, sizeof(xecfg)); - xpp.flags = o->xdl_opts; - xpp.ignore_regex = o->ignore_regex; - xpp.ignore_regex_nr = o->ignore_regex_nr; - xpp.anchors = o->anchors; - xpp.anchors_nr = o->anchors_nr; - xecfg.ctxlen = o->context; - xecfg.interhunkctxlen = o->interhunkcontext; - xecfg.flags = XDL_EMIT_NO_HUNK_HDR; - if (xdi_diff_outf(&mf1, &mf2, NULL, - diffstat_consume, diffstat, &xpp, &xecfg)) - die("unable to generate diffstat for %s", one->path); + if (p->line_ranges) { + struct line_range_filter lr_filter; + + line_range_filter_init(&lr_filter, + p->line_ranges, + diffstat_consume, + diffstat); + + if (line_range_filter_diff(&lr_filter, &mf1, + &mf2, &xpp, &xecfg)) + die("unable to generate diffstat for %s", + one->path); + } else if (xdi_diff_outf(&mf1, &mf2, NULL, + diffstat_consume, diffstat, + &xpp, &xecfg)) + die("unable to generate diffstat for %s", + one->path); + } if (DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two)) { struct diffstat_file *file = @@ -4277,11 +4570,29 @@ static void builtin_diffstat(const char *name_a, const char *name_b, diff_free_filespec_data(two); } +/* + * Is the 0-based line index within any of the tracked ranges? + * (range_set ranges are 0-based, half-open [start, end).) This is a + * one-shot query for a single line and scans; the streaming filter + * (line_range_line_fn) uses a forward cursor instead. + */ +static int idx_in_ranges(const struct range_set *ranges, long idx) +{ + unsigned int i; + + for (i = 0; i < ranges->nr; i++) + if (idx >= ranges->ranges[i].start && + idx < ranges->ranges[i].end) + return 1; + return 0; +} + static void builtin_checkdiff(const char *name_a, const char *name_b, const char *attr_path, struct diff_filespec *one, struct diff_filespec *two, - struct diff_options *o) + struct diff_options *o, + const struct range_set *line_ranges) { mmfile_t mf1, mf2; struct checkdiff_t data; @@ -4321,7 +4632,19 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 1; /* at least one context line */ xpp.flags = 0; - if (xdi_diff_outf(&mf1, &mf2, checkdiff_consume_hunk, + + if (line_ranges) { + struct line_range_filter lr_filter; + + line_range_filter_init(&lr_filter, line_ranges, + checkdiff_consume, &data); + lr_filter.orig_hunk_fn = checkdiff_consume_hunk; + + if (line_range_filter_diff(&lr_filter, &mf1, &mf2, + &xpp, &xecfg)) + die("unable to generate checkdiff for %s", + one->path); + } else if (xdi_diff_outf(&mf1, &mf2, checkdiff_consume_hunk, checkdiff_consume, &data, &xpp, &xecfg)) die("unable to generate checkdiff for %s", one->path); @@ -4334,6 +4657,17 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, check_blank_at_eof(&mf1, &mf2, &ecbdata); blank_at_eof = ecbdata.blank_at_eof_in_postimage; + /* + * check_blank_at_eof() scans the whole file; with -L, + * keep the report only when its line is in a tracked + * range. The error's location is the first trailing + * blank line (blank_at_eof, 1-based; ranges 0-based), so + * we scope by that line. + */ + if (blank_at_eof && line_ranges && + !idx_in_ranges(line_ranges, blank_at_eof - 1)) + blank_at_eof = 0; + if (blank_at_eof) { static char *err; if (!err) @@ -4594,8 +4928,9 @@ int diff_populate_filespec(struct repository *r, } } else { + size_t size_st = 0; struct object_info info = { - .sizep = &s->size + .sizep = &size_st }; if (!(size_only || check_binary)) @@ -4617,6 +4952,7 @@ int diff_populate_filespec(struct repository *r, die("unable to read %s", oid_to_hex(&s->oid)); object_read: + s->size = cast_size_t_to_ulong(size_st); if (size_only || check_binary) { if (size_only) return 0; @@ -4631,6 +4967,7 @@ int diff_populate_filespec(struct repository *r, if (odb_read_object_info_extended(r->objects, &s->oid, &info, OBJECT_INFO_LOOKUP_REPLACE)) die("unable to read %s", oid_to_hex(&s->oid)); + s->size = cast_size_t_to_ulong(size_st); } s->should_free = 1; } @@ -5126,7 +5463,8 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o) diff_fill_oid_info(p->one, o->repo->index); diff_fill_oid_info(p->two, o->repo->index); - builtin_checkdiff(name, other, attr_path, p->one, p->two, o); + builtin_checkdiff(name, other, attr_path, p->one, p->two, o, + p->line_ranges); } void repo_diff_setup(struct repository *r, struct diff_options *options) @@ -5923,6 +6261,27 @@ static int diff_opt_submodule(const struct option *opt, return 0; } +static int diff_opt_ext_diff(const struct option *opt, + const char *arg, int unset) +{ + struct diff_options *options = opt->value; + + BUG_ON_OPT_ARG(arg); + options->flags.allow_external = !unset; + options->flags.allow_diff_process = !unset; + return 0; +} + +static int diff_opt_diff_process(const struct option *opt, + const char *arg, int unset) +{ + struct diff_options *options = opt->value; + + BUG_ON_OPT_ARG(arg); + options->flags.allow_diff_process = !unset; + return 0; +} + static int diff_opt_textconv(const struct option *opt, const char *arg, int unset) { @@ -6253,8 +6612,12 @@ struct option *add_diff_options(const struct option *opts, N_("exit with 1 if there were differences, 0 otherwise")), OPT_BOOL(0, "quiet", &options->flags.quick, N_("disable all output of the program")), - OPT_BOOL(0, "ext-diff", &options->flags.allow_external, - N_("allow an external diff helper to be executed")), + OPT_CALLBACK_F(0, "ext-diff", options, NULL, + N_("allow an external diff helper to be executed"), + PARSE_OPT_NOARG, diff_opt_ext_diff), + OPT_CALLBACK_F(0, "diff-process", options, NULL, + N_("allow a configured diff process to be consulted"), + PARSE_OPT_NOARG, diff_opt_diff_process), OPT_CALLBACK_F(0, "textconv", options, NULL, N_("run external text conversion filters when comparing binary files"), PARSE_OPT_NOARG, diff_opt_textconv), @@ -6851,7 +7214,7 @@ void flush_one_hunk(struct object_id *result, struct git_hash_ctx *ctx) int i; git_hash_final(hash, ctx); - the_hash_algo->init_fn(ctx); + git_hash_init(ctx, the_hash_algo); /* 20-byte sum, with carry */ for (i = 0; i < the_hash_algo->rawsz; ++i) { carry += result->hash[i] + hash[i]; @@ -6895,7 +7258,7 @@ static int diff_get_patch_id(struct diff_options *options, struct object_id *oid struct git_hash_ctx ctx; struct patch_id_t data; - the_hash_algo->init_fn(&ctx); + git_hash_init(&ctx, the_hash_algo); memset(&data, 0, sizeof(struct patch_id_t)); data.ctx = &ctx; oidclr(oid, the_repository->hash_algo); @@ -6983,6 +7346,7 @@ static int diff_get_patch_id(struct diff_options *options, struct object_id *oid flush_one_hunk(oid, &ctx); } + git_hash_discard(&ctx); return 0; } diff --git a/diff.h b/diff.h index bb5cddaf3499e9..e0b58a8105fe8f 100644 --- a/diff.h +++ b/diff.h @@ -173,6 +173,15 @@ struct diff_flags { */ unsigned allow_external; + /** + * Allows diff..process to be consulted. Set by the + * porcelain commands whose output may reflect a diff process + * (diff, log, show, blame) and by --ext-diff or --diff-process; + * plumbing does not set it by default, so its output stays + * builtin. Cleared by --no-ext-diff or --no-diff-process. + */ + unsigned allow_diff_process; + /** * For communication between the calling program and the options parser; * tell the calling program to signal the presence of difference using @@ -206,6 +215,13 @@ struct diff_flags { unsigned suppress_diff_headers; unsigned dual_color_diffed_diffs; unsigned suppress_hunk_header_line_count; + + /* + * Do not serve the diffstat from the precomputed-hunks store. + * Set by format-patch so a generated patch carries the builtin + * counts and does not depend on the sender's local store state. + */ + unsigned no_precomputed_hunks; }; static inline void diff_flags_or(struct diff_flags *a, @@ -224,6 +240,17 @@ static inline void diff_flags_or(struct diff_flags *a, #define DIFF_WITH_ALG(opts, flag) (((opts)->xdl_opts & ~XDF_DIFF_ALGORITHM_MASK) | XDF_##flag) +/* + * The xdl_opts bits git turns on by default that a from-scratch xdl_opts + * (git blame's own option parsing) does not set, and so must OR in to match + * a store warmed at the default diff settings; a diff_options-based consumer + * (diffstat) already has them in o->xdl_opts. Today this is only the indent + * heuristic. It does NOT cover a non-default diff.algorithm: a repo that + * configures one records under that algorithm, and a consumer keying without + * it misses (a lost hit, not wrong output). + */ +#define DIFF_HUNKS_DEFAULT_XDL_OPTS XDF_INDENT_HEURISTIC + enum diff_words_type { DIFF_WORDS_NONE = 0, DIFF_WORDS_PORCELAIN, @@ -420,6 +447,15 @@ struct diff_options { */ int max_depth; int max_depth_valid; + + /* + * Precomputed diff hunks (see diff-hunks.h). diffstat consults the + * hunk provider interface before running xdiff, keyed by each file + * pair's blob object IDs. When hunks_writer is set (a warming run), + * diffstat also records the hunks it computes; the writer is + * attached only for the stat output formats. + */ + struct diff_hunks_writer *hunks_writer; }; unsigned diff_filter_bit(char status); @@ -668,6 +704,17 @@ void diffcore_fix_diff_index(void); int diff_queue_is_empty(struct diff_options *o); void diff_flush(struct diff_options*); void diff_free(struct diff_options*); + +/* + * Attach a diff-hunks writer to a diff producing a stat format, so a + * warming run records the hunks it computes; a no-op when writing is off + * or for other formats. (Reading is separate: consumers consult the + * providers through diff_provider_consult(); see diff-provider.h.) Pair + * with diff_hunks_detach() once the diff is done. + */ +void diff_hunks_attach(struct diff_options *o); +void diff_hunks_detach(struct diff_options *o); + void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc); /* diff-raw status letters */ diff --git a/diffcore-break.c b/diffcore-break.c index 17b5ad1fedeb3b..b5bcc956ccb831 100644 --- a/diffcore-break.c +++ b/diffcore-break.c @@ -289,6 +289,8 @@ void diffcore_merge_broken(void) */ for (j = i + 1; j < q->nr; j++) { struct diff_filepair *pp = q->queue[j]; + if (!pp) + continue; if (pp->broken_pair && !strcmp(pp->one->path, pp->two->path) && !strcmp(p->one->path, pp->two->path)) { diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c index a52d569911c48e..bad3a88c0f7ea3 100644 --- a/diffcore-pickaxe.c +++ b/diffcore-pickaxe.c @@ -16,7 +16,8 @@ typedef int (*pickaxe_fn)(mmfile_t *one, mmfile_t *two, struct diff_options *o, - regex_t *regexp, kwset_t kws); + regex_t *regexp, kwset_t kws, + const struct range_set *ranges); struct diffgrep_cb { regex_t *regexp; @@ -42,7 +43,8 @@ static int diffgrep_consume(void *priv, char *line, unsigned long len) static int diff_grep(mmfile_t *one, mmfile_t *two, struct diff_options *o, - regex_t *regexp, kwset_t kws UNUSED) + regex_t *regexp, kwset_t kws UNUSED, + const struct range_set *ranges) { struct diffgrep_cb ecbdata; xpparam_t xpp; @@ -50,8 +52,11 @@ static int diff_grep(mmfile_t *one, mmfile_t *two, int ret; /* - * We have both sides; need to run textual diff and see if - * the pattern appears on added/deleted lines. + * We have both sides; need to run textual diff and see if the + * pattern appears on added/deleted lines. Under -L (ranges set), + * forward only the tracked range's lines so the match is scoped. + * -G needs only a hit/no-hit answer, so the line-number bookkeeping + * the filter does for -L patch and check output is irrelevant here. */ memset(&xpp, 0, sizeof(xpp)); memset(&xecfg, 0, sizeof(xecfg)); @@ -65,8 +70,12 @@ static int diff_grep(mmfile_t *one, mmfile_t *two, * An xdiff error might be our "data->hit" from above. See the * comment for xdiff_emit_line_fn in xdiff-interface.h */ - ret = xdi_diff_outf(one, two, NULL, diffgrep_consume, - &ecbdata, &xpp, &xecfg); + if (ranges) + ret = diff_emit_line_ranges(one, two, ranges, diffgrep_consume, + &ecbdata, &xpp, &xecfg); + else + ret = xdi_diff_outf(one, two, NULL, diffgrep_consume, + &ecbdata, &xpp, &xecfg); if (ecbdata.hit) return 1; if (ret) @@ -119,8 +128,13 @@ static unsigned int contains(mmfile_t *mf, regex_t *regexp, kwset_t kws, static int has_changes(mmfile_t *one, mmfile_t *two, struct diff_options *o UNUSED, - regex_t *regexp, kwset_t kws) + regex_t *regexp, kwset_t kws, + const struct range_set *ranges UNUSED) { + /* + * -S counts needle occurrences in each whole blob. Scoping this to + * a -L range is left to a follow-up; for now -S ignores the range. + */ unsigned int c1 = one ? contains(one, regexp, kws, 0) : 0; unsigned int c2 = two ? contains(two, regexp, kws, c1 + 1) : 0; return c1 != c2; @@ -132,6 +146,7 @@ static int pickaxe_match(struct diff_filepair *p, struct diff_options *o, struct userdiff_driver *textconv_one = NULL; struct userdiff_driver *textconv_two = NULL; mmfile_t mf1, mf2; + const struct range_set *ranges; int ret; /* ignore unmerged */ @@ -169,7 +184,13 @@ static int pickaxe_match(struct diff_filepair *p, struct diff_options *o, mf1.size = fill_textconv(o->repo, textconv_one, p->one, &mf1.ptr); mf2.size = fill_textconv(o->repo, textconv_two, p->two, &mf2.ptr); - ret = fn(&mf1, &mf2, o, regexp, kws); + /* + * -L scopes the search to the tracked range, but the range is in + * original-file line coordinates that do not map onto textconv + * output, so search the whole file when textconv is in play. + */ + ranges = (textconv_one || textconv_two) ? NULL : p->line_ranges; + ret = fn(&mf1, &mf2, o, regexp, kws, ranges); if (textconv_one) free(mf1.ptr); @@ -203,7 +224,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 33c81c256ee925..1094d7be3c16ec 100644 --- a/dir.c +++ b/dir.c @@ -126,7 +126,7 @@ int count_slashes(const char *s) int git_fspathcmp(const char *a, const char *b) { - return ignore_case ? strcasecmp(a, b) : strcmp(a, b); + return repo_ignore_case(the_repository) ? strcasecmp(a, b) : strcmp(a, b); } int fspatheq(const char *a, const char *b) @@ -136,7 +136,7 @@ int fspatheq(const char *a, const char *b) int git_fspathncmp(const char *a, const char *b, size_t count) { - return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count); + return repo_ignore_case(the_repository) ? strncasecmp(a, b, count) : strncmp(a, b, count); } int paths_collide(const char *a, const char *b) @@ -153,7 +153,7 @@ int paths_collide(const char *a, const char *b) unsigned int fspathhash(const char *str) { - return ignore_case ? strihash(str) : strhash(str); + return repo_ignore_case(the_repository) ? strihash(str) : strhash(str); } int git_fnmatch(const struct pathspec_item *item, @@ -202,7 +202,7 @@ static int fnmatch_icase_mem(const char *pattern, int patternlen, use_str = str_buf.buf; } - if (ignore_case) + if (repo_ignore_case(the_repository)) flags |= WM_CASEFOLD; match_status = wildmatch(use_pat, use_str, flags); @@ -324,7 +324,7 @@ static int do_read_blob(const struct object_id *oid, struct oid_stat *oid_stat, size_t *size_out, char **data_out) { enum object_type type; - unsigned long sz; + size_t sz; char *data; *size_out = 0; @@ -1851,7 +1851,7 @@ static struct dir_entry *dir_add_name(struct dir_struct *dir, struct index_state *istate, const char *pathname, int len) { - if (index_file_exists(istate, pathname, len, ignore_case)) + if (index_file_exists(istate, pathname, len, repo_ignore_case(the_repository))) return NULL; ALLOC_GROW(dir->entries, dir->nr+1, dir->internal.alloc); @@ -1888,7 +1888,7 @@ static enum exist_status directory_exists_in_index_icase(struct index_state *ist if (index_dir_exists(istate, dirname, len)) return index_directory; - ce = index_file_exists(istate, dirname, len, ignore_case); + ce = index_file_exists(istate, dirname, len, repo_ignore_case(the_repository)); if (ce && S_ISGITLINK(ce->ce_mode)) return index_gitdir; @@ -1907,7 +1907,7 @@ static enum exist_status directory_exists_in_index(struct index_state *istate, { int pos; - if (ignore_case) + if (repo_ignore_case(the_repository)) return directory_exists_in_index_icase(istate, dirname, len); pos = index_name_pos(istate, dirname, len); @@ -2447,7 +2447,7 @@ static enum path_treatment treat_path(struct dir_struct *dir, /* Always exclude indexed files */ has_path_in_index = !!index_file_exists(istate, path->buf, path->len, - ignore_case); + repo_ignore_case(the_repository)); if (dtype != DT_DIR && has_path_in_index) return path_none; @@ -3201,7 +3201,7 @@ static int cmp_icase(char a, char b) { if (a == b) return 0; - if (ignore_case) + if (repo_ignore_case(the_repository)) return toupper(a) - toupper(b); return a - b; } @@ -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); @@ -3791,13 +3792,18 @@ static int read_one_dir(struct untracked_cache_dir **untracked_, ALLOC_ARRAY(ud.untracked, ud.untracked_nr); ud.dirs_alloc = ud.dirs_nr = decode_varint(&data); - if (data > end) + if (data > end) { + free(ud.untracked); return -1; + } ALLOC_ARRAY(ud.dirs, ud.dirs_nr); eos = memchr(data, '\0', end - data); - if (!eos || eos == end) + if (!eos || eos == end) { + free(ud.untracked); + free(ud.dirs); return -1; + } *untracked_ = untracked = xmalloc(st_add3(sizeof(*untracked), eos - data, 1)); memcpy(untracked, &ud, sizeof(ud)); diff --git a/entry.c b/entry.c index 7817aee362ed9e..6b79884e3246d6 100644 --- a/entry.c +++ b/entry.c @@ -92,11 +92,9 @@ static int create_file(const char *path, unsigned int mode) void *read_blob_entry(const struct cache_entry *ce, size_t *size) { enum object_type type; - unsigned long ul; void *blob_data = odb_read_object(the_repository->objects, &ce->oid, - &type, &ul); + &type, size); - *size = ul; if (blob_data) { if (type == OBJ_BLOB) return blob_data; @@ -443,7 +441,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 fc3ed8bb1c7a66..a0e6d0b9b35ad9 100644 --- a/environment.c +++ b/environment.c @@ -42,20 +42,13 @@ 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,20 +67,15 @@ 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 #define PROTECT_HFS_DEFAULT 0 #endif -int protect_hfs = PROTECT_HFS_DEFAULT; #ifndef PROTECT_NTFS_DEFAULT #define PROTECT_NTFS_DEFAULT 1 #endif -int protect_ntfs = PROTECT_NTFS_DEFAULT; /* * The character that begins a commented line in user-editable file @@ -100,9 +88,6 @@ int auto_comment_line_char; bool warn_on_auto_comment_char; #endif /* !WITH_BREAKING_CHANGES */ -/* This is set by setup_git_directory_gently() and/or git_default_config() */ -char *git_work_tree_cfg; - /* * Repository-local GIT_* environment variables; see environment.h for details. */ @@ -136,10 +121,31 @@ const char *getenv_safe(struct strvec *argv, const char *name) return argv->v[argv->nr - 1]; } -int is_bare_repository(void) +int is_bare_repository(struct repository *repo) { /* if core.bare is not 'false', let's see if there is a work tree */ - return is_bare_repository_cfg && !repo_get_work_tree(the_repository); + return repo->bare_cfg && !repo_get_work_tree(repo); +} + +int repo_protect_ntfs(struct repository *repo) +{ + return (repo && repo->initialized) ? + repo_config_values(repo)->protect_ntfs : + PROTECT_NTFS_DEFAULT; +} + +int repo_protect_hfs(struct repository *repo) +{ + return (repo && repo->initialized) ? + repo_config_values(repo)->protect_hfs : + PROTECT_HFS_DEFAULT; +} + +int repo_ignore_case(struct repository *repo) +{ + return (repo && repo->initialized) ? + repo_config_values(repo)->ignore_case : + 0; } int have_git_dir(void) @@ -233,6 +239,7 @@ static const struct fsync_component_name { { "pack", FSYNC_COMPONENT_PACK }, { "pack-metadata", FSYNC_COMPONENT_PACK_METADATA }, { "commit-graph", FSYNC_COMPONENT_COMMIT_GRAPH }, + { "diff-hunks", FSYNC_COMPONENT_DIFF_HUNKS }, { "index", FSYNC_COMPONENT_INDEX }, { "objects", FSYNC_COMPONENTS_OBJECTS }, { "reference", FSYNC_COMPONENT_REFERENCE }, @@ -309,16 +316,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); @@ -335,7 +342,7 @@ int git_default_core_config(const char *var, const char *value, } if (!strcmp(var, "core.ignorecase")) { - ignore_case = git_config_bool(var, value); + cfg->ignore_case = git_config_bool(var, value); return 0; } @@ -345,7 +352,7 @@ int git_default_core_config(const char *var, const char *value, } if (!strcmp(var, "core.bare")) { - is_bare_repository_cfg = git_config_bool(var, value); + the_repository->bare_cfg = git_config_bool(var, value); return 0; } @@ -379,7 +386,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 +398,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,22 +538,22 @@ 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; } if (!strcmp(var, "core.protecthfs")) { - protect_hfs = git_config_bool(var, value); + cfg->protect_hfs = git_config_bool(var, value); return 0; } if (!strcmp(var, "core.protectntfs")) { - protect_ntfs = git_config_bool(var, value); + cfg->protect_ntfs = git_config_bool(var, value); return 0; } @@ -556,8 +563,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 +674,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 +715,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; } @@ -720,5 +731,16 @@ void repo_config_values_init(struct repo_config_values *cfg) { cfg->attributes_file = NULL; cfg->apply_sparse_checkout = 0; + cfg->protect_hfs = PROTECT_HFS_DEFAULT; + cfg->protect_ntfs = PROTECT_NTFS_DEFAULT; + cfg->ignore_case = 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 9eb97b3869c9b1..acfb670be107a0 100644 --- a/environment.h +++ b/environment.h @@ -91,6 +91,19 @@ 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; + int protect_hfs; + int protect_ntfs; + int ignore_case; + + /* section "sparse" config values */ + int sparse_expect_files_outside_of_patterns; /* section "branch" config values */ enum branch_track branch_track; @@ -123,20 +136,30 @@ int git_default_config(const char *, const char *, int git_default_core_config(const char *var, const char *value, const struct config_context *ctx, void *cb); +/* + * Getters for the `protect_hfs` and `protect_ntfs` fields of `struct repo_config_values`. + * They check `repo->initialized` to prevent calling `repo_config_values()` + * before the repository setup is fully complete or in non-git environments. + */ +int repo_protect_hfs(struct repository *repo); +int repo_protect_ntfs(struct repository *repo); + +/* + * Getter for the `ignore_case` field of `struct repo_config_values`. + * It checks `repo->initialized` to prevent calling repo_config_values()` + * before the repository setup is fully complete or in non-git environments. + */ +int repo_ignore_case(struct repository *repo); + void repo_config_values_init(struct repo_config_values *cfg); +int is_bare_repository(struct repository *repo); + /* * TODO: All the below state either explicitly or implicitly relies on * `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,38 +170,22 @@ void repo_config_values_init(struct repo_config_values *cfg); * Please do not add new global config variables here. */ # ifdef USE_THE_REPOSITORY_VARIABLE + /* * Returns true iff we have a configured git repository (either via * setup_git_directory, or in the environment via $GIT_DIR). */ int have_git_dir(void); -extern int is_bare_repository_cfg; -int is_bare_repository(void); -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-pack.c b/fetch-pack.c index 120e01f3cf2674..29c41132ee0495 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -662,8 +662,8 @@ static int mark_complete_oid(const struct reference *ref, void *cb_data UNUSED) static void mark_recent_complete_commits(struct fetch_pack_args *args, timestamp_t cutoff) { - while (complete.nr) { - struct commit *item = prio_queue_peek(&complete); + struct commit *item; + while ((item = prio_queue_peek(&complete))) { if (item->date < cutoff) break; print_verbose(args, _("Marking %s as complete"), diff --git a/fmt-merge-msg.c b/fmt-merge-msg.c index 45d8b20e970328..14441f23ae7b3b 100644 --- a/fmt-merge-msg.c +++ b/fmt-merge-msg.c @@ -528,11 +528,11 @@ static void fmt_merge_msg_sigs(struct strbuf *out) for (i = 0; i < origins.nr; i++) { struct object_id *oid = origins.items[i].util; enum object_type type; - unsigned long size; + size_t size; char *buf = odb_read_object(the_repository->objects, oid, &type, &size); char *origbuf = buf; - unsigned long len = size; + size_t len = size; struct signature_check sigc = { NULL }; struct strbuf payload = STRBUF_INIT, sig = STRBUF_INIT; diff --git a/fsck.c b/fsck.c index b4ffee6a043474..94c8651c7dfa28 100644 --- a/fsck.c +++ b/fsck.c @@ -1328,7 +1328,7 @@ static int fsck_blobs(struct oidset *blobs_found, struct oidset *blobs_done, oidset_iter_init(blobs_found, &iter); while ((oid = oidset_iter_next(&iter))) { enum object_type type; - unsigned long size; + size_t size; char *buf; if (oidset_contains(blobs_done, oid)) diff --git a/fsmonitor.c b/fsmonitor.c index d07dc18967ae33..107767527ebec7 100644 --- a/fsmonitor.c +++ b/fsmonitor.c @@ -453,7 +453,7 @@ static void fsmonitor_refresh_callback(struct index_state *istate, char *name) * case-insensitive file system, try again using the name-hash * and dir-name-hash. */ - if (!nr_in_cone && ignore_case) { + if (!nr_in_cone && repo_ignore_case(the_repository)) { nr_in_cone = handle_using_name_hash_icase(istate, name); if (!nr_in_cone) nr_in_cone = handle_using_dir_name_hash_icase( diff --git a/git-gui/Makefile b/git-gui/Makefile index ca01068810bbe6..2e1711adc5a509 100644 --- a/git-gui/Makefile +++ b/git-gui/Makefile @@ -64,12 +64,12 @@ REMOVE_F0 = $(RM_RF) # space is required here REMOVE_F1 = CLEAN_DST = true +ifneq ($(findstring s,$(firstword -$(MAKEFLAGS))),s) ifndef V QUIET = @ QUIET_GEN = $(QUIET)echo ' ' GEN '$@' && QUIET_INDEX = $(QUIET)echo ' ' INDEX $(dir $@) && - QUIET_MSGFMT0 = $(QUIET)printf ' MSGFMT %12s ' $@ && v=` - QUIET_MSGFMT1 = 2>&1` && echo "$$v" | sed -e 's/fuzzy translations/fuzzy/' | sed -e 's/ messages*//g' + QUIET_MSGFMT = $(QUIET)echo ' ' MSGFMT '$@' && INSTALL_D0 = dir= INSTALL_D1 = && echo ' ' DEST $$dir && $(INSTALL) -d -m 755 "$$dir" @@ -89,6 +89,7 @@ ifndef V REMOVE_F0 = dst= REMOVE_F1 = && echo ' ' REMOVE `basename "$$dst"` && $(RM_RF) "$$dst" endif +endif TCLTK_PATH ?= wish ifeq (./,$(dir $(TCLTK_PATH))) @@ -97,10 +98,6 @@ else 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)) @@ -157,7 +154,7 @@ $(PO_TEMPLATE): $(SCRIPT_SH) $(ALL_LIBFILES) update-po:: $(PO_TEMPLATE) $(foreach p, $(ALL_POFILES), echo Updating $p ; msgmerge -U $p $(PO_TEMPLATE) ; ) $(ALL_MSGFILES): %.msg : %.po - $(QUIET_MSGFMT0)$(MSGFMT) --statistics --tcl -l $(basename $(notdir $<)) -d $(dir $@) $< $(QUIET_MSGFMT1) + $(QUIET_MSGFMT)$(MSGFMT) --tcl -l $(basename $(notdir $<)) -d $(dir $@) $< lib/tclIndex: $(ALL_LIBFILES) generate-tclindex.sh GIT-GUI-BUILD-OPTIONS $(QUIET_INDEX)$(SHELL_PATH) generate-tclindex.sh . ./GIT-GUI-BUILD-OPTIONS $(ALL_LIBFILES) diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index 23fe76e498bd17..15dd2b3a84ccd1 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -372,8 +372,8 @@ if {[tk windowingsystem] eq "aqua"} { 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 get_config {name} { } 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 @@ proc load_current_branch {} { 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 @@ proc load_config {include_global} { ## ## 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.sh}} { 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 @@ blame { disable_option multicommit disable_option branch disable_option transport + disable_option picker } citool { enable_option singlecommit @@ -1077,6 +1064,7 @@ citool { 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 @@ citool { set argv [lrange $argv 1 end] } } +pick { + disable_option gitdir_discovery +} } ###################################################################### @@ -1122,26 +1113,138 @@ unset argv0dir ## ## 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 @@ if {$hashalgorithm eq "sha1"} { 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 @@ if {[lindex $_reponame end] eq {.git}} { set _reponame [lindex $_reponame end] } -set env(GIT_DIR) $_gitdir -set env(GIT_WORK_TREE) $_gitworktree - ###################################################################### ## ## global init @@ -2007,7 +2064,6 @@ proc incr_font_size {font {amt 1}} { 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 @@ proc do_gitk {revs {is_submodule false}} { 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 @@ proc do_gitk {revs {is_submodule false}} { # 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 @@ proc do_git_gui {} { 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 @@ proc normalize_relpath {path} { } 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 @@ switch -- $subcommand { browser - blame { if {$subcommand eq "blame"} { - set subcommand_args {[--line=] rev? path} + set subcommand_args {[--line=] [rev] [--] } + set required_pathtype blob } else { - set subcommand_args {rev? path} + set subcommand_args {[rev] [--] } + 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 7e1462a20c59d9..4b06afee93f9a7 100644 --- a/git-gui/lib/choose_repository.tcl +++ b/git-gui/lib/choose_repository.tcl @@ -15,7 +15,7 @@ field w_recentlist ; # Listbox containing recent repositories 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 @@ constructor pick {} { if {$top eq {.}} { eval destroy [winfo children $top] } + + return $pick_ok } method _center {} { @@ -327,8 +329,7 @@ method _git_init {} { } _append_recentrepos [pwd] - set ::_gitdir .git - set ::_prefix {} + set pick_ok 1 return 1 } @@ -409,6 +410,7 @@ method _do_new2 {} { if {![_git_init $this]} { return } + set pick_ok 1 set done 1 } @@ -621,7 +623,7 @@ method _do_clone2 {} { } tkwait variable @done - if {!$clone_ok} { + if {!$pick_ok} { error_popup [mc "Clone failed."] return } @@ -632,18 +634,12 @@ method _do_clone2_done {ok} { 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 @@ method _do_open2 {} { } _append_recentrepos [pwd] - set ::_gitdir $actualgit - set ::_prefix {} + set pick_ok 1 set done 1 } diff --git a/git-gui/lib/option.tcl b/git-gui/lib/option.tcl index 487d70691d77d0..43f79ff812ce48 100644 --- a/git-gui/lib/option.tcl +++ b/git-gui/lib/option.tcl @@ -155,7 +155,7 @@ proc do_options {} { {i-0..300 gui.blamehistoryctx {mc "Blame History Context Radius (days)"}} {i-1..99 gui.diffcontext {mc "Number of Diff Context Lines"}} {t gui.diffopts {mc "Additional Diff Parameters"}} - {i-0..99 gui.commitmsgwidth {mc "Commit Message Text Width"}} + {i-0..9999 gui.commitmsgwidth {mc "Commit Message Text Width"}} {t gui.newbranchtemplate {mc "New Branch Name Template"}} {c gui.encoding {mc "Default File Contents Encoding"}} {b gui.warndetachedcommit {mc "Warn before committing to a detached head"}} diff --git a/git-gui/po/bg.po b/git-gui/po/bg.po index ff5adb25cce612..472863da3b10ee 100644 --- a/git-gui/po/bg.po +++ b/git-gui/po/bg.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: git-gui master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-07-22 17:37+0200\n" -"PO-Revision-Date: 2026-01-19 10:12+0100\n" +"POT-Creation-Date: 2026-06-02 19:13+0200\n" +"PO-Revision-Date: 2026-06-14 22:57+0200\n" "Last-Translator: Alexander Shopov \n" "Language-Team: Bulgarian \n" "Language: bg\n" @@ -43,18 +43,21 @@ msgstr "Прекалено ниска версия на git, необходим msgid "git returned:" msgstr "git върна:" +msgid "Invalid configuration:" +msgstr "Неправилна настройка:" + +msgid "Unusable repo/worktree:" +msgstr "Неизползваемо хранилище/раб. дърво:" + msgid "Git directory not found:" msgstr "Директорията на Git не е открита:" -msgid "Cannot move to top of working directory:" -msgstr "Не може да се премине към родителската директория." +msgid "No working directory" +msgstr "Работната директория липсва" msgid "Cannot use bare repository:" msgstr "Голо хранилище не може да се използва:" -msgid "No working directory" -msgstr "Работната директория липсва" - msgid "Refreshing file status..." msgstr "Обновяване на състоянието на файла…" @@ -305,12 +308,21 @@ msgstr "употреба:" msgid "Usage" msgstr "Употреба" -msgid "Error" -msgstr "Грешка" +#, tcl-format +msgid "fatal: no such file '%s' in worktree" +msgstr "фатална грешка: в работното дърво липсва файл „%s“" + +#, tcl-format +msgid "fatal: '%s' is not a valid rev'" +msgstr "фатална грешка: „%s“ не е версия" + +#, tcl-format +msgid "'%s' is not a directory in rev '%s'" +msgstr "„%s“ не е директория във версия „%s“" #, tcl-format -msgid "fatal: cannot stat path %s: No such file or directory" -msgstr "ФАТАЛНА ГРЕШКА: пътят „%s“ липсва: такъв файл или директория няма" +msgid "'%s' is not a filename in rev '%s'" +msgstr "„%s“ не е име на файл във версия „%s“" msgid "Current Branch:" msgstr "Текущ клон:" @@ -461,6 +473,9 @@ msgstr "Анотиране на родителското подаване" msgid "Reading %s..." msgstr "Чете се „%s“…" +msgid "Error" +msgstr "Грешка" + msgid "Loading copy/move tracking annotations..." msgstr "Зареждане на анотациите за проследяване на копирането/преместването…" diff --git a/git-zlib.c b/git-zlib.c index b91cb323aee916..d21adb3bf5b15e 100644 --- a/git-zlib.c +++ b/git-zlib.c @@ -38,12 +38,17 @@ static inline uInt zlib_buf_cap(unsigned long len) return (ZLIB_BUF_MAX < len) ? ZLIB_BUF_MAX : len; } +static inline uLong zlib_uLong_cap(size_t s) +{ + return s < ULONG_MAX_VALUE ? (uLong)s : ULONG_MAX_VALUE; +} + static void zlib_pre_call(git_zstream *s) { s->z.next_in = s->next_in; s->z.next_out = s->next_out; - s->z.total_in = (uLong)(s->total_in & ULONG_MAX_VALUE); - s->z.total_out = (uLong)(s->total_out & ULONG_MAX_VALUE); + s->z.total_in = zlib_uLong_cap(s->total_in); + s->z.total_out = zlib_uLong_cap(s->total_out); s->z.avail_in = zlib_buf_cap(s->avail_in); s->z.avail_out = zlib_buf_cap(s->avail_out); } @@ -60,7 +65,7 @@ static void zlib_post_call(git_zstream *s, int status) * We track our own totals and verify only the low bits match. */ if ((s->z.total_out & ULONG_MAX_VALUE) != - ((s->total_out + bytes_produced) & ULONG_MAX_VALUE)) + ((zlib_uLong_cap(s->total_out) + bytes_produced) & ULONG_MAX_VALUE)) BUG("total_out mismatch"); /* * zlib does not update total_in when it returns Z_NEED_DICT, @@ -68,7 +73,7 @@ static void zlib_post_call(git_zstream *s, int status) */ if (status != Z_NEED_DICT && (s->z.total_in & ULONG_MAX_VALUE) != - ((s->total_in + bytes_consumed) & ULONG_MAX_VALUE)) + ((zlib_uLong_cap(s->total_in) + bytes_consumed) & ULONG_MAX_VALUE)) BUG("total_in mismatch"); s->total_out += bytes_produced; diff --git a/git.c b/git.c index 36f08891ef5476..cb149e4b4e96ec 100644 --- a/git.c +++ b/git.c @@ -255,7 +255,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) *envchanged = 1; } else if (!strcmp(cmd, "--bare")) { char *cwd = xgetcwd(); - is_bare_repository_cfg = 1; + startup_info->force_bare_repository = true; setenv(GIT_DIR_ENVIRONMENT, cwd, 0); free(cwd); setenv(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, "0", 1); @@ -306,7 +306,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) } else if (!strcmp(cmd, "--shallow-file")) { (*argv)++; (*argc)--; - set_alternate_shallow_file(the_repository, (*argv)[0], 1); + setenv(GIT_SHALLOW_FILE_ENVIRONMENT, (*argv)[0], 1); if (envchanged) *envchanged = 1; } else if (!strcmp(cmd, "-C")) { @@ -566,6 +566,7 @@ static struct cmd_struct commands[] = { { "diagnose", cmd_diagnose, RUN_SETUP_GENTLY }, { "diff", cmd_diff, NO_PARSEOPT }, { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT }, + { "diff-hunks", cmd_diff_hunks, RUN_SETUP }, { "diff-index", cmd_diff_index, RUN_SETUP | NO_PARSEOPT }, { "diff-pairs", cmd_diff_pairs, RUN_SETUP | NO_PARSEOPT }, { "diff-tree", cmd_diff_tree, RUN_SETUP | NO_PARSEOPT }, diff --git a/gitk-git/Makefile b/gitk-git/Makefile index 41116d8a146f26..dd87f501e56868 100644 --- a/gitk-git/Makefile +++ b/gitk-git/Makefile @@ -43,9 +43,12 @@ PO_TEMPLATE = po/gitk.pot ALL_POFILES = $(wildcard po/*.po) ALL_MSGFILES = $(subst .po,.msg,$(ALL_POFILES)) +ifneq ($(findstring s,$(firstword -$(MAKEFLAGS))),s) ifndef V QUIET = @ QUIET_GEN = $(QUIET)echo ' ' GEN $@ && + QUIET_MSGFMT = $(QUIET)echo ' ' MSGFMT $@ && +endif endif all:: gitk-wish $(ALL_MSGFILES) @@ -75,8 +78,7 @@ update-po:: $(PO_TEMPLATE) echo; \ echo " git config filter.gettext-no-location.clean \"msgcat --no-location -\"" $(ALL_MSGFILES): %.msg : %.po - @echo Generating catalog $@ - $(MSGFMT) --statistics --tcl -l $(basename $(notdir $<)) -d $(dir $@) $< + $(QUIET_MSGFMT)$(MSGFMT) --tcl -l $(basename $(notdir $<)) -d $(dir $@) $< .PHONY: all install uninstall clean update-po .PHONY: FORCE diff --git a/gitk-git/gitk b/gitk-git/gitk index 27302749669c21..0f3571050bef1c 100755 --- a/gitk-git/gitk +++ b/gitk-git/gitk @@ -2469,7 +2469,8 @@ proc makewindow {} { -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 @@ proc makewindow {} { .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 {resizeclistpanes %W %w} lappend bglist $canv $canv2 $canv3 pack .tf.histframe.pwclist -fill both -expand 1 -side left diff --git a/gitk-git/po/bg.po b/gitk-git/po/bg.po index e7e2f873217ce5..3521fdbeb4c40e 100644 --- a/gitk-git/po/bg.po +++ b/gitk-git/po/bg.po @@ -1,15 +1,15 @@ # Bulgarian translation of gitk po-file. -# Copyright (C) 2014, 2015, 2019, 2020, 2024, 2025 Alexander Shopov . +# Copyright (C) 2014, 2015, 2019, 2020, 2024, 2025, 2026 Alexander Shopov . # This file is distributed under the same license as the git package. -# Alexander Shopov , 2014, 2015, 2019, 2020, 2024, 2025. +# Alexander Shopov , 2014, 2015, 2019, 2020, 2024, 2025, 2026. # # msgid "" msgstr "" "Project-Id-Version: Gitk master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-07-22 18:34+0200\n" -"PO-Revision-Date: 2025-07-28 13:38+0200\n" +"POT-Creation-Date: 2026-04-18 17:41+0200\n" +"PO-Revision-Date: 2026-06-14 22:45+0200\n" "Last-Translator: Alexander Shopov \n" "Language-Team: Bulgarian \n" "Language: bg\n" @@ -183,10 +183,10 @@ msgid "New version" msgstr "Нова версия" msgid "Lines of context" -msgstr "Контекст в редове" +msgstr "Редове контекст" msgid "Ignore space change" -msgstr "Празните знаци без значение" +msgstr "Празни знаци без значение" msgid "Line diff" msgstr "Поредови разлики" @@ -954,6 +954,9 @@ msgstr "Показване на локалните промени" msgid "Hide remote refs" msgstr "Скриване на отдалечените указатели" +msgid "Refs to hide (space-separated globs)" +msgstr "Указатели за скриване (шаблони разделени с интервал):" + msgid "Copy commit ID to clipboard" msgstr "Копиране на контролната сума към буфера за обмен" @@ -999,14 +1002,23 @@ msgstr "Избор…" msgid "Web browser" msgstr "Уеб браузър" -msgid "Colors: press to choose" -msgstr "Цветове: избира се с натискане" +msgid "Themes - change requires restart" +msgstr "Теми — за прилагане трябва да рестартирате програмата" -msgid "Interface" -msgstr "Интерфейс" +msgid "Theme to use after restart" +msgstr "Тема за прилагане след рестартиране на програмата" -msgid "interface" -msgstr "интерфейс" +msgid "Theme definition file" +msgstr "Файл с тема" + +msgid "The theme definition file may affect all themes." +msgstr "Файлът с тема може да повлияе на всички теми." + +msgid "Apply theme" +msgstr "Прилагане на тема" + +msgid "Colors: press to choose" +msgstr "Цветове: избира се с натискане" msgid "Background" msgstr "Фон" @@ -1059,6 +1071,12 @@ msgstr "фон на отбелязан ред" msgid "Select bg" msgstr "Избор на фон" +msgid "Link" +msgstr "Връзка" + +msgid "link" +msgstr "връзка" + msgid "Fonts: press to choose" msgstr "Шрифтове: избира се с натискане" @@ -1083,6 +1101,9 @@ msgstr "Цветове" msgid "Fonts" msgstr "Шрифтове" +msgid "Gitk: select theme definition" +msgstr "Gitk: избор на тема" + #, tcl-format msgid "Gitk: choose color for %s" msgstr "Gitk: избор на цвят на „%s“" diff --git a/gitk-git/po/es.po b/gitk-git/po/es.po index 25260c6571c1c3..881738e9e6af1f 100644 --- a/gitk-git/po/es.po +++ b/gitk-git/po/es.po @@ -8,30 +8,30 @@ msgid "" msgstr "" "Project-Id-Version: Gitk\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-05-17 14:32+1000\n" -"PO-Revision-Date: 2008-03-25 11:20+0100\n" -"Last-Translator: Santiago Gala \n" +"POT-Creation-Date: 2026-04-18 18:45+0200\n" +"PO-Revision-Date: 2026-04-19 20:18+0200\n" +"Last-Translator: Luis B \n" "Language-Team: Spanish\n" -"Language: \n" +"Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.4.2\n" msgid "Couldn't get list of unmerged files:" msgstr "Imposible obtener la lista de archivos pendientes de fusión:" msgid "Color words" -msgstr "" +msgstr "Colorear palabras" msgid "Markup words" -msgstr "" +msgstr "Marcar cambios por palabras" -#, fuzzy msgid "Error parsing revisions:" -msgstr "Error al leer las diferencias de fusión:" +msgstr "Error procesando revisiones:" msgid "Error executing --argscmd command:" -msgstr "" +msgstr "Error ejecutando comando --argscmd:" msgid "No files selected: --merge specified but no files are unmerged." msgstr "" @@ -45,9 +45,8 @@ msgstr "" "No hay archivos seleccionados: se seleccionó la opción --merge pero los " "archivos especificados no necesitan fusión." -#, fuzzy msgid "Error executing git log:" -msgstr "Error al crear la etiqueta:" +msgstr "Error ejecutando git log:" msgid "Reading" msgstr "Leyendo" @@ -74,19 +73,19 @@ msgid "Cancel" msgstr "Cancelar" msgid "&Update" -msgstr "Actualizar" +msgstr "Act&ualizar" msgid "&Reload" -msgstr "" +msgstr "&Recargar" msgid "Reread re&ferences" -msgstr "Releer referencias" +msgstr "Releer re&ferencias" msgid "&List references" -msgstr "Lista de referencias" +msgstr "Mostrar &lista de referencias" msgid "Start git &gui" -msgstr "" +msgstr "Ejecutar 'git &gui'" msgid "&Quit" msgstr "Salir" @@ -124,11 +123,11 @@ msgstr "Combinaciones de teclas" msgid "&Help" msgstr "Ayuda" -msgid "SHA1 ID:" -msgstr "SHA1 ID:" +msgid "Commit ID:" +msgstr "Commit ID:" msgid "Row" -msgstr "" +msgstr "Fila" msgid "Find" msgstr "Buscar" @@ -146,7 +145,7 @@ msgid "adding/removing string:" msgstr "que añade/elimina cadena:" msgid "changing lines matching:" -msgstr "" +msgstr "que cambia líneas que coinciden con:" msgid "Exact" msgstr "Exacto" @@ -170,7 +169,7 @@ msgid "Author" msgstr "Autor" msgid "Committer" -msgstr "" +msgstr "Comiteador" msgid "Search" msgstr "Buscar" @@ -188,10 +187,10 @@ msgid "Lines of context" msgstr "Líneas de contexto" msgid "Ignore space change" -msgstr "Ignora cambios de espaciado" +msgstr "Ignorar cambios de espaciado" msgid "Line diff" -msgstr "" +msgstr "Diferencia de líneas" msgid "Patch" msgstr "Parche" @@ -199,6 +198,9 @@ msgstr "Parche" msgid "Tree" msgstr "Árbol" +msgid "Unknown windowing system, cannot bind mouse" +msgstr "Sistema de ventanas desconocido, no se puede asociar el ratón" + msgid "Diff this -> selected" msgstr "Diferencia de esta -> seleccionada" @@ -206,11 +208,14 @@ msgid "Diff selected -> this" msgstr "Diferencia de seleccionada -> esta" msgid "Make patch" -msgstr "Crear patch" +msgstr "Crear parche" msgid "Create tag" msgstr "Crear etiqueta" +msgid "Copy commit reference" +msgstr "Copiar referencia de la revisión" + msgid "Write commit to file" msgstr "Escribir revisiones a archivo" @@ -223,39 +228,38 @@ msgstr "Añadir esta revisión a la rama actual (cherry-pick)" msgid "Reset HEAD branch to here" msgstr "Traer la rama HEAD aquí" -#, fuzzy msgid "Mark this commit" -msgstr "Añadir esta revisión a la rama actual (cherry-pick)" +msgstr "Marcar esta revisión" msgid "Return to mark" -msgstr "" +msgstr "Volver a revisión marcada" msgid "Find descendant of this and mark" -msgstr "" +msgstr "Encontrar primer descendiente de esta y revisión marcada" msgid "Compare with marked commit" -msgstr "" +msgstr "Comparar con revisión marcada" -#, fuzzy msgid "Diff this -> marked commit" -msgstr "Diferencia de esta -> seleccionada" +msgstr "Diferencia de esta -> revisión marcada" -#, fuzzy msgid "Diff marked commit -> this" -msgstr "Diferencia de seleccionada -> esta" +msgstr "Diferencia de revisión marcada -> esta" -#, fuzzy msgid "Revert this commit" -msgstr "Añadir esta revisión a la rama actual (cherry-pick)" +msgstr "Revertir este commit" msgid "Check out this branch" msgstr "Cambiar a esta rama" +msgid "Rename this branch" +msgstr "Renombrar esta rama" + msgid "Remove this branch" msgstr "Eliminar esta rama" msgid "Copy branch name" -msgstr "" +msgstr "Copiar nombre de rama" msgid "Highlight this too" msgstr "Seleccionar también" @@ -264,21 +268,23 @@ msgid "Highlight this only" msgstr "Seleccionar sólo" msgid "External diff" -msgstr "" +msgstr "Comparador de diferencias externo" msgid "Blame parent commit" -msgstr "" +msgstr "Ejecutar git blame en el commit padre" msgid "Copy path" -msgstr "" +msgstr "Copiar ruta" msgid "Show origin of this line" -msgstr "" +msgstr "Mostrar origen de esta línea" msgid "Run git gui blame on this line" -msgstr "" +msgstr "Ejecutar git gui blame en esta línea" + +msgid "About gitk" +msgstr "Acerca de gitk" -#, fuzzy msgid "" "\n" "Gitk - a commit viewer for git\n" @@ -290,7 +296,7 @@ msgstr "" "\n" "Gitk - un visualizador de revisiones para git\n" "\n" -"Copyright \\u00a9 2005-2016 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Uso y redistribución permitidos según los términos de la Licencia Pública " "General de GNU (GNU GPL)" @@ -308,9 +314,9 @@ msgstr "Combinaciones de tecla de Gitk:" msgid "<%s-Q>\t\tQuit" msgstr "<%s-Q>\t\tSalir" -#, fuzzy, tcl-format +#, tcl-format msgid "<%s-W>\t\tClose window" -msgstr "<%s-F>\t\tBuscar" +msgstr "<%s-W>\t\tCerrar ventana" msgid "\t\tMove to first commit" msgstr "\t\tIr a la primera revisión" @@ -318,24 +324,21 @@ msgstr "\t\tIr a la primera revisión" msgid "\t\tMove to last commit" msgstr "\t\tIr a la última revisión" -#, fuzzy msgid ", p, k\tMove up one commit" -msgstr ", p, i\tSubir una revisión" +msgstr ", p, k\tSubir una revisión" -#, fuzzy msgid ", n, j\tMove down one commit" -msgstr ", n, k\tBajar una revisión" +msgstr ", n, j\tBajar una revisión" -#, fuzzy msgid ", z, h\tGo back in history list" -msgstr ", z, j\tRetroceder en la historia" +msgstr ", z, h\tRetroceder en la historia" msgid ", x, l\tGo forward in history list" msgstr ", x, l\tAvanzar en la historia" #, tcl-format msgid "<%s-n>\tGo to n-th parent of current commit in history list" -msgstr "" +msgstr "<%s-n>\tIr al padre número N del commit actual" msgid "\tMove up one page in commit list" msgstr "\tSubir una página en la lista de revisiones" @@ -399,12 +402,11 @@ msgstr "<%s-G>\t\tBuscar el siguiente" msgid "\tMove to next find hit" msgstr "\tBuscar el siguiente" -#, fuzzy msgid "g\t\tGo to commit" -msgstr "\t\tIr a la última revisión" +msgstr "g\t\tIr a la revisión" msgid "/\t\tFocus the search box" -msgstr "" +msgstr "/\t\tFoco en el cuadro de búsqueda" msgid "?\t\tMove to previous find hit" msgstr "?\t\tBuscar el anterior" @@ -439,51 +441,54 @@ msgstr "<%s-minus>\tDisminuir tamaño del texto" msgid "\t\tUpdate" msgstr "\t\tActualizar" -#, fuzzy, tcl-format +#, tcl-format msgid "Error creating temporary directory %s:" -msgstr "Error en la creación del parche:" +msgstr "Error creando directorio temporal %s:" -#, fuzzy, tcl-format +#, tcl-format msgid "Error getting \"%s\" from %s:" -msgstr "Error al leer las diferencias de fusión:" +msgstr "Error obteniendo \"%s\" de %s:" -#, fuzzy msgid "command failed:" -msgstr "Línea de comandos" +msgstr "el comando falló:" -#, fuzzy msgid "No such commit" -msgstr "No se han guardado cambios" +msgstr "No existe el commit" msgid "git gui blame: command failed:" -msgstr "" +msgstr "git gui blame: error de ejecución:" #, tcl-format msgid "Couldn't read merge head: %s" -msgstr "" +msgstr "No se pudo leer merge head: %s" -#, fuzzy, tcl-format +#, tcl-format msgid "Error reading index: %s" -msgstr "Error al crear la etiqueta:" +msgstr "Error leyendo índice:%s" #, tcl-format msgid "Couldn't start git blame: %s" -msgstr "" +msgstr "No se pudo iniciar git blame: %s" msgid "Searching" msgstr "Buscando" -#, fuzzy, tcl-format +#, tcl-format msgid "Error running git blame: %s" -msgstr "Error al crear la etiqueta:" +msgstr "Error al ejecutar git blame: %s" #, tcl-format msgid "That line comes from commit %s, which is not in this view" -msgstr "" +msgstr "Esa línea proviene del commit %s, que no está en esta vista" -#, fuzzy msgid "External diff viewer failed:" -msgstr "f\t\tDesplazar la vista de diferencias al archivo siguiente" +msgstr "Visor de diferencias externo falló:" + +msgid "All files" +msgstr "Todos los archivos" + +msgid "View" +msgstr "Vista" msgid "Gitk view definition" msgstr "Definición de vistas de Gitk" @@ -492,96 +497,93 @@ msgid "Remember this view" msgstr "Recordar esta vista" msgid "References (space separated list):" -msgstr "" +msgstr "Referencias (lista separada por espacios):" msgid "Branches & tags:" -msgstr "" +msgstr "Ramas y etiquetas:" -#, fuzzy msgid "All refs" -msgstr "Todos los archivos" +msgstr "Todas las referencias" msgid "All (local) branches" -msgstr "" +msgstr "Todas las ramas (locales)" msgid "All tags" -msgstr "" +msgstr "Todas las etiquetas" msgid "All remote-tracking branches" -msgstr "" +msgstr "Todas las ramas remotas" msgid "Commit Info (regular expressions):" -msgstr "" +msgstr "Información del commit (expresiones regulares)" -#, fuzzy msgid "Author:" -msgstr "Autor" +msgstr "Autor:" -#, fuzzy msgid "Committer:" -msgstr "revisión" +msgstr "Comiteador:" msgid "Commit Message:" -msgstr "" +msgstr "Mensaje del commit:" msgid "Matches all Commit Info criteria" -msgstr "" +msgstr "Cumple todos los criterios de 'Información del commit'" msgid "Matches no Commit Info criteria" -msgstr "" +msgstr "Cumple ningún criterio de 'Información del commit'" msgid "Changes to Files:" -msgstr "" +msgstr "Cambios en ficheros:" msgid "Fixed String" -msgstr "" +msgstr "Texto exacto" msgid "Regular Expression" -msgstr "" +msgstr "Expresión regular" -#, fuzzy msgid "Search string:" -msgstr "Buscando" +msgstr "Buscar cadena:" +# Notice git log since and until format msgid "" "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" msgstr "" +"Fechas del commit (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, " +"2009 15:27:38\"):" msgid "Since:" -msgstr "" +msgstr "Desde:" msgid "Until:" -msgstr "" +msgstr "Hasta:" msgid "Limit and/or skip a number of revisions (positive integer):" -msgstr "" +msgstr "Limitar y/o saltar las siguientes revisiones (número positivo)" msgid "Number to show:" -msgstr "" +msgstr "Limitar a n revisiones:" msgid "Number to skip:" -msgstr "" +msgstr "Saltar n revisiones:" msgid "Miscellaneous options:" -msgstr "" +msgstr "Opciones varias:" msgid "Strictly sort by date" -msgstr "" +msgstr "Ordenar estrictamente por fecha" msgid "Mark branch sides" -msgstr "" +msgstr "Marcar lados de ramas" -#, fuzzy msgid "Limit to first parent" -msgstr "Limitar las diferencias a las rutas seleccionadas" +msgstr "Limitar al primer padre" msgid "Simple history" -msgstr "" +msgstr "Historia simple" -#, fuzzy msgid "Additional arguments to git log:" -msgstr "Revisiones a incluir (argumentos a git log):" +msgstr "Argumentos extra a git log:" msgid "Enter files and directories to include, one per line:" msgstr "Introducir archivos y directorios a incluir, uno por línea:" @@ -590,17 +592,16 @@ msgid "Command to generate more commits to include:" msgstr "Comando que genera más revisiones a incluir:" msgid "Gitk: edit view" -msgstr "" +msgstr "Gitk: editar vista" msgid "-- criteria for selecting revisions" -msgstr "" +msgstr "-- criterios para filtrar revisiones" -#, fuzzy msgid "View Name" -msgstr "Vista" +msgstr "Nombre de la vista" msgid "Apply (F5)" -msgstr "" +msgstr "Aplicar (F5)" msgid "Error in commit selection arguments:" msgstr "Error en los argumentos de selección de las revisiones:" @@ -626,11 +627,14 @@ msgstr "Cambios locales añadidos al índice pero sin completar revisión" msgid "Local uncommitted changes, not checked in to index" msgstr "Cambios locales sin añadir al índice" +msgid "Error starting web browser:" +msgstr "Error al arrancar navegador web:" + msgid "and many more" -msgstr "" +msgstr "y muchos más" msgid "many" -msgstr "" +msgstr "muchos" msgid "Tags:" msgstr "Etiquetas:" @@ -650,28 +654,28 @@ msgstr "Sigue-a" msgid "Precedes" msgstr "Precede-a" -#, fuzzy, tcl-format +#, tcl-format msgid "Error getting diffs: %s" -msgstr "Error al leer las diferencias de fusión:" +msgstr "Error al leer las diferencias: %s" msgid "Goto:" msgstr "Ir a:" #, tcl-format -msgid "Short SHA1 id %s is ambiguous" -msgstr "La id SHA1 abreviada %s es ambigua" +msgid "Short commit ID %s is ambiguous" +msgstr "El id SHA1 abreviado %s es ambiguo" -#, fuzzy, tcl-format +#, tcl-format msgid "Revision %s is not known" -msgstr "La id SHA1 %s es desconocida" +msgstr "Revisión %s es desconocida" #, tcl-format -msgid "SHA1 id %s is not known" -msgstr "La id SHA1 %s es desconocida" +msgid "Commit ID %s is not known" +msgstr "El id del commit %s es desconocido" #, tcl-format msgid "Revision %s is not in the current view" -msgstr "" +msgstr "La revisión %s no está en la vista actual" msgid "Date" msgstr "Fecha" @@ -681,51 +685,55 @@ msgstr "Hijas" #, tcl-format msgid "Reset %s branch to here" -msgstr "Poner la rama %s en esta revisión" +msgstr "Resetear la rama %s en esta revisión" msgid "Detached head: can't reset" -msgstr "" +msgstr "En estado 'detached head': no se puede resetear la rama" msgid "Skipping merge commit " -msgstr "" +msgstr "Omitiendo el commit de merge " -#, fuzzy msgid "Error getting patch ID for " -msgstr "Error en la creación del parche:" +msgstr "Error obteniendo ID del parche para " msgid " - stopping\n" -msgstr "" +msgstr " - deteniendo\n" -#, fuzzy msgid "Commit " -msgstr "revisión" +msgstr "Revisión " msgid "" " is the same patch as\n" " " msgstr "" +" es el mismo parche que\n" +" " msgid "" " differs from\n" " " msgstr "" +" difiere de\n" +" " msgid "" "Diff of commits:\n" "\n" msgstr "" +"Diferencia de commits:\n" +"\n" #, tcl-format msgid " has %s children - stopping\n" -msgstr "" +msgstr " tiene %s hijos - parando\n" -#, fuzzy, tcl-format +#, tcl-format msgid "Error writing commit to file: %s" -msgstr "Error al escribir revisión:" +msgstr "Error al escribir revisión al fichero: %s" -#, fuzzy, tcl-format +#, tcl-format msgid "Error diffing commits: %s" -msgstr "Error al escribir revisión:" +msgstr "Error comparando revisiones: %s" msgid "Top" msgstr "Origen" @@ -764,11 +772,10 @@ msgid "Tag name:" msgstr "Nombre de etiqueta:" msgid "Tag message is optional" -msgstr "" +msgstr "El mensaje de la etiqueta es opcional" -#, fuzzy msgid "Tag message:" -msgstr "Nombre de etiqueta:" +msgstr "Mensaje de la etiqueta:" msgid "Create" msgstr "Crear" @@ -792,15 +799,28 @@ msgstr "Escribir" msgid "Error writing commit:" msgstr "Error al escribir revisión:" +msgid "Create branch" +msgstr "Crear nueva rama" + +#, tcl-format +msgid "Rename branch %s" +msgstr "Renombrar rama '%s'" + +msgid "Rename" +msgstr "Renombrar" + msgid "Name:" msgstr "Nombre:" msgid "Please specify a name for the new branch" msgstr "Especifique un nombre para la nueva rama" -#, fuzzy, tcl-format +#, tcl-format msgid "Branch '%s' already exists. Overwrite?" -msgstr "La etiqueta \"%s\" ya existe" +msgstr "La rama '%s' ya existe. ¿Sobreescribir?" + +msgid "Please specify a new name for the branch" +msgstr "Especifique un nuevo nombre para la rama" #, tcl-format msgid "Commit %s is already included in branch %s -- really re-apply it?" @@ -814,33 +834,42 @@ msgid "" "Cherry-pick failed because of local changes to file '%s'.\n" "Please commit, reset or stash your changes and try again." msgstr "" +"El comando cherry-pick falló por los cambios locales al fichero '%s'.\n" +"Por favor, comiteé, reseteé o use stash con sus cambios y pruebe de nuevo" msgid "" "Cherry-pick failed because of merge conflict.\n" "Do you wish to run git citool to resolve it?" msgstr "" +"El comando cherry-pick falló debido a un conflicto de fusión.\n" +"¿Desea ejecutar git citool para resolverlo?" msgid "No changes committed" msgstr "No se han guardado cambios" -#, fuzzy, tcl-format +#, tcl-format msgid "Commit %s is not included in branch %s -- really revert it?" -msgstr "La revisión %s ya está incluida en la rama %s -- ¿Volver a aplicarla?" +msgstr "" +"La revisión %s no está incluida en la rama %s -- ¿seguro que quiere " +"revertirla?" -#, fuzzy msgid "Reverting" -msgstr "Reponiendo" +msgstr "Revirtiendo" #, tcl-format msgid "" "Revert failed because of local changes to the following files:%s Please " "commit, reset or stash your changes and try again." msgstr "" +"El comando revert falló por los cambios locales a estos ficheros:%s Por " +"favor, comitéelos, reseteé o use stash y pruebe otra vez" msgid "" "Revert failed because of merge conflict.\n" " Do you wish to run git citool to resolve it?" msgstr "" +"Revert falló por un conflicto de fusión.\n" +"¿Quiere ejecutar git citool para resolverlo?" msgid "Confirm reset" msgstr "Confirmar git reset" @@ -868,6 +897,10 @@ msgstr "" msgid "Resetting" msgstr "Reponiendo" +#, tcl-format +msgid "A local branch named %s exists already" +msgstr "Ya existe una rama local llamada '%s'" + msgid "Checking out" msgstr "Creando copia de trabajo" @@ -889,6 +922,9 @@ msgstr "Etiquetas y ramas: %s" msgid "Filter" msgstr "Filtro" +msgid "Sort refs by type" +msgstr "Ordenar referencias por tipo" + msgid "" "Error reading commit topology information; branch and preceding/following " "tag information will be incomplete." @@ -902,15 +938,6 @@ msgstr "Etiqueta" msgid "Id" msgstr "Id" -msgid "Gitk font chooser" -msgstr "Selector de tipografías gitk" - -msgid "B" -msgstr "B" - -msgid "I" -msgstr "I" - msgid "Commit list display options" msgstr "Opciones de visualización de la lista de revisiones" @@ -924,12 +951,23 @@ msgstr "Ancho máximo del gráfico (en % del panel)" msgid "Show local changes" msgstr "Mostrar cambios locales" -#, fuzzy -msgid "Auto-select SHA1 (length)" -msgstr "Seleccionar automáticamente SHA1 hash" - msgid "Hide remote refs" -msgstr "" +msgstr "Esconder referencias remotas" + +msgid "Refs to hide (space-separated globs)" +msgstr "Referencias a esconder (globs separados por espacios)" + +msgid "Copy commit ID to clipboard" +msgstr "Copiar id de la revisión al portapapeles" + +msgid "Copy commit ID to X11 selection" +msgstr "Copiar id de la revisión a selección X11" + +msgid "Length of commit ID to copy" +msgstr "Longitud del ID de la revisión al copiar" + +msgid "Wheel scrolling multiplier" +msgstr "Multiplicador desplazamiento rueda del ratón" msgid "Diff display options" msgstr "Opciones de visualización de diferencias" @@ -937,92 +975,108 @@ msgstr "Opciones de visualización de diferencias" msgid "Tab spacing" msgstr "Espaciado de tabulador" -#, fuzzy +msgid "Wrap comment text" +msgstr "Ajustar texto de comentarios" + +msgid "Wrap other text" +msgstr "Ajustar otros textos" + msgid "Display nearby tags/heads" -msgstr "Mostrar etiquetas cercanas" +msgstr "Mostrar etiquetas/referencias cercanas" msgid "Maximum # tags/heads to show" -msgstr "" +msgstr "Máximo número de etiquetas/" msgid "Limit diffs to listed paths" msgstr "Limitar las diferencias a las rutas seleccionadas" msgid "Support per-file encodings" -msgstr "" +msgstr "Soporte para codificaciones por archivo" msgid "External diff tool" -msgstr "" +msgstr "Herramienta de comparación externa" msgid "Choose..." -msgstr "" +msgstr "Escoger..." -#, fuzzy -msgid "General options" -msgstr "Generar parche" +msgid "Web browser" +msgstr "Navegador web" -msgid "Use themed widgets" -msgstr "" +msgid "Themes - change requires restart" +msgstr "Temas - los cambios requieren reinicio" -msgid "(change requires restart)" -msgstr "" +msgid "Theme to use after restart" +msgstr "Tema a usar tras reiniciar" -msgid "(currently unavailable)" -msgstr "" +msgid "Theme definition file" +msgstr "Fichero de definición del tema" -msgid "Colors: press to choose" -msgstr "Colores: pulse para seleccionar" +msgid "The theme definition file may affect all themes." +msgstr "El fichero de definición del tema puede afectar a todos los temas." -msgid "Interface" -msgstr "" +msgid "Apply theme" +msgstr "Aplicar tema" -#, fuzzy -msgid "interface" -msgstr "Tipografía para interfaz de usuario" +msgid "Colors: press to choose" +msgstr "Colores: pulse para seleccionar" msgid "Background" msgstr "Fondo" -#, fuzzy msgid "background" -msgstr "Fondo" +msgstr "fondo" msgid "Foreground" msgstr "Primer plano" -#, fuzzy msgid "foreground" -msgstr "Primer plano" +msgstr "primer plano" msgid "Diff: old lines" -msgstr "Diff: líneas viejas" +msgstr "Diff: líneas antiguas" -#, fuzzy msgid "diff old lines" -msgstr "Diff: líneas viejas" +msgstr "diff líneas antiguas" + +msgid "Diff: old lines bg" +msgstr "Diff: fondo de líneas antiguas" + +msgid "diff old lines bg" +msgstr "diff fondo de líneas antiguas" msgid "Diff: new lines" msgstr "Diff: líneas nuevas" -#, fuzzy msgid "diff new lines" -msgstr "Diff: líneas nuevas" +msgstr "diff líneas nuevas" + +msgid "Diff: new lines bg" +msgstr "Diff: fondo de líneas nuevas" + +msgid "diff new lines bg" +msgstr "diff fondo de líneas nuevas" msgid "Diff: hunk header" msgstr "Diff: cabecera de fragmento" -#, fuzzy msgid "diff hunk header" -msgstr "Diff: cabecera de fragmento" +msgstr "diff cabecera de fragmento" msgid "Marked line bg" -msgstr "" +msgstr "Fondo de línea marcada" msgid "marked line background" -msgstr "" +msgstr "fondo de linea marcada" msgid "Select bg" msgstr "Color de fondo de la selección" +msgid "Link" +msgstr "Enlace" + +msgid "link" +msgstr "enlace" + msgid "Fonts: press to choose" msgstr "Tipografías: pulse para elegir" @@ -1038,27 +1092,22 @@ msgstr "Tipografía para interfaz de usuario" msgid "Gitk preferences" msgstr "Preferencias de gitk" -#, fuzzy msgid "General" -msgstr "Generar" +msgstr "General" msgid "Colors" -msgstr "" +msgstr "Colores" msgid "Fonts" -msgstr "" +msgstr "Fuentes" + +msgid "Gitk: select theme definition" +msgstr "Gitk: escoger definición de tema" #, tcl-format msgid "Gitk: choose color for %s" msgstr "Gitk: elegir color para %s" -msgid "" -"Sorry, gitk cannot run with this version of Tcl/Tk.\n" -" Gitk requires at least Tcl/Tk 8.4." -msgstr "" -"Esta versión de Tcl/Tk es demasiado antigua.\n" -" Gitk requiere Tcl/Tk versión 8.4 o superior." - msgid "Cannot find a git repository here." msgstr "No hay un repositorio git aquí." @@ -1070,6 +1119,37 @@ msgstr "" msgid "Bad arguments to gitk:" msgstr "Argumentos incorrectos a Gitk:" +#~ msgid "SHA1 ID:" +#~ msgstr "SHA1 ID:" + +#~ msgid "Gitk font chooser" +#~ msgstr "Selector de tipografías gitk" + +#~ msgid "B" +#~ msgstr "B" + +#~ msgid "I" +#~ msgstr "I" + +#, fuzzy +#~ msgid "Auto-select SHA1 (length)" +#~ msgstr "Seleccionar automáticamente SHA1 hash" + +#, fuzzy +#~ msgid "General options" +#~ msgstr "Generar parche" + +#, fuzzy +#~ msgid "interface" +#~ msgstr "Tipografía para interfaz de usuario" + +#~ msgid "" +#~ "Sorry, gitk cannot run with this version of Tcl/Tk.\n" +#~ " Gitk requires at least Tcl/Tk 8.4." +#~ msgstr "" +#~ "Esta versión de Tcl/Tk es demasiado antigua.\n" +#~ " Gitk requiere Tcl/Tk versión 8.4 o superior." + #~ msgid "SHA1 ID: " #~ msgstr "SHA1 ID: " diff --git a/gpg-interface.c b/gpg-interface.c index dafd5371fa806d..95abf1ef4e1a0c 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -990,21 +990,18 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature, return ret; } -/* - * Strip CR from the line endings, in case we are on Windows. - * NEEDSWORK: make it trim only CRs before LFs and rename - */ -static void remove_cr_after(struct strbuf *buffer, size_t offset) +/* Strip CR before LF from the line endings, in case we are on Windows. */ +static void strip_cr_before_lf(struct strbuf *buffer, size_t offset) { size_t i, j; for (i = j = offset; i < buffer->len; i++) { - if (buffer->buf[i] != '\r') { - if (i != j) - buffer->buf[j] = buffer->buf[i]; - j++; - } + if (buffer->buf[i] == '\r' && + i + 1 < buffer->len && buffer->buf[i + 1] == '\n') + continue; + buffer->buf[j++] = buffer->buf[i]; } + strbuf_setlen(buffer, j); } @@ -1049,8 +1046,8 @@ static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature, } strbuf_release(&gpg_status); - /* Strip CR from the line endings, in case we are on Windows. */ - remove_cr_after(signature, bottom); + /* Strip CR before LF from the line endings, in case we are on Windows. */ + strip_cr_before_lf(signature, bottom); return 0; } @@ -1136,8 +1133,8 @@ static int sign_buffer_ssh(struct strbuf *buffer, struct strbuf *signature, ssh_signature_filename.buf); goto out; } - /* Strip CR from the line endings, in case we are on Windows. */ - remove_cr_after(signature, bottom); + /* Strip CR before LF from the line endings, in case we are on Windows. */ + strip_cr_before_lf(signature, bottom); out: if (key_file) diff --git a/grep.c b/grep.c index a54e5d86a96cfd..1d75d314211aa8 100644 --- a/grep.c +++ b/grep.c @@ -1931,9 +1931,11 @@ void grep_source_clear_data(struct grep_source *gs) static int grep_source_load_oid(struct grep_source *gs) { enum object_type type; + size_t size_st = 0; gs->buf = odb_read_object(gs->repo->objects, gs->identifier, - &type, &gs->size); + &type, &size_st); + gs->size = cast_size_t_to_ulong(size_st); if (!gs->buf) return error(_("'%s': unable to read %s"), gs->name, diff --git a/hash.c b/hash.c index e925b9754e04fc..82f7e2440455ff 100644 --- a/hash.c +++ b/hash.c @@ -72,6 +72,11 @@ static void git_hash_sha1_final_oid(struct object_id *oid, struct git_hash_ctx * oid->algo = GIT_HASH_SHA1; } +static void git_hash_sha1_discard(struct git_hash_ctx *ctx) +{ + git_SHA1_Discard(&ctx->state.sha1); +} + static void git_hash_sha1_init_unsafe(struct git_hash_ctx *ctx) { ctx->algop = unsafe_hash_algo(&hash_algos[GIT_HASH_SHA1]); @@ -102,6 +107,11 @@ static void git_hash_sha1_final_oid_unsafe(struct object_id *oid, struct git_has oid->algo = GIT_HASH_SHA1; } +static void git_hash_sha1_discard_unsafe(struct git_hash_ctx *ctx) +{ + git_SHA1_Discard_unsafe(&ctx->state.sha1_unsafe); +} + static void git_hash_sha256_init(struct git_hash_ctx *ctx) { ctx->algop = unsafe_hash_algo(&hash_algos[GIT_HASH_SHA256]); @@ -135,6 +145,11 @@ static void git_hash_sha256_final_oid(struct object_id *oid, struct git_hash_ctx oid->algo = GIT_HASH_SHA256; } +static void git_hash_sha256_discard(struct git_hash_ctx *ctx) +{ + git_SHA256_Discard(&ctx->state.sha256); +} + static void git_hash_unknown_init(struct git_hash_ctx *ctx UNUSED) { BUG("trying to init unknown hash"); @@ -165,6 +180,11 @@ static void git_hash_unknown_final_oid(struct object_id *oid UNUSED, BUG("trying to finalize unknown hash"); } +static void git_hash_unknown_discard(struct git_hash_ctx *ctx UNUSED) +{ + BUG("trying to discard unknown hash"); +} + static const struct git_hash_algo sha1_unsafe_algo = { .name = "sha1", .format_id = GIT_SHA1_FORMAT_ID, @@ -176,6 +196,7 @@ static const struct git_hash_algo sha1_unsafe_algo = { .update_fn = git_hash_sha1_update_unsafe, .final_fn = git_hash_sha1_final_unsafe, .final_oid_fn = git_hash_sha1_final_oid_unsafe, + .discard_fn = git_hash_sha1_discard_unsafe, .empty_tree = &empty_tree_oid, .empty_blob = &empty_blob_oid, .null_oid = &null_oid_sha1, @@ -193,6 +214,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = { .update_fn = git_hash_unknown_update, .final_fn = git_hash_unknown_final, .final_oid_fn = git_hash_unknown_final_oid, + .discard_fn = git_hash_unknown_discard, .empty_tree = NULL, .empty_blob = NULL, .null_oid = NULL, @@ -208,6 +230,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = { .update_fn = git_hash_sha1_update, .final_fn = git_hash_sha1_final, .final_oid_fn = git_hash_sha1_final_oid, + .discard_fn = git_hash_sha1_discard, .unsafe = &sha1_unsafe_algo, .empty_tree = &empty_tree_oid, .empty_blob = &empty_blob_oid, @@ -224,6 +247,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = { .update_fn = git_hash_sha256_update, .final_fn = git_hash_sha256_final, .final_oid_fn = git_hash_sha256_final_oid, + .discard_fn = git_hash_sha256_discard, .empty_tree = &empty_tree_oid_sha256, .empty_blob = &empty_blob_oid_sha256, .null_oid = &null_oid_sha256, @@ -261,26 +285,47 @@ void git_hash_free(struct git_hash_ctx *ctx) void git_hash_init(struct git_hash_ctx *ctx, const struct git_hash_algo *algop) { algop->init_fn(ctx); + ctx->active = true; } void git_hash_clone(struct git_hash_ctx *dst, const struct git_hash_ctx *src) { + if (!src->active) + BUG("attempt to copy from an inactive hash context"); + if (!dst->active) + BUG("attempt to copy to an inactive hash context"); src->algop->clone_fn(dst, src); } void git_hash_update(struct git_hash_ctx *ctx, const void *in, size_t len) { + if (!ctx->active) + BUG("attempt to update an inactive hash context"); ctx->algop->update_fn(ctx, in, len); } void git_hash_final(unsigned char *hash, struct git_hash_ctx *ctx) { + if (!ctx->active) + BUG("attempt to finalize an inactive hash context"); ctx->algop->final_fn(hash, ctx); + ctx->active = false; } void git_hash_final_oid(struct object_id *oid, struct git_hash_ctx *ctx) { + if (!ctx->active) + BUG("attempt to finalize an inactive hash context"); ctx->algop->final_oid_fn(oid, ctx); + ctx->active = false; +} + +void git_hash_discard(struct git_hash_ctx *ctx) +{ + if (!ctx->active) + return; + ctx->algop->discard_fn(ctx); + ctx->active = false; } uint32_t hash_algo_by_name(const char *name) diff --git a/hash.h b/hash.h index c082a53c9ac93d..cf94ad57002788 100644 --- a/hash.h +++ b/hash.h @@ -37,6 +37,7 @@ # define platform_SHA1_Clone_unsafe openssl_SHA1_Clone # define platform_SHA1_Update_unsafe openssl_SHA1_Update # define platform_SHA1_Final_unsafe openssl_SHA1_Final +# define platform_SHA1_Discard_unsafe openssl_SHA1_Discard # else # define platform_SHA_CTX_unsafe SHA_CTX # define platform_SHA1_Init_unsafe SHA1_Init @@ -92,6 +93,7 @@ # define platform_SHA1_Final_unsafe platform_SHA1_Final # ifdef platform_SHA1_Clone # define platform_SHA1_Clone_unsafe platform_SHA1_Clone +# define platform_SHA1_Discard_unsafe platform_SHA1_Discard # endif # ifdef SHA1_NEEDS_CLONE_HELPER # define SHA1_NEEDS_CLONE_HELPER_UNSAFE @@ -110,9 +112,11 @@ #ifdef platform_SHA1_Clone #define git_SHA1_Clone platform_SHA1_Clone +#define git_SHA1_Discard platform_SHA1_Discard #endif #ifdef platform_SHA1_Clone_unsafe # define git_SHA1_Clone_unsafe platform_SHA1_Clone_unsafe +# define git_SHA1_Discard_unsafe platform_SHA1_Discard_unsafe #endif #ifndef platform_SHA256_CTX @@ -129,6 +133,7 @@ #ifdef platform_SHA256_Clone #define git_SHA256_Clone platform_SHA256_Clone +#define git_SHA256_Discard platform_SHA256_Discard #endif #ifdef SHA1_MAX_BLOCK_SIZE @@ -142,6 +147,10 @@ static inline void git_SHA1_Clone(git_SHA_CTX *dst, const git_SHA_CTX *src) { memcpy(dst, src, sizeof(*dst)); } +static inline void git_SHA1_Discard(git_SHA_CTX *ctx UNUSED) +{ + /* noop */ +} #endif #ifndef SHA1_NEEDS_CLONE_HELPER_UNSAFE static inline void git_SHA1_Clone_unsafe(git_SHA_CTX_unsafe *dst, @@ -149,6 +158,10 @@ static inline void git_SHA1_Clone_unsafe(git_SHA_CTX_unsafe *dst, { memcpy(dst, src, sizeof(*dst)); } +static inline void git_SHA1_Discard_unsafe(git_SHA_CTX_unsafe *ctx UNUSED) +{ + /* noop */ +} #endif #ifndef SHA256_NEEDS_CLONE_HELPER @@ -156,6 +169,10 @@ static inline void git_SHA256_Clone(git_SHA256_CTX *dst, const git_SHA256_CTX *s { memcpy(dst, src, sizeof(*dst)); } +static inline void git_SHA256_Discard(git_SHA256_CTX *ctx UNUSED) +{ + /* noop */ +} #endif /* @@ -264,6 +281,7 @@ struct git_hash_ctx { git_SHA_CTX_unsafe sha1_unsafe; git_SHA256_CTX sha256; } state; + bool active; }; typedef void (*git_hash_init_fn)(struct git_hash_ctx *ctx); @@ -271,6 +289,7 @@ typedef void (*git_hash_clone_fn)(struct git_hash_ctx *dst, const struct git_has typedef void (*git_hash_update_fn)(struct git_hash_ctx *ctx, const void *in, size_t len); typedef void (*git_hash_final_fn)(unsigned char *hash, struct git_hash_ctx *ctx); typedef void (*git_hash_final_oid_fn)(struct object_id *oid, struct git_hash_ctx *ctx); +typedef void (*git_hash_discard_fn)(struct git_hash_ctx *ctx); struct git_hash_algo { /* @@ -291,20 +310,16 @@ struct git_hash_algo { /* The block size of the hash. */ size_t blksz; - /* The hash initialization function. */ + /* + * Low-level implementation hooks. Callers should use the git_hash_* + * wrappers below rather than invoking these directly. + */ git_hash_init_fn init_fn; - - /* The hash context cloning function. */ git_hash_clone_fn clone_fn; - - /* The hash update function. */ git_hash_update_fn update_fn; - - /* The hash finalization function. */ git_hash_final_fn final_fn; - - /* The hash finalization function for object IDs. */ git_hash_final_oid_fn final_oid_fn; + git_hash_discard_fn discard_fn; /* The OID of the empty tree. */ const struct object_id *empty_tree; @@ -320,11 +335,40 @@ struct git_hash_algo { }; extern const struct git_hash_algo hash_algos[GIT_HASH_NALGOS]; +/* + * Prepare an uninitialized hash context for use. You must eventually release + * the context with git_hash_final() (or final_oid()) or by calling + * git_hash_discard(). + */ void git_hash_init(struct git_hash_ctx *ctx, const struct git_hash_algo *algop); + +/* + * Clone the state of a hash. Both src and dst must have been initialized with + * git_hash_init(). + */ void git_hash_clone(struct git_hash_ctx *dst, const struct git_hash_ctx *src); + +/* + * Add more data to an initialized hash context. + */ void git_hash_update(struct git_hash_ctx *ctx, const void *in, size_t len); + +/* + * Retrieve the final hash value from a context, releasing any resources. + */ void git_hash_final(unsigned char *hash, struct git_hash_ctx *ctx); + +/* + * Like git_hash_final(), but write the result into an object_id. + */ void git_hash_final_oid(struct object_id *oid, struct git_hash_ctx *ctx); + +/* + * Discard a hash context without computing the final value, but still + * releasing any resources. + */ +void git_hash_discard(struct git_hash_ctx *ctx); + const struct git_hash_algo *hash_algo_ptr_by_number(uint32_t algo); struct git_hash_ctx *git_hash_alloc(void); void git_hash_free(struct git_hash_ctx *ctx); diff --git a/hook.h b/hook.h index b4372b636ff4de..27bb1aeb2ef465 100644 --- a/hook.h +++ b/hook.h @@ -128,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`. * */ diff --git a/http-push.c b/http-push.c index 520d6c3b6ade1f..60f6f8f0546cf4 100644 --- a/http-push.c +++ b/http-push.c @@ -365,17 +365,18 @@ static void start_put(struct transfer_request *request) enum object_type type; char hdr[50]; void *unpacked; - unsigned long len; + size_t len; 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; @@ -775,7 +776,7 @@ static void handle_new_lock_ctx(struct xml_ctx *ctx, int tag_closed) } else if (!strcmp(ctx->name, DAV_ACTIVELOCK_TOKEN)) { lock->token = xstrdup(ctx->cdata); - the_hash_algo->init_fn(&hash_ctx); + git_hash_init(&hash_ctx, the_hash_algo); git_hash_update(&hash_ctx, lock->token, strlen(lock->token)); git_hash_final(lock_token_hash, &hash_ctx); diff --git a/http-walker.c b/http-walker.c index f252de089f67a0..b58a3b2a92be38 100644 --- a/http-walker.c +++ b/http-walker.c @@ -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 ea9b16861bc3d4..caccf2108e4479 100644 --- a/http.c +++ b/http.c @@ -819,6 +819,8 @@ static int set_curl_proxy_type(CURL *result, const char *protocol) if (has_proxy_cert_password()) curl_easy_setopt(result, CURLOPT_PROXY_KEYPASSWD, proxy_cert_auth.password); + + return 0; } return -1; @@ -2609,18 +2611,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; @@ -2826,6 +2828,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; @@ -2840,7 +2843,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); @@ -2876,7 +2879,7 @@ struct http_object_request *new_http_object_request(const char *base_url, git_inflate_init(&freq->stream); - the_hash_algo->init_fn(&freq->c); + git_hash_init(&freq->c, the_hash_algo); freq->url = get_remote_object_url(base_url, hex, 0); @@ -2912,7 +2915,7 @@ struct http_object_request *new_http_object_request(const char *base_url, git_inflate_end(&freq->stream); memset(&freq->stream, 0, sizeof(freq->stream)); git_inflate_init(&freq->stream); - the_hash_algo->init_fn(&freq->c); + git_hash_init(&freq->c, the_hash_algo); if (prev_posn>0) { prev_posn = 0; lseek(freq->localfile, 0, SEEK_SET); @@ -2966,6 +2969,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; @@ -2992,7 +2996,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); @@ -3024,6 +3028,7 @@ void release_http_object_request(struct http_object_request **freq_p) curl_slist_free_all(freq->headers); strbuf_release(&freq->tmpfile); git_inflate_end(&freq->stream); + git_hash_discard(&freq->c); free(freq); *freq_p = NULL; diff --git a/imap-send.c b/imap-send.c index cfd6a5120c50e4..0d16d02029232b 100644 --- a/imap-send.c +++ b/imap-send.c @@ -1750,6 +1750,7 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server, curl_easy_cleanup(curl); curl_global_cleanup(); + strbuf_release(&msgbuf.buf); if (cred.username) { if (res == CURLE_OK) diff --git a/line-log.c b/line-log.c index 5fc75ae275e03a..0179f138f70288 100644 --- a/line-log.c +++ b/line-log.c @@ -1141,8 +1141,7 @@ int line_log_process_ranges_arbitrary_commit(struct rev_info *rev, struct commit if (range) { if (commit->parents && !bloom_filter_check(rev, commit, range)) { - struct line_log_data *prange = line_log_data_copy(range); - add_line_range(rev, commit->parents->item, prange); + add_line_range(rev, commit->parents->item, range); clear_commit_line_range(rev, commit); } else if (commit->parents && commit->parents->next) changed = process_ranges_merge_commit(rev, commit, range); diff --git a/list-objects-filter.c b/list-objects-filter.c index 78316e7f90c8d4..c912ff3079a7d7 100644 --- a/list-objects-filter.c +++ b/list-objects-filter.c @@ -280,7 +280,7 @@ static enum list_objects_filter_result filter_blobs_limit( void *filter_data_) { struct filter_blobs_limit_data *filter_data = filter_data_; - unsigned long object_length; + size_t object_length; enum object_type t; switch (filter_situation) { diff --git a/log-tree.c b/log-tree.c index 88b3019293b725..83a3c4bf9b16b9 100644 --- a/log-tree.c +++ b/log-tree.c @@ -3,6 +3,7 @@ #include "git-compat-util.h" #include "commit-reach.h" +#include "commit-slab.h" #include "config.h" #include "diff.h" #include "diffcore.h" @@ -1089,6 +1090,96 @@ static int do_remerge_diff(struct rev_info *opt, return !opt->loginfo; } +/* Per-commit path storage for --follow across merges */ +define_commit_slab(follow_pathspec_slab, char *); + +static const char *pathspec_single_path(const struct pathspec *ps) +{ + if (ps->nr != 1) + return NULL; + return ps->items[0].match; +} + +static void set_pathspec_to_single_path(struct pathspec *ps, const char *path) +{ + const char *paths[2] = { path, NULL }; + + clear_pathspec(ps); + parse_pathspec(ps, + PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL, + PATHSPEC_LITERAL_PATH, "", paths); +} + +static void remember_follow_pathspec(struct rev_info *opt, + struct commit *c, const char *path) +{ + char **slot; + + if (!path) + return; + if (!opt->follow_pathspec_slab) { + opt->follow_pathspec_slab = xmalloc(sizeof(*opt->follow_pathspec_slab)); + init_follow_pathspec_slab(opt->follow_pathspec_slab); + } + slot = follow_pathspec_slab_at(opt->follow_pathspec_slab, c); + if (*slot && !strcmp(*slot, path)) + return; + free(*slot); + *slot = xstrdup(path); +} + +static const char *recall_follow_pathspec(struct rev_info *opt, + struct commit *c) +{ + char **slot; + + if (!opt->follow_pathspec_slab) + return NULL; + slot = follow_pathspec_slab_peek(opt->follow_pathspec_slab, c); + return slot ? *slot : NULL; +} + +static void free_follow_pathspec_slot(char **slot) +{ + FREE_AND_NULL(*slot); +} + +void release_follow_pathspec_slab(struct rev_info *opt) +{ + if (!opt->follow_pathspec_slab) + return; + deep_clear_follow_pathspec_slab(opt->follow_pathspec_slab, + free_follow_pathspec_slot); + FREE_AND_NULL(opt->follow_pathspec_slab); +} + +/* Compute a path to follow in parent, if there is one */ +static void propagate_follow_pathspec_to_parent(struct rev_info *opt, + struct commit *commit, + struct commit *parent) +{ + struct diff_options diff_opts; + const char *path; + + parse_commit_or_die(parent); + repo_diff_setup(opt->diffopt.repo, &diff_opts); + copy_pathspec(&diff_opts.pathspec, &opt->diffopt.pathspec); + diff_opts.flags.recursive = 1; + diff_opts.flags.follow_renames = 1; + diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; + diff_setup_done(&diff_opts); + diff_tree_oid(get_commit_tree_oid(parent), + get_commit_tree_oid(commit), + "", &diff_opts); + + path = pathspec_single_path(&diff_opts.pathspec); + if (path) + remember_follow_pathspec(opt, parent, path); + + diff_queue_clear(&diff_queued_diff); + diff_free(&diff_opts); +} + /* * Show the diff of a commit. * @@ -1185,6 +1276,16 @@ int log_tree_commit(struct rev_info *opt, struct commit *commit) opt->loginfo = &log; opt->diffopt.no_free = 1; + /* Any recorded path for this commit? If so, restore it */ + if (opt->diffopt.flags.follow_renames) { + const char *stored = recall_follow_pathspec(opt, commit); + if (stored) { + const char *current = pathspec_single_path(&opt->diffopt.pathspec); + if (!current || strcmp(current, stored)) + set_pathspec_to_single_path(&opt->diffopt.pathspec, stored); + } + } + 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); @@ -1197,6 +1298,21 @@ int log_tree_commit(struct rev_info *opt, struct commit *commit) fprintf(opt->diffopt.file, "\n%s\n", opt->break_bar); if (shown) show_diff_of_diff(opt); + + /* Record what path each parent of this commit should use */ + if (opt->diffopt.flags.follow_renames) { + struct commit_list *parents = get_saved_parents(opt, commit); + if (parents && parents->next) { + struct commit_list *p; + for (p = parents; p; p = p->next) + propagate_follow_pathspec_to_parent(opt, commit, + p->item); + } else if (parents) { + remember_follow_pathspec(opt, parents->item, + pathspec_single_path(&opt->diffopt.pathspec)); + } + } + opt->loginfo = NULL; maybe_flush_or_die(opt->diffopt.file, "stdout"); opt->diffopt.no_free = no_free; diff --git a/log-tree.h b/log-tree.h index 07924be8bcea5e..e8679b6c4aa3a3 100644 --- a/log-tree.h +++ b/log-tree.h @@ -26,6 +26,7 @@ struct decoration_options { int parse_decorate_color_config(const char *var, const char *slot_name, const char *value); int log_tree_diff_flush(struct rev_info *); int log_tree_commit(struct rev_info *, struct commit *); +void release_follow_pathspec_slab(struct rev_info *); void show_log(struct rev_info *opt); void format_decorations(struct strbuf *sb, const struct commit *commit, enum git_colorbool use_color, const struct decoration_options *opts); diff --git a/loose.c b/loose.c index f7a3dd1a72f0fc..bf01d3e42def34 100644 --- a/loose.c +++ b/loose.c @@ -46,38 +46,37 @@ 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, NULL); + 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; + int ret = -1; - 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"); @@ -86,7 +85,6 @@ static int load_one_loose_object_map(struct repository *repo, struct odb_source return 0; } - errno = 0; if (strbuf_getwholeline(&buf, fp, '\n') || strcmp(buf.buf, loose_object_header)) goto err; while (!strbuf_getline_lf(&buf, fp)) { @@ -97,16 +95,15 @@ 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); - strbuf_release(&path); - return errno ? -1 : 0; + ret = ferror(fp) ? -1 : 0; err: + fclose(fp); strbuf_release(&buf); strbuf_release(&path); - return -1; + return ret; } int repo_read_loose_object_map(struct repository *repo) @@ -119,7 +116,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 +169,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 +178,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,32 +194,33 @@ 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); return 0; errout: error_errno(_("failed to write loose object index %s"), path.buf); - close(fd); + if (fd >= 0) + close(fd); rollback_lock_file(&lock); strbuf_release(&buf); strbuf_release(&path); 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 6af1702973c058..6c9b3f4571602f 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/mailmap.c b/mailmap.c index 3b2691781d8ff1..1386ab208cc57c 100644 --- a/mailmap.c +++ b/mailmap.c @@ -1,5 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE - #include "git-compat-util.h" #include "environment.h" #include "string-list.h" @@ -186,7 +184,7 @@ int read_mailmap_blob(struct repository *repo, struct string_list *map, { struct object_id oid; char *buf; - unsigned long size; + size_t size; enum object_type type; if (!name) @@ -219,10 +217,10 @@ int read_mailmap(struct repository *repo, struct string_list *map) map->strdup_strings = 1; map->cmp = namemap_cmp; - if (!mailmap_blob && is_bare_repository()) + if (!mailmap_blob && is_bare_repository(repo)) mailmap_blob = xstrdup("HEAD:.mailmap"); - if (!startup_info->have_repository || !is_bare_repository()) + if (!startup_info->have_repository || !is_bare_repository(repo)) err |= read_mailmap_file(map, ".mailmap", startup_info->have_repository ? MAILMAP_NOFOLLOW : 0); diff --git a/match-trees.c b/match-trees.c index 4216933d06b163..2a43c0fa1ade89 100644 --- a/match-trees.c +++ b/match-trees.c @@ -61,7 +61,7 @@ static void *fill_tree_desc_strict(struct repository *r, { void *buffer; enum object_type type; - unsigned long size; + size_t size; buffer = odb_read_object(r->objects, hash, &type, &size); if (!buffer) @@ -186,7 +186,7 @@ static int splice_tree(struct repository *r, char *subpath; int toplen; char *buf; - unsigned long sz; + size_t sz; struct tree_desc desc; unsigned char *rewrite_here; const struct object_id *rewrite_with; diff --git a/merge-blobs.c b/merge-blobs.c index 6fc279941714f6..16a75bd1e302b3 100644 --- a/merge-blobs.c +++ b/merge-blobs.c @@ -9,7 +9,7 @@ static int fill_mmfile_blob(mmfile_t *f, struct blob *obj) { void *buf; - unsigned long size; + size_t size; enum object_type type; buf = odb_read_object(the_repository->objects, &obj->object.oid, @@ -35,7 +35,7 @@ static void *three_way_filemerge(struct index_state *istate, mmfile_t *base, mmfile_t *our, mmfile_t *their, - unsigned long *size) + size_t *size) { enum ll_merge_result merge_status; mmbuffer_t res; @@ -61,7 +61,7 @@ static void *three_way_filemerge(struct index_state *istate, void *merge_blobs(struct index_state *istate, const char *path, struct blob *base, struct blob *our, - struct blob *their, unsigned long *size) + struct blob *their, size_t *size) { void *res = NULL; mmfile_t f1, f2, common; diff --git a/merge-blobs.h b/merge-blobs.h index 13cf9669e5b2c4..5797517a064e13 100644 --- a/merge-blobs.h +++ b/merge-blobs.h @@ -6,6 +6,6 @@ struct index_state; void *merge_blobs(struct index_state *, const char *, struct blob *, struct blob *, - struct blob *, unsigned long *); + struct blob *, size_t *); #endif /* MERGE_BLOBS_H */ diff --git a/merge-ort.c b/merge-ort.c index 544be9e466c9b5..c410a5d353234c 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -728,6 +728,8 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti, strintmap_clear_func(&renames->deferred[i].possible_trivial_merges); strset_clear_func(&renames->deferred[i].target_dirs); renames->deferred[i].trivial_merges_okay = 1; /* 1 == maybe */ + free(renames->pairs[i].queue); + diff_queue_init(&renames->pairs[i]); } renames->cached_pairs_valid_side = 0; renames->dir_rename_mask = 0; @@ -1008,32 +1010,34 @@ static int traverse_trees_wrapper(struct index_state *istate, info->traverse_path = renames->callback_data_traverse_path; info->fn = old_fn; for (i = old_offset; i < renames->callback_data_nr; ++i) { - info->fn(n, - renames->callback_data[i].mask, - renames->callback_data[i].dirmask, - renames->callback_data[i].names, - info); + ret = info->fn(n, + renames->callback_data[i].mask, + renames->callback_data[i].dirmask, + renames->callback_data[i].names, + info); + if (ret < 0) + break; } renames->callback_data_nr = old_offset; free(renames->callback_data_traverse_path); renames->callback_data_traverse_path = old_callback_data_traverse_path; info->traverse_path = NULL; - return 0; + return ret < 0 ? ret : 0; } -static void setup_path_info(struct merge_options *opt, - struct string_list_item *result, - const char *current_dir_name, - int current_dir_name_len, - char *fullpath, /* we'll take over ownership */ - struct name_entry *names, - struct name_entry *merged_version, - unsigned is_null, /* boolean */ - unsigned df_conflict, /* boolean */ - unsigned filemask, - unsigned dirmask, - int resolved /* boolean */) +static int setup_path_info(struct merge_options *opt, + struct string_list_item *result, + const char *current_dir_name, + int current_dir_name_len, + char *fullpath, /* we'll take over ownership */ + struct name_entry *names, + struct name_entry *merged_version, + unsigned is_null, /* boolean */ + unsigned df_conflict, /* boolean */ + unsigned filemask, + unsigned dirmask, + int resolved /* boolean */) { /* result->util is void*, so mi is a convenience typed variable */ struct merged_info *mi; @@ -1077,9 +1081,11 @@ static void setup_path_info(struct merge_options *opt, */ mi->is_null = 1; } - strmap_put(&opt->priv->paths, fullpath, mi); + if (strmap_put(&opt->priv->paths, fullpath, mi)) + return error(_("tree has duplicate entries for '%s'"), fullpath); result->string = fullpath; result->util = mi; + return 0; } static void add_pair(struct merge_options *opt, @@ -1346,9 +1352,10 @@ static int collect_merge_info_callback(int n, */ if (side1_matches_mbase && side2_matches_mbase) { /* mbase, side1, & side2 all match; use mbase as resolution */ - setup_path_info(opt, &pi, dirname, info->pathlen, fullpath, - names, names+0, mbase_null, 0 /* df_conflict */, - filemask, dirmask, 1 /* resolved */); + if (setup_path_info(opt, &pi, dirname, info->pathlen, fullpath, + names, names+0, mbase_null, 0 /* df_conflict */, + filemask, dirmask, 1 /* resolved */)) + return -1; /* Quit traversing */ return mask; } @@ -1360,9 +1367,10 @@ static int collect_merge_info_callback(int n, */ if (sides_match && filemask == 0x07) { /* use side1 (== side2) version as resolution */ - setup_path_info(opt, &pi, dirname, info->pathlen, fullpath, - names, names+1, side1_null, 0, - filemask, dirmask, 1); + if (setup_path_info(opt, &pi, dirname, info->pathlen, fullpath, + names, names+1, side1_null, 0, + filemask, dirmask, 1)) + return -1; /* Quit traversing */ return mask; } @@ -1374,18 +1382,20 @@ static int collect_merge_info_callback(int n, */ if (side1_matches_mbase && filemask == 0x07) { /* use side2 version as resolution */ - setup_path_info(opt, &pi, dirname, info->pathlen, fullpath, - names, names+2, side2_null, 0, - filemask, dirmask, 1); + if (setup_path_info(opt, &pi, dirname, info->pathlen, fullpath, + names, names+2, side2_null, 0, + filemask, dirmask, 1)) + return -1; /* Quit traversing */ return mask; } /* Similar to above but swapping sides 1 and 2 */ if (side2_matches_mbase && filemask == 0x07) { /* use side1 version as resolution */ - setup_path_info(opt, &pi, dirname, info->pathlen, fullpath, - names, names+1, side1_null, 0, - filemask, dirmask, 1); + if (setup_path_info(opt, &pi, dirname, info->pathlen, fullpath, + names, names+1, side1_null, 0, + filemask, dirmask, 1)) + return -1; /* Quit traversing */ return mask; } @@ -1409,8 +1419,9 @@ static int collect_merge_info_callback(int n, * unconflict some more cases, but that comes later so all we can * do now is record the different non-null file hashes.) */ - setup_path_info(opt, &pi, dirname, info->pathlen, fullpath, - names, NULL, 0, df_conflict, filemask, dirmask, 0); + if (setup_path_info(opt, &pi, dirname, info->pathlen, fullpath, + names, NULL, 0, df_conflict, filemask, dirmask, 0)) + return -1; /* Quit traversing */ ci = pi.util; VERIFY_CI(ci); @@ -1738,7 +1749,6 @@ static int collect_merge_info(struct merge_options *opt, setup_traverse_info(&info, opt->priv->toplevel_dir); info.fn = collect_merge_info_callback; info.data = opt; - info.show_all_errors = 1; if (repo_parse_tree(opt->repo, merge_base) < 0 || repo_parse_tree(opt->repo, side1) < 0 || @@ -3716,7 +3726,7 @@ static int read_oid_strbuf(struct merge_options *opt, { void *buf; enum object_type type; - unsigned long size; + size_t size; buf = odb_read_object(opt->repo->objects, oid, &type, &size); if (!buf) { path_msg(opt, ERROR_OBJECT_READ_FAILED, 0, diff --git a/meson.build b/meson.build index 064fe2e2f1f4e5..6d6ac8e7537975 100644 --- a/meson.build +++ b/meson.build @@ -161,6 +161,21 @@ # These machine files can be passed to `meson setup` via the `--native-file` # option. # +# Fuzzing +# ======= +# +# Meson supports building the fuzzing targets by setting `-Dfuzzers=true`. By +# default, the targets will be built without libFuzzer and thus won't be usable +# for fuzzing. You have to configure a couple of options to properly wire up +# libFuzzer: +# +# $ meson setup build-fuzzers \ +# -Db_sanitize=address,fuzzer-no-link \ +# -Dfuzzers=true \ +# -Dfuzzers_link_args=-fsanitize=fuzzer +# $ meson compile -C build-fuzzers +# $ ./build-fuzzers/oss-fuzz/fuzz-config +# # Cross compilation # ================= # @@ -278,7 +293,20 @@ compat_sources = [ 'compat/terminal.c', ] +hook_list = 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, +) + libgit_sources = [ + hook_list, 'abspath.c', 'add-interactive.c', 'add-patch.c', @@ -328,6 +356,8 @@ libgit_sources = [ 'diff-merges.c', 'diff-lib.c', 'diff-no-index.c', + 'diff-process.c', + 'diff-provider.c', 'diff.c', 'diffcore-break.c', 'diffcore-delta.c', @@ -335,6 +365,7 @@ libgit_sources = [ 'diffcore-pickaxe.c', 'diffcore-rename.c', 'diffcore-rotate.c', + 'diff-hunks.c', 'dir-iterator.c', 'dir.c', 'editor.c', @@ -405,6 +436,8 @@ libgit_sources = [ 'odb/source.c', 'odb/source-files.c', 'odb/source-inmemory.c', + 'odb/source-loose.c', + 'odb/source-packed.c', 'odb/streaming.c', 'odb/transaction.c', 'oid-array.c', @@ -420,6 +453,7 @@ libgit_sources = [ 'pack-revindex.c', 'pack-write.c', 'packfile.c', + 'packfile-list.c', 'pager.c', 'parallel-checkout.c', 'parse.c', @@ -565,19 +599,8 @@ libgit_sources += custom_target( 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 = [ + hook_list, 'builtin/add.c', 'builtin/am.c', 'builtin/annotate.c', @@ -612,6 +635,7 @@ builtin_sources = [ 'builtin/describe.c', 'builtin/diagnose.c', 'builtin/diff-files.c', + 'builtin/diff-hunks.c', 'builtin/diff-index.c', 'builtin/diff-pairs.c', 'builtin/diff-tree.c', diff --git a/meson_options.txt b/meson_options.txt index 80a8025f20be6e..dc88f130d754a1 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -106,7 +106,7 @@ option('highlight_bin', type: 'string', value: 'highlight') # 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', @@ -131,3 +131,5 @@ option('test_utf8_locale', type: 'string', description: 'Name of a UTF-8 locale used for testing.') option('fuzzers', type: 'boolean', value: false, description: 'Enable building fuzzers.') +option('fuzzers_link_args', type: 'array', value: [], + description: 'Linker arguments used to link fuzzers. Use -fsanitize=fuzzer for fuzzing.') diff --git a/midx-write.c b/midx-write.c index 561e9eedc0e6ef..8c1837f6df4671 100644 --- a/midx-write.c +++ b/midx-write.c @@ -25,9 +25,9 @@ #define NO_PREFERRED_PACK (~((uint32_t)0)) extern int midx_checksum_valid(struct multi_pack_index *m); -extern void clear_midx_files_ext(struct odb_source *source, const char *ext, +extern void clear_midx_files_ext(struct odb_source_packed *source, const char *ext, const char *keep_hash); -extern void clear_incremental_midx_files_ext(struct odb_source *source, +extern void clear_incremental_midx_files_ext(struct odb_source_packed *source, const char *ext, const struct strvec *keep_hashes); extern int cmp_idx_or_pack_name(const char *idx_or_pack_name, @@ -119,7 +119,7 @@ struct write_midx_context { struct string_list *to_include; struct repository *repo; - struct odb_source *source; + struct odb_source_packed *source; }; static uint32_t midx_pack_perm(struct write_midx_context *ctx, @@ -1107,7 +1107,7 @@ static int link_midx_to_chain(struct multi_pack_index *m) return ret; } -static void clear_midx_files(struct odb_source *source, +static void clear_midx_files(struct odb_source_packed *source, const struct strvec *hashes, unsigned incremental) { /* @@ -1237,7 +1237,7 @@ static int midx_hashcmp(const struct multi_pack_index *a, } struct write_midx_opts { - struct odb_source *source; /* non-optional */ + struct odb_source_packed *source; /* non-optional */ struct string_list *packs_to_include; struct string_list *packs_to_drop; @@ -1253,7 +1253,7 @@ struct write_midx_opts { static int write_midx_internal(struct write_midx_opts *opts) { - struct repository *r = opts->source->odb->repo; + struct repository *r = opts->source->base.odb->repo; struct strbuf midx_name = STRBUF_INIT; unsigned char midx_hash[GIT_MAX_RAWSZ]; uint32_t start_pack; @@ -1301,7 +1301,7 @@ static int write_midx_internal(struct write_midx_opts *opts) if (ctx.incremental) strbuf_addf(&midx_name, "%s/pack/multi-pack-index.d/tmp_midx_XXXXXX", - opts->source->path); + opts->source->base.path); else get_midx_filename(opts->source, &midx_name); if (safe_create_leading_directories(r, midx_name.buf)) @@ -1396,7 +1396,7 @@ static int write_midx_internal(struct write_midx_opts *opts) fill_packs_from_midx_range(&ctx, bitmap_order); } else { ctx.to_include = opts->packs_to_include; - for_each_file_in_pack_dir(opts->source->path, add_pack_to_midx, &ctx); + for_each_file_in_pack_dir(opts->source->base.path, add_pack_to_midx, &ctx); } stop_progress(&ctx.progress); @@ -1461,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); @@ -1847,7 +1847,7 @@ static int write_midx_internal(struct write_midx_opts *opts) return result; } -int write_midx_file(struct odb_source *source, +int write_midx_file(struct odb_source_packed *source, const char *preferred_pack_name, const char *refs_snapshot, unsigned flags) @@ -1862,7 +1862,7 @@ int write_midx_file(struct odb_source *source, return write_midx_internal(&opts); } -int write_midx_file_only(struct odb_source *source, +int write_midx_file_only(struct odb_source_packed *source, struct string_list *packs_to_include, const char *preferred_pack_name, const char *refs_snapshot, @@ -1881,7 +1881,7 @@ int write_midx_file_only(struct odb_source *source, return write_midx_internal(&opts); } -int write_midx_file_compact(struct odb_source *source, +int write_midx_file_compact(struct odb_source_packed *source, struct multi_pack_index *from, struct multi_pack_index *to, const char *incremental_base, @@ -1898,7 +1898,7 @@ int write_midx_file_compact(struct odb_source *source, return write_midx_internal(&opts); } -int expire_midx_packs(struct odb_source *source, unsigned flags) +int expire_midx_packs(struct odb_source_packed *source, unsigned flags) { uint32_t i, *count, result = 0; struct string_list packs_to_drop = STRING_LIST_INIT_DUP; @@ -1915,7 +1915,7 @@ int expire_midx_packs(struct odb_source *source, unsigned flags) if (flags & MIDX_PROGRESS) progress = start_delayed_progress( - source->odb->repo, + source->base.odb->repo, _("Counting referenced objects"), m->num_objects); for (i = 0; i < m->num_objects; i++) { @@ -1927,7 +1927,7 @@ int expire_midx_packs(struct odb_source *source, unsigned flags) if (flags & MIDX_PROGRESS) progress = start_delayed_progress( - source->odb->repo, + source->base.odb->repo, _("Finding and deleting unreferenced packfiles"), m->num_packs); for (i = 0; i < m->num_packs; i++) { @@ -2085,9 +2085,9 @@ static void fill_included_packs_batch(struct repository *r, free(pack_info); } -int midx_repack(struct odb_source *source, size_t batch_size, unsigned flags) +int midx_repack(struct odb_source_packed *source, size_t batch_size, unsigned flags) { - struct repository *r = source->odb->repo; + struct repository *r = source->base.odb->repo; int result = 0; uint32_t i, packs_to_repack = 0; unsigned char *include_pack; @@ -2131,7 +2131,7 @@ int midx_repack(struct odb_source *source, size_t batch_size, unsigned flags) strvec_push(&cmd.args, "pack-objects"); - strvec_pushf(&cmd.args, "%s/pack/pack", source->path); + strvec_pushf(&cmd.args, "%s/pack/pack", source->base.path); if (delta_base_offset) strvec_push(&cmd.args, "--delta-base-offset"); diff --git a/midx.c b/midx.c index efbfbb13f4106a..76c3f92cc374b8 100644 --- a/midx.c +++ b/midx.c @@ -17,9 +17,9 @@ #define MIDX_PACK_ERROR ((void *)(intptr_t)-1) int midx_checksum_valid(struct multi_pack_index *m); -void clear_midx_files_ext(struct odb_source *source, const char *ext, +void clear_midx_files_ext(struct odb_source_packed *source, const char *ext, const char *keep_hash); -void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext, +void clear_incremental_midx_files_ext(struct odb_source_packed *source, const char *ext, const struct strvec *keep_hashes); int cmp_idx_or_pack_name(const char *idx_or_pack_name, const char *idx_name); @@ -27,25 +27,25 @@ int cmp_idx_or_pack_name(const char *idx_or_pack_name, const char *midx_get_checksum_hex(const struct multi_pack_index *m) { return hash_to_hex_algop(midx_get_checksum_hash(m), - m->source->odb->repo->hash_algo); + m->source->base.odb->repo->hash_algo); } const unsigned char *midx_get_checksum_hash(const struct multi_pack_index *m) { - return m->data + m->data_len - m->source->odb->repo->hash_algo->rawsz; + return m->data + m->data_len - m->source->base.odb->repo->hash_algo->rawsz; } -void get_midx_filename(struct odb_source *source, struct strbuf *out) +void get_midx_filename(struct odb_source_packed *source, struct strbuf *out) { get_midx_filename_ext(source, out, NULL, NULL); } -void get_midx_filename_ext(struct odb_source *source, struct strbuf *out, +void get_midx_filename_ext(struct odb_source_packed *source, struct strbuf *out, const unsigned char *hash, const char *ext) { - strbuf_addf(out, "%s/pack/multi-pack-index", source->path); + strbuf_addf(out, "%s/pack/multi-pack-index", source->base.path); if (ext) - strbuf_addf(out, "-%s.%s", hash_to_hex_algop(hash, source->odb->repo->hash_algo), ext); + strbuf_addf(out, "-%s.%s", hash_to_hex_algop(hash, source->base.odb->repo->hash_algo), ext); } static int midx_read_oid_fanout(const unsigned char *chunk_start, @@ -99,17 +99,16 @@ static int midx_read_object_offsets(const unsigned char *chunk_start, return 0; } -struct multi_pack_index *get_multi_pack_index(struct odb_source *source) +struct multi_pack_index *get_multi_pack_index(struct odb_source_packed *source) { - struct odb_source_files *files = odb_source_files_downcast(source); - packfile_store_prepare(files->packed); - return files->packed->midx; + odb_source_prepare(&source->base, 0); + return source->midx; } -static struct multi_pack_index *load_multi_pack_index_one(struct odb_source *source, +static struct multi_pack_index *load_multi_pack_index_one(struct odb_source_packed *source, const char *midx_name) { - struct repository *r = source->odb->repo; + struct repository *r = source->base.odb->repo; struct multi_pack_index *m = NULL; int fd; struct stat st; @@ -234,23 +233,23 @@ static struct multi_pack_index *load_multi_pack_index_one(struct odb_source *sou return NULL; } -void get_midx_chain_dirname(struct odb_source *source, struct strbuf *buf) +void get_midx_chain_dirname(struct odb_source_packed *source, struct strbuf *buf) { - strbuf_addf(buf, "%s/pack/multi-pack-index.d", source->path); + strbuf_addf(buf, "%s/pack/multi-pack-index.d", source->base.path); } -void get_midx_chain_filename(struct odb_source *source, struct strbuf *buf) +void get_midx_chain_filename(struct odb_source_packed *source, struct strbuf *buf) { get_midx_chain_dirname(source, buf); strbuf_addstr(buf, "/multi-pack-index-chain"); } -void get_split_midx_filename_ext(struct odb_source *source, struct strbuf *buf, +void get_split_midx_filename_ext(struct odb_source_packed *source, struct strbuf *buf, const unsigned char *hash, const char *ext) { get_midx_chain_dirname(source, buf); strbuf_addf(buf, "/multi-pack-index-%s.%s", - hash_to_hex_algop(hash, source->odb->repo->hash_algo), ext); + hash_to_hex_algop(hash, source->base.odb->repo->hash_algo), ext); } static int open_multi_pack_index_chain(const struct git_hash_algo *hash_algo, @@ -306,11 +305,11 @@ static int add_midx_to_chain(struct multi_pack_index *midx, return 1; } -static struct multi_pack_index *load_midx_chain_fd_st(struct odb_source *source, +static struct multi_pack_index *load_midx_chain_fd_st(struct odb_source_packed *source, int fd, struct stat *st, int *incomplete_chain) { - const struct git_hash_algo *hash_algo = source->odb->repo->hash_algo; + const struct git_hash_algo *hash_algo = source->base.odb->repo->hash_algo; struct multi_pack_index *midx_chain = NULL; struct strbuf buf = STRBUF_INIT; int valid = 1; @@ -362,7 +361,7 @@ static struct multi_pack_index *load_midx_chain_fd_st(struct odb_source *source, return midx_chain; } -static struct multi_pack_index *load_multi_pack_index_chain(struct odb_source *source) +static struct multi_pack_index *load_multi_pack_index_chain(struct odb_source_packed *source) { struct strbuf chain_file = STRBUF_INIT; struct stat st; @@ -370,7 +369,8 @@ static struct multi_pack_index *load_multi_pack_index_chain(struct odb_source *s struct multi_pack_index *m = NULL; get_midx_chain_filename(source, &chain_file); - if (open_multi_pack_index_chain(source->odb->repo->hash_algo, chain_file.buf, &fd, &st)) { + if (open_multi_pack_index_chain(source->base.odb->repo->hash_algo, + chain_file.buf, &fd, &st)) { int incomplete; /* ownership of fd is taken over by load function */ m = load_midx_chain_fd_st(source, fd, &st, &incomplete); @@ -380,7 +380,7 @@ static struct multi_pack_index *load_multi_pack_index_chain(struct odb_source *s return m; } -struct multi_pack_index *load_multi_pack_index(struct odb_source *source) +struct multi_pack_index *load_multi_pack_index(struct odb_source_packed *source) { struct strbuf midx_name = STRBUF_INIT; struct multi_pack_index *m; @@ -456,7 +456,7 @@ static uint32_t midx_for_pack(struct multi_pack_index **_m, int prepare_midx_pack(struct multi_pack_index *m, uint32_t pack_int_id) { - struct odb_source_files *files = odb_source_files_downcast(m->source); + struct odb_source_packed *packed = m->source; struct strbuf pack_name = STRBUF_INIT; struct packed_git *p; @@ -467,10 +467,10 @@ int prepare_midx_pack(struct multi_pack_index *m, if (m->packs[pack_int_id]) return 0; - strbuf_addf(&pack_name, "%s/pack/%s", files->base.path, + strbuf_addf(&pack_name, "%s/pack/%s", packed->base.path, m->pack_names[pack_int_id]); - p = packfile_store_load_pack(files->packed, - pack_name.buf, files->base.local); + p = packfile_store_load_pack(packed, + pack_name.buf, packed->base.local); strbuf_release(&pack_name); if (!p) { @@ -523,7 +523,7 @@ int bsearch_one_midx(const struct object_id *oid, struct multi_pack_index *m, { int ret = bsearch_hash(oid->hash, m->chunk_oid_fanout, m->chunk_oid_lookup, - m->source->odb->repo->hash_algo->rawsz, + m->source->base.odb->repo->hash_algo->rawsz, result); if (result) *result += m->num_objects_in_base; @@ -554,7 +554,7 @@ struct object_id *nth_midxed_object_oid(struct object_id *oid, n = midx_for_object(&m, n); oidread(oid, m->chunk_oid_lookup + st_mult(m->hash_len, n), - m->source->odb->repo->hash_algo); + m->source->base.odb->repo->hash_algo); return oid; } @@ -734,26 +734,25 @@ int midx_preferred_pack(struct multi_pack_index *m, uint32_t *pack_int_id) return 0; } -int prepare_multi_pack_index_one(struct odb_source *source) +int prepare_multi_pack_index_one(struct odb_source_packed *source) { - struct odb_source_files *files = odb_source_files_downcast(source); - struct repository *r = source->odb->repo; + struct repository *r = source->base.odb->repo; prepare_repo_settings(r); if (!r->settings.core_multi_pack_index) return 0; - if (files->packed->midx) + if (source->midx) return 1; - files->packed->midx = load_multi_pack_index(source); + source->midx = load_multi_pack_index(source); - return !!files->packed->midx; + return !!source->midx; } int midx_checksum_valid(struct multi_pack_index *m) { - return hashfile_checksum_valid(m->source->odb->repo->hash_algo, + return hashfile_checksum_valid(m->source->base.odb->repo->hash_algo, m->data, m->data_len); } @@ -776,7 +775,7 @@ static void clear_midx_file_ext(const char *full_path, size_t full_path_len UNUS die_errno(_("failed to remove %s"), full_path); } -void clear_midx_files_ext(struct odb_source *source, const char *ext, +void clear_midx_files_ext(struct odb_source_packed *source, const char *ext, const char *keep_hash) { struct clear_midx_data data = { @@ -793,12 +792,12 @@ void clear_midx_files_ext(struct odb_source *source, const char *ext, strbuf_release(&buf); } - for_each_file_in_pack_dir(source->path, clear_midx_file_ext, &data); + for_each_file_in_pack_dir(source->base.path, clear_midx_file_ext, &data); strset_clear(&data.keep); } -void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext, +void clear_incremental_midx_files_ext(struct odb_source_packed *source, const char *ext, const struct strvec *keep_hashes) { struct clear_midx_data data = { @@ -817,7 +816,7 @@ void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext } } - for_each_file_in_pack_subdir(source->path, "multi-pack-index.d", + for_each_file_in_pack_subdir(source->base.path, "multi-pack-index.d", clear_midx_file_ext, &data); strbuf_release(&buf); @@ -826,26 +825,28 @@ void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext void clear_midx_file(struct repository *r) { + struct odb_source_files *files; struct strbuf midx = STRBUF_INIT; - get_midx_filename(r->objects->sources, &midx); - if (r->objects) { struct odb_source *source; for (source = r->objects->sources; source; source = source->next) { - struct odb_source_files *files = odb_source_files_downcast(source); + files = odb_source_files_downcast(source); if (files->packed->midx) close_midx(files->packed->midx); files->packed->midx = NULL; } } + files = odb_source_files_downcast(r->objects->sources); + get_midx_filename(files->packed, &midx); + if (remove_path(midx.buf)) die(_("failed to clear multi-pack-index at %s"), midx.buf); - clear_midx_files_ext(r->objects->sources, MIDX_EXT_BITMAP, NULL); - clear_midx_files_ext(r->objects->sources, MIDX_EXT_REV, NULL); + clear_midx_files_ext(files->packed, MIDX_EXT_BITMAP, NULL); + clear_midx_files_ext(files->packed, MIDX_EXT_REV, NULL); strbuf_release(&midx); } @@ -853,28 +854,27 @@ void clear_midx_file(struct repository *r) void clear_incremental_midx_files(struct repository *r, const struct strvec *keep_hashes) { - struct odb_source *source = r->objects->sources; + struct odb_source_files *files; + struct odb_source *source; 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); + for (source = r->objects->sources; source; source = source->next) { + files = odb_source_files_downcast(source); if (files->packed->midx) close_midx(files->packed->midx); files->packed->midx = NULL; } + files = odb_source_files_downcast(r->objects->sources); + get_midx_chain_filename(files->packed, &chain); + 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); + clear_incremental_midx_files_ext(files->packed, MIDX_EXT_BITMAP, keep_hashes); + clear_incremental_midx_files_ext(files->packed, MIDX_EXT_REV, keep_hashes); + clear_incremental_midx_files_ext(files->packed, MIDX_EXT_MIDX, keep_hashes); strbuf_release(&chain); } @@ -918,9 +918,9 @@ static int compare_pair_pos_vs_id(const void *_a, const void *_b) display_progress(progress, _n); \ } while (0) -int verify_midx_file(struct odb_source *source, unsigned flags) +int verify_midx_file(struct odb_source_packed *source, unsigned flags) { - struct repository *r = source->odb->repo; + struct repository *r = source->base.odb->repo; struct pair_pos_vs_id *pairs = NULL; uint32_t i; struct progress *progress = NULL; diff --git a/midx.h b/midx.h index 63853a03a47fd1..939c18e5885e6f 100644 --- a/midx.h +++ b/midx.h @@ -37,7 +37,7 @@ struct strvec; "GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL" struct multi_pack_index { - struct odb_source *source; + struct odb_source_packed *source; const unsigned char *data; size_t data_len; @@ -92,16 +92,16 @@ struct multi_pack_index { const char *midx_get_checksum_hex(const struct multi_pack_index *m) /* static buffer */; const unsigned char *midx_get_checksum_hash(const struct multi_pack_index *m); -void get_midx_filename(struct odb_source *source, struct strbuf *out); -void get_midx_filename_ext(struct odb_source *source, struct strbuf *out, +void get_midx_filename(struct odb_source_packed *source, struct strbuf *out); +void get_midx_filename_ext(struct odb_source_packed *source, struct strbuf *out, const unsigned char *hash, const char *ext); -void get_midx_chain_dirname(struct odb_source *source, struct strbuf *out); -void get_midx_chain_filename(struct odb_source *source, struct strbuf *out); -void get_split_midx_filename_ext(struct odb_source *source, struct strbuf *buf, +void get_midx_chain_dirname(struct odb_source_packed *source, struct strbuf *out); +void get_midx_chain_filename(struct odb_source_packed *source, struct strbuf *out); +void get_split_midx_filename_ext(struct odb_source_packed *source, struct strbuf *buf, const unsigned char *hash, const char *ext); -struct multi_pack_index *get_multi_pack_index(struct odb_source *source); -struct multi_pack_index *load_multi_pack_index(struct odb_source *source); +struct multi_pack_index *get_multi_pack_index(struct odb_source_packed *source); +struct multi_pack_index *load_multi_pack_index(struct odb_source_packed *source); int prepare_midx_pack(struct multi_pack_index *m, uint32_t pack_int_id); struct packed_git *nth_midxed_pack(struct multi_pack_index *m, uint32_t pack_int_id); @@ -123,22 +123,22 @@ int midx_contains_pack(struct multi_pack_index *m, 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); +int prepare_multi_pack_index_one(struct odb_source_packed *source); /* * Variant of write_midx_file which writes a MIDX containing only the packs * specified in packs_to_include. */ -int write_midx_file(struct odb_source *source, +int write_midx_file(struct odb_source_packed *source, const char *preferred_pack_name, const char *refs_snapshot, unsigned flags); -int write_midx_file_only(struct odb_source *source, +int write_midx_file_only(struct odb_source_packed *source, struct string_list *packs_to_include, const char *preferred_pack_name, const char *refs_snapshot, const char *incremental_base, unsigned flags); -int write_midx_file_compact(struct odb_source *source, +int write_midx_file_compact(struct odb_source_packed *source, struct multi_pack_index *from, struct multi_pack_index *to, const char *incremental_base, @@ -146,9 +146,9 @@ int write_midx_file_compact(struct odb_source *source, 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); +int verify_midx_file(struct odb_source_packed *source, unsigned flags); +int expire_midx_packs(struct odb_source_packed *source, unsigned flags); +int midx_repack(struct odb_source_packed *source, size_t batch_size, unsigned flags); void close_midx(struct multi_pack_index *m); diff --git a/name-hash.c b/name-hash.c index b91e276267891e..83757db8746230 100644 --- a/name-hash.c +++ b/name-hash.c @@ -126,7 +126,7 @@ static void hash_index_entry(struct index_state *istate, struct cache_entry *ce) hashmap_add(&istate->name_hash, &ce->ent); } - if (ignore_case) + if (repo_ignore_case(the_repository)) add_dir_entry(istate, ce); } @@ -207,7 +207,7 @@ static int lookup_lazy_params(struct index_state *istate) * code to build the "istate->name_hash". We don't * need the complexity here. */ - if (!ignore_case) + if (!repo_ignore_case(the_repository)) return 0; nr_cpus = online_cpus(); @@ -651,7 +651,7 @@ void remove_name_hash(struct index_state *istate, struct cache_entry *ce) ce->ce_flags &= ~CE_HASHED; hashmap_remove(&istate->name_hash, &ce->ent, ce); - if (ignore_case) + if (repo_ignore_case(the_repository)) remove_dir_entry(istate, ce); } diff --git a/negotiator/default.c b/negotiator/default.c index 78d58d57cebbfb..19cdf3808cf266 100644 --- a/negotiator/default.c +++ b/negotiator/default.c @@ -113,10 +113,12 @@ static const struct object_id *get_rev(struct negotiation_state *ns) unsigned int mark; struct commit_list *parents; - if (ns->rev_list.nr == 0 || ns->non_common_revs == 0) + if (ns->non_common_revs == 0) return NULL; commit = prio_queue_get(&ns->rev_list); + if (!commit) + return NULL; repo_parse_commit(the_repository, commit); parents = commit->parents; diff --git a/negotiator/skipping.c b/negotiator/skipping.c index 68c9b3b997dc80..db90fa77b5325b 100644 --- a/negotiator/skipping.c +++ b/negotiator/skipping.c @@ -143,8 +143,7 @@ static int push_parent(struct data *data, struct entry *entry, /* * Find the existing entry and use it. */ - for (size_t i = 0; i < data->rev_list.nr; i++) { - parent_entry = data->rev_list.array[i].data; + prio_queue_for_each(&data->rev_list, parent_entry) { if (parent_entry->commit == to_push) goto parent_found; } @@ -181,10 +180,12 @@ static const struct object_id *get_rev(struct data *data) struct commit_list *p; int parent_pushed = 0; - if (data->rev_list.nr == 0 || data->non_common_revs == 0) + if (data->non_common_revs == 0) return NULL; entry = prio_queue_get(&data->rev_list); + if (!entry) + return NULL; commit = entry->commit; commit->object.flags |= POPPED; if (!(commit->object.flags & COMMON)) @@ -253,8 +254,9 @@ static void have_sent(struct fetch_negotiator *n, struct commit *c) static void release(struct fetch_negotiator *n) { struct data *data = n->data; - for (size_t i = 0; i < data->rev_list.nr; i++) - free(data->rev_list.array[i].data); + void *entry; + prio_queue_for_each(&data->rev_list, entry) + free(entry); clear_prio_queue(&data->rev_list); FREE_AND_NULL(data); } diff --git a/notes-cache.c b/notes-cache.c index bf5bb1f6c13a13..74cef802bd8d93 100644 --- a/notes-cache.c +++ b/notes-cache.c @@ -82,7 +82,7 @@ char *notes_cache_get(struct notes_cache *c, struct object_id *key_oid, const struct object_id *value_oid; enum object_type type; char *value; - unsigned long size; + size_t size; value_oid = get_note(&c->tree, key_oid); if (!value_oid) diff --git a/notes-merge.c b/notes-merge.c index b9322abbcb4863..118cad2518d82e 100644 --- a/notes-merge.c +++ b/notes-merge.c @@ -339,7 +339,7 @@ static void write_note_to_worktree(const struct object_id *obj, const struct object_id *note) { enum object_type type; - unsigned long size; + size_t size; void *buf = odb_read_object(the_repository->objects, note, &type, &size); if (!buf) diff --git a/notes.c b/notes.c index 8f315e2a00d265..ec9c2cb150d4e3 100644 --- a/notes.c +++ b/notes.c @@ -811,7 +811,8 @@ int combine_notes_concatenate(struct object_id *cur_oid, const struct object_id *new_oid) { char *cur_msg = NULL, *new_msg = NULL, *buf; - unsigned long cur_len, new_len, buf_len; + unsigned long buf_len; + size_t cur_len, new_len; enum object_type cur_type, new_type; int ret; @@ -875,7 +876,7 @@ static int string_list_add_note_lines(struct string_list *list, const struct object_id *oid) { char *data; - unsigned long len; + size_t len; enum object_type t; if (is_null_oid(oid)) @@ -1282,7 +1283,8 @@ static void format_note(struct notes_tree *t, const struct object_id *object_oid static const char utf8[] = "utf-8"; const struct object_id *oid; char *msg, *msg_p; - unsigned long linelen, msglen; + unsigned long linelen; + size_t msglen; enum object_type type; if (!t) diff --git a/object-file.c b/object-file.c index 90f995d0000bf6..7ff2b730ac0ead 100644 --- a/object-file.c +++ b/object-file.c @@ -22,21 +22,15 @@ #include "odb.h" #include "odb/streaming.h" #include "odb/transaction.h" -#include "oidtree.h" #include "pack.h" #include "packfile.h" #include "path.h" #include "read-cache-ll.h" #include "setup.h" +#include "strvec.h" #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) @@ -61,14 +55,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; } @@ -94,21 +88,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) { @@ -146,16 +125,13 @@ int stream_object_signature(struct repository *r, hdrlen = format_object_header(hdr, sizeof(hdr), st->type, st->size); /* Sha1.. */ - r->hash_algo->init_fn(&c); + git_hash_init(&c, r->hash_algo); git_hash_update(&c, hdr, hdrlen); for (;;) { char buf[1024 * 16]; ssize_t readlen = odb_read_stream_read(st, buf, sizeof(buf)); - - if (readlen < 0) { - odb_read_stream_close(st); + if (readlen < 0) return -1; - } if (!readlen) break; git_hash_update(&c, buf, readlen); @@ -164,34 +140,6 @@ int stream_object_signature(struct repository *r, return !oideq(oid, &real_oid) ? -1 : 0; } -/* - * 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. @@ -215,42 +163,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; @@ -280,9 +197,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); @@ -340,7 +257,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; @@ -381,7 +298,7 @@ static int parse_loose_header(const char *hdr, struct object_info *oi) } if (oi->sizep) - *oi->sizep = cast_size_t_to_ulong(size); + *oi->sizep = size; /* * The length must be followed by a zero byte @@ -396,192 +313,28 @@ 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, + const void *buf, size_t len, struct object_id *oid, - char *hdr, int *hdrlen) + char *hdr, size_t *hdrlen) { - algo->init_fn(c); + git_hash_init(c, algo); git_hash_update(c, hdr, *hdrlen); git_hash_update(c, buf, len); 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, size_t len, + enum object_type type, struct object_id *oid, + char *hdr, size_t *hdrlen) { struct git_hash_ctx c; /* Generate the header */ *hdrlen = format_object_header(hdr, *hdrlen, type, len); - /* Sha1.. */ + /* Hash (function pointers) computation */ hash_object_body(algo, &c, buf, len, oid, hdr, hdrlen); } @@ -717,11 +470,11 @@ 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, + size_t len, enum object_type type, struct object_id *oid) { char hdr[MAX_HEADER_LEN]; - int hdrlen = sizeof(hdr); + size_t hdrlen = sizeof(hdr); write_object_file_prepare(algo, buf, len, type, oid, hdr, &hdrlen); } @@ -742,9 +495,10 @@ struct odb_transaction_files { struct tmp_objdir *objdir; struct transaction_packfile packfile; + const char *prefix; }; -static void prepare_loose_object_transaction(struct odb_transaction *base) +static int odb_transaction_files_prepare(struct odb_transaction *base) { struct odb_transaction_files *transaction = container_of_or_null(base, struct odb_transaction_files, base); @@ -756,19 +510,28 @@ static void prepare_loose_object_transaction(struct odb_transaction *base) * added at the time they call odb_transaction_files_begin. */ if (!transaction || transaction->objdir) - return; + return 0; + + transaction->objdir = tmp_objdir_create(base->source->odb->repo, transaction->prefix); + if (!transaction->objdir) + return error(_("unable to create temporary object directory")); - transaction->objdir = tmp_objdir_create(base->source->odb->repo, "bulk-fsync"); - if (transaction->objdir) - tmp_objdir_replace_primary_odb(transaction->objdir, 0); + tmp_objdir_replace_primary_odb(transaction->objdir, 0); + + return 0; } -static void fsync_loose_object_transaction(struct odb_transaction *base, - int fd, const char *filename) +static void odb_transaction_files_fsync(struct odb_transaction *base, + int fd, const char *filename) { struct odb_transaction_files *transaction = container_of_or_null(base, struct odb_transaction_files, base); + if (!transaction || !transaction->objdir) { + fsync_or_die(fd, filename); + return; + } + /* * If we have an active ODB transaction, we issue a call that * cleans the filesystem page cache but avoids a hardware flush @@ -776,58 +539,22 @@ static void fsync_loose_object_transaction(struct odb_transaction *base, * before renaming the objects to their final names as part of * flush_batch_fsync. */ - if (!transaction || !transaction->objdir || - git_fsync(fd, FSYNC_WRITEOUT_ONLY) < 0) { + if (git_fsync(fd, FSYNC_WRITEOUT_ONLY) < 0) { if (errno == ENOSYS) warning(_("core.fsyncMethod = batch is unsupported on this platform")); fsync_or_die(fd, filename); } } -/* - * Cleanup after batch-mode fsync_object_files. - */ -static void flush_loose_object_transaction(struct odb_transaction_files *transaction) -{ - struct strbuf temp_path = STRBUF_INIT; - struct tempfile *temp; - - if (!transaction->objdir) - return; - - /* - * Issue a full hardware flush against a temporary file to ensure - * that all objects are durable before any renames occur. The code in - * fsync_loose_object_transaction has already issued a writeout - * request, but it has not flushed any writeback cache in the storage - * hardware or any filesystem logs. This fsync call acts as a barrier - * to ensure that the data in each new object file is durable before - * the final name is visible. - */ - strbuf_addf(&temp_path, "%s/bulk_fsync_XXXXXX", - repo_get_object_directory(transaction->base.source->odb->repo)); - temp = xmks_tempfile(temp_path.buf); - fsync_or_die(get_tempfile_fd(temp), get_tempfile_path(temp)); - delete_tempfile(&temp); - strbuf_release(&temp_path); - - /* - * Make the object files visible in the primary ODB after their data is - * fully durable. - */ - tmp_objdir_migrate(transaction->objdir); - transaction->objdir = NULL; -} - /* 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); + odb_transaction_files_fsync(loose->base.odb->transaction, fd, filename); else if (fsync_object_files > 0) fsync_or_die(fd, filename); else @@ -896,7 +623,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, @@ -904,30 +631,31 @@ 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); + git_hash_init(c, algo); if (compat && compat_c) - compat->init_fn(compat_c); + git_hash_init(compat_c, compat); /* Start to feed header to zlib stream */ stream->next_in = (unsigned char *)hdr; @@ -945,14 +673,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); @@ -973,12 +701,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); @@ -991,10 +719,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]; @@ -1005,11 +733,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); + odb_transaction_files_prepare(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) @@ -1021,14 +749,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); @@ -1036,7 +764,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; @@ -1047,21 +775,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]; @@ -1075,10 +797,10 @@ int odb_source_loose_write_stream(struct odb_source *source, int hdrlen; if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT)) - prepare_loose_object_transaction(source->odb->transaction); + odb_transaction_files_prepare(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); /* @@ -1089,7 +811,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) { @@ -1117,7 +839,7 @@ int odb_source_loose_write_stream(struct odb_source *source, 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 @@ -1140,16 +862,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); @@ -1157,7 +879,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); @@ -1166,63 +888,23 @@ 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; + size_t len; struct object_info oi = OBJECT_INFO_INIT; struct object_id compat_oid; enum object_type type; @@ -1230,9 +912,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; @@ -1245,9 +929,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; @@ -1429,7 +1113,7 @@ static int hash_blob_stream(struct odb_write_stream *stream, header_len = format_object_header((char *)buf, sizeof(buf), OBJ_BLOB, size); - hash_algo->init_fn(&ctx); + git_hash_init(&ctx, hash_algo); git_hash_update(&ctx, buf, header_len); while (!stream->is_finished) { @@ -1464,9 +1148,10 @@ static void stream_blob_to_pack(struct transaction_packfile *state, unsigned char obuf[16384]; unsigned hdrlen; int status = Z_OK; + 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; @@ -1600,7 +1285,7 @@ static int odb_transaction_files_write_object_stream(struct odb_transaction *bas header_len = format_object_header((char *)obuf, sizeof(obuf), OBJ_BLOB, size); - transaction->base.source->odb->repo->hash_algo->init_fn(&ctx); + git_hash_init(&ctx, transaction->base.source->odb->repo->hash_algo); git_hash_update(&ctx, obuf, header_len); /* @@ -1639,6 +1324,8 @@ static int odb_transaction_files_write_object_stream(struct odb_transaction *bas state->alloc_written); state->written[state->nr_written++] = idx; } + + hashfile_checkpoint_release(&checkpoint); return 0; } @@ -1668,13 +1355,17 @@ int index_fd(struct index_state *istate, struct object_id *oid, if (flags & INDEX_WRITE_OBJECT) { struct object_database *odb = the_repository->objects; - struct odb_transaction *transaction = odb_transaction_begin(odb); + struct odb_transaction *transaction = odb->transaction; + int inflight = !!transaction; - ret = odb_transaction_write_object_stream(odb->transaction, + if (!inflight) + odb_transaction_begin_or_die(odb, &transaction, 0); + ret = odb_transaction_write_object_stream(transaction, &stream, xsize_t(st->st_size), oid); - odb_transaction_commit(transaction); + if (!inflight) + odb_transaction_commit(transaction); } else { ret = hash_blob_stream(&stream, the_repository->hash_algo, oid, @@ -1741,13 +1432,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; @@ -1832,229 +1523,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 *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_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, NULL); - 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, @@ -2068,7 +1536,7 @@ static int check_stream_oid(git_zstream *stream, unsigned long total_read; int status = Z_OK; - algop->init_fn(&c); + git_hash_init(&c, algop); git_hash_update(&c, hdr, stream->total_out); /* @@ -2095,11 +1563,13 @@ static int check_stream_oid(git_zstream *stream, if (status != Z_STREAM_END) { error(_("corrupt loose object '%s'"), oid_to_hex(expected_oid)); + git_hash_discard(&c); return -1; } if (stream->avail_in) { error(_("garbage at end of loose object '%s'"), oid_to_hex(expected_oid)); + git_hash_discard(&c); return -1; } @@ -2126,7 +1596,7 @@ int read_loose_object(struct repository *repo, unsigned long mapsize; git_zstream stream; char hdr[MAX_HEADER_LEN]; - unsigned long *size = oi->sizep; + size_t *size = oi->sizep; fd = git_open(path); if (fd >= 0) @@ -2180,179 +1650,93 @@ int read_loose_object(struct repository *repo, return ret; } -static void odb_transaction_files_commit(struct odb_transaction *base) +static int odb_transaction_files_commit(struct odb_transaction *base) { struct odb_transaction_files *transaction = container_of(base, struct odb_transaction_files, base); - flush_loose_object_transaction(transaction); - flush_packfile_transaction(transaction); -} - -struct odb_transaction *odb_transaction_files_begin(struct odb_source *source) -{ - struct odb_transaction_files *transaction; - struct object_database *odb = source->odb; - - if (odb->transaction) - return NULL; - - 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; -}; + if (transaction->objdir) { + struct strbuf temp_path = STRBUF_INIT; + struct tempfile *temp; -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; + /* + * Issue a full hardware flush against a temporary file to ensure + * that all objects are durable before any renames occur. The code in + * odb_transaction_files_fsync has already issued a writeout + * request, but it has not flushed any writeback cache in the storage + * hardware or any filesystem logs. This fsync call acts as a barrier + * to ensure that the data in each new object file is durable before + * the final name is visible. + */ + strbuf_addf(&temp_path, "%s/bulk_fsync_XXXXXX", + repo_get_object_directory(transaction->base.source->odb->repo)); + temp = xmks_tempfile(temp_path.buf); + fsync_or_die(get_tempfile_fd(temp), get_tempfile_path(temp)); + delete_tempfile(&temp); + strbuf_release(&temp_path); - switch (st->z_state) { - case ODB_LOOSE_READ_STREAM_DONE: - return 0; - case ODB_LOOSE_READ_STREAM_ERROR: - return -1; - default: - break; - } + /* + * Make the object files visible in the primary ODB after their data is + * fully durable. + */ + if (tmp_objdir_migrate(transaction->objdir)) + return error(_("unable to migrate temporary objects")); - 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; + transaction->objdir = NULL; } - 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; + flush_packfile_transaction(transaction); - 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; + return 0; } -static int close_istream_loose(struct odb_read_stream *_st) +static int odb_transaction_files_env(struct odb_transaction *base, + struct strvec *env) { - struct odb_loose_read_stream *st = - container_of(_st, struct odb_loose_read_stream, base); + struct odb_transaction_files *transaction = + container_of(base, struct odb_transaction_files, base); + int ret; - if (st->z_state == ODB_LOOSE_READ_STREAM_INUSE) - git_inflate_end(&st->z); - munmap(st->mapped, st->mapsize); - return 0; + ret = odb_transaction_files_prepare(&transaction->base); + if (!ret) + strvec_pushv(env, tmp_objdir_env(transaction->objdir)); + + return ret; } -int odb_source_loose_read_object_stream(struct odb_read_stream **out, - struct odb_source *source, - const struct object_id *oid) +int odb_transaction_files_begin(struct odb_source *source, + struct odb_transaction **out, + enum odb_transaction_flags flags) { - 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; + struct odb_transaction_files *transaction; - /* - * 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); + 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; + transaction->base.env = odb_transaction_files_env; - 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; + transaction->prefix = "bulk-fsync"; + if (flags & ODB_TRANSACTION_RECEIVE) { + /* + * ODB transactions for git-receive-pack(1) eagerly create a + * temporary directory and use a different temporary directory + * prefix. + * + * NEEDSWORK: This transaction flag is only used by the "files" + * backend to special case temporary directory set up and + * handling. Ideally transaction users should not have to care + * though. To avoid this, we could eagerly create the temporary + * directory and use the same prefix name for all transactions. + */ + transaction->prefix = "incoming"; + if (odb_transaction_files_prepare(&transaction->base)) { + free(transaction); + return -1; + } } - /* - * 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; + *out = &transaction->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 5241b8dd5c564d..d04ffa6493ff17 100644 --- a/object-file.h +++ b/object-file.h @@ -4,6 +4,11 @@ #include "git-zlib.h" #include "object.h" #include "odb.h" +#include "odb/source-loose.h" +#include "odb/transaction.h" + +/* The maximum size for an object header. */ +#define MAX_HEADER_LEN 32 struct index_state; @@ -17,61 +22,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 +42,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 +82,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 @@ -201,8 +132,16 @@ int finalize_object_file_flags(struct repository *repo, enum finalize_object_file_flags flags); void hash_object_file(const struct git_hash_algo *algo, const void *buf, - unsigned long len, enum object_type type, + size_t len, enum object_type type, struct object_id *oid); +void write_object_file_prepare(const struct git_hash_algo *algo, + const void *buf, size_t len, + enum object_type type, struct object_id *oid, + char *hdr, size_t *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,14 +161,44 @@ 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; /* * Tell the object database to optimize for adding * multiple objects. odb_transaction_files_commit must be called - * to make new objects visible. If a transaction is already - * pending, NULL is returned. + * to make new objects visible. */ -struct odb_transaction *odb_transaction_files_begin(struct odb_source *source); +int odb_transaction_files_begin(struct odb_source *source, + struct odb_transaction **out, + enum odb_transaction_flags flags); #endif /* OBJECT_FILE_H */ diff --git a/object-name.c b/object-name.c index 9ac86f19c77bbd..83efba0ba668e5 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); @@ -1208,7 +1209,7 @@ static int get_oid_oneline(struct repository *r, l->item->object.flags |= ONELINE_SEEN; prio_queue_put(©, l->item); } - while (copy.nr) { + while (prio_queue_size(©)) { const char *p, *buf; struct commit *commit; int matches; @@ -1707,8 +1708,8 @@ static char *resolve_relative_path(struct repository *r, const char *rel) die(_("relative path syntax can't be used outside working tree")); /* die() inside prefix_path() if resolved path is outside worktree */ - return prefix_path(the_repository, startup_info->prefix, - startup_info->prefix ? strlen(startup_info->prefix) : 0, + return prefix_path(the_repository, the_repository->prefix, + the_repository->prefix ? strlen(the_repository->prefix) : 0, rel); } diff --git a/object.c b/object.c index 465902ecc6dbb5..23b84aa7e29531 100644 --- a/object.c +++ b/object.c @@ -325,7 +325,7 @@ struct object *parse_object_with_flags(struct repository *r, { int skip_hash = !!(flags & PARSE_OBJECT_SKIP_HASH_CHECK); int discard_tree = !!(flags & PARSE_OBJECT_DISCARD_TREE); - unsigned long size; + size_t size; enum object_type type; int eaten; const struct object_id *repl = lookup_replace_object(r, oid); diff --git a/odb.c b/odb.c index 965ef68e4eca22..b300cd522d4d68 100644 --- a/odb.c +++ b/odb.c @@ -2,6 +2,7 @@ #include "abspath.h" #include "commit-graph.h" #include "config.h" +#include "diff-hunks.h" #include "dir.h" #include "environment.h" #include "gettext.h" @@ -625,7 +626,7 @@ static int oid_object_info_convert(struct repository *r, enum object_type type; struct object_id oid, delta_base_oid; struct object_info new_oi, *oi; - unsigned long size; + size_t size; void *content; int ret; @@ -691,8 +692,8 @@ static int oid_object_info_convert(struct repository *r, return -1; } } - input_oi->whence = new_oi.whence; - input_oi->u = new_oi.u; + if (input_oi->source_infop) + *input_oi->source_infop = *new_oi.source_infop; return ret; } @@ -716,7 +717,7 @@ int odb_read_object_info_extended(struct object_database *odb, /* returns enum object_type or negative */ int odb_read_object_info(struct object_database *odb, const struct object_id *oid, - unsigned long *sizep) + size_t *sizep) { enum object_type type; struct object_info oi = OBJECT_INFO_INIT; @@ -730,7 +731,7 @@ int odb_read_object_info(struct object_database *odb, } int odb_pretend_object(struct object_database *odb, - void *buf, unsigned long len, enum object_type type, + void *buf, size_t len, enum object_type type, struct object_id *oid) { hash_object_file(odb->repo->hash_algo, buf, len, type, oid); @@ -744,7 +745,7 @@ int odb_pretend_object(struct object_database *odb, void *odb_read_object(struct object_database *odb, const struct object_id *oid, enum object_type *type, - unsigned long *size) + size_t *size) { struct object_info oi = OBJECT_INFO_INIT; unsigned flags = OBJECT_INFO_DIE_IF_CORRUPT | OBJECT_INFO_LOOKUP_REPLACE; @@ -762,12 +763,12 @@ void *odb_read_object(struct object_database *odb, void *odb_read_object_peeled(struct object_database *odb, const struct object_id *oid, enum object_type required_type, - unsigned long *size, + size_t *size, struct object_id *actual_oid_return) { enum object_type type; void *buffer; - unsigned long isize; + size_t isize; struct object_id actual_oid; oidcpy(&actual_oid, oid); @@ -1033,6 +1034,7 @@ void odb_close(struct object_database *o) for (source = o->sources; source; source = source->next) odb_source_close(source); close_commit_graph(o); + close_diff_hunks_store(o); } static void odb_free_sources(struct object_database *o) @@ -1070,7 +1072,7 @@ void odb_free(struct object_database *o) free(o); } -void odb_reprepare(struct object_database *o) +void odb_prepare(struct object_database *o, enum odb_prepare_flags flags) { struct odb_source *source; @@ -1082,13 +1084,19 @@ void odb_reprepare(struct object_database *o) * the linked list, so existing odbs will continue to exist for * the lifetime of the process. */ - o->loaded_alternates = 0; - odb_prepare_alternates(o); + if (flags & ODB_PREPARE_FLUSH_CACHES) { + o->loaded_alternates = 0; + o->object_count_valid = 0; + } + odb_prepare_alternates(o); for (source = o->sources; source; source = source->next) - odb_source_reprepare(source); - - o->object_count_valid = 0; + odb_source_prepare(source, flags); obj_read_unlock(); } + +void odb_reprepare(struct object_database *o) +{ + odb_prepare(o, ODB_PREPARE_FLUSH_CACHES); +} diff --git a/odb.h b/odb.h index 73553ed5a7b1ea..949d55668f82e7 100644 --- a/odb.h +++ b/odb.h @@ -8,6 +8,8 @@ #include "thread-utils.h" struct cached_object_entry; +struct diff_hunks_store; +struct list_objects_filter_options; struct odb_source_inmemory; struct packed_git; struct repository; @@ -40,7 +42,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. */ @@ -75,6 +77,9 @@ struct object_database { struct commit_graph *commit_graph; unsigned commit_graph_attempted : 1; /* if loading has been attempted */ + struct diff_hunks_store *diff_hunks_store; + unsigned diff_hunks_store_attempted : 1; /* if loading has been attempted */ + /* * 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 @@ -124,10 +129,22 @@ void odb_free(struct object_database *o); */ void odb_close(struct object_database *o); +enum odb_prepare_flags { + /* + * Flush caches, reload alternates and then re-prepare each object + * source so that new objects may become accessible. + */ + ODB_PREPARE_FLUSH_CACHES = (1 << 0), +}; + /* - * Clear caches, reload alternates and then reload object sources so that new - * objects may become accessible. + * Prepare the object database for use. Calling this function is generally not + * needed, but can be useful in case the caller wants to pre-open individual + * sources. */ +void odb_prepare(struct object_database *o, enum odb_prepare_flags flags); + +/* Equivalent to `odb_prepare(o, ODB_PREPARE_FLUSH_CACHES)`. */ void odb_reprepare(struct object_database *o); /* @@ -228,12 +245,12 @@ struct odb_source *odb_add_to_alternates_memory(struct object_database *odb, void *odb_read_object(struct object_database *odb, const struct object_id *oid, enum object_type *type, - unsigned long *size); + size_t *size); void *odb_read_object_peeled(struct object_database *odb, const struct object_id *oid, enum object_type required_type, - unsigned long *size, + size_t *size, struct object_id *oid_ret); /* @@ -245,36 +262,22 @@ void *odb_read_object_peeled(struct object_database *odb, * that reference it. */ int odb_pretend_object(struct object_database *odb, - void *buf, unsigned long len, enum object_type type, + void *buf, size_t len, enum object_type type, struct object_id *oid); -struct object_info { - /* Request */ - enum object_type *typep; - unsigned long *sizep; - off_t *disk_sizep; - struct object_id *delta_base_oid; - void **contentp; +/* + * Object database source information that can be used to uniquely identify an + * object and learn more about how exactly it is stored. + */ +struct odb_source_info { + /* The source that this object has been looked up from. */ + struct odb_source *source; /* - * The time the given looked-up object has been last modified. - * - * Note: the mtime may be ambiguous in case the object exists multiple - * times in the object database. It is thus _not_ recommended to use - * this field outside of contexts where you would read every instance - * of the object, like for example with `odb_for_each_object()`. As it - * is impossible to say at the ODB level what the intent of the caller - * is (e.g. whether to find the oldest or newest object), it is the - * responsibility of the caller to disambiguate the mtimes. + * Backend-specific information about the specific object. This can be + * used for example to uniquely identify a given object in case it + * exists multiple times. */ - time_t *mtimep; - - /* Response */ - enum { - OI_CACHED, - OI_LOOSE, - OI_PACKED, - } whence; union { /* * struct { @@ -297,6 +300,52 @@ struct object_info { } u; }; +/* + * The object info contains the query and response that is to be used for + * functions that end up reading object information. Callers are expected to + * populate pointers whose information they want to request. + */ +struct object_info { + /* The object type. */ + enum object_type *typep; + + /* The inflated object size in bytes. */ + size_t *sizep; + + /* The object size as stored on disk. */ + off_t *disk_sizep; + + /* + * The base the object is deltified against, in case it is stored as a + * delta. + */ + struct object_id *delta_base_oid; + + /* The object contents. Ownership of memory goes over to the caller. */ + void **contentp; + + /* + * The time the given looked-up object has been last modified. + * + * Note: the mtime may be ambiguous in case the object exists multiple + * times in the object database. It is thus _not_ recommended to use + * this field outside of contexts where you would read every instance + * of the object, like for example with `odb_for_each_object()`. As it + * is impossible to say at the ODB level what the intent of the caller + * is (e.g. whether to find the oldest or newest object), it is the + * responsibility of the caller to disambiguate the mtimes. + */ + time_t *mtimep; + + /* + * Backend-specific information that tells the caller where exactly an + * object was looked up from. This information should help disambiguate + * object lookups in case the same object exists in multiple sources, + * or multiple times in the same source. + */ + struct odb_source_info *source_infop; +}; + /* * Initializer for a "struct object_info" that wants no items. You may * also memset() the memory to all-zeroes. @@ -356,7 +405,7 @@ int odb_read_object_info_extended(struct object_database *odb, */ int odb_read_object_info(struct object_database *odb, const struct object_id *oid, - unsigned long *sizep); + size_t *sizep); enum odb_has_object_flags { /* Retry packed storage after checking packed and loose storage */ @@ -458,6 +507,17 @@ struct odb_for_each_object_options { */ const struct object_id *prefix; size_t prefix_hex_len; + + /* + * Optional object filter that allows backends to skip yielding + * objects that are excluded by the filter as an optimization. The + * filter is a best-effort hint: backends may use it to skip + * excluded objects (e.g. by consulting a reachability bitmap), but + * are also free to ignore it entirely and yield every object. As a + * consequence, callers must re-apply the filter on yielded objects + * if they require strict filtering semantics. + */ + const struct list_objects_filter_options *filter; }; /* diff --git a/odb/source-files.c b/odb/source-files.c index b5abd20e971e78..413875851135a5 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,8 +28,8 @@ 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); - packfile_store_free(files->packed); + odb_source_free(&files->loose->base); + odb_source_free(&files->packed->base); odb_source_release(&files->base); free(files); } @@ -36,14 +37,16 @@ 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); - packfile_store_close(files->packed); + odb_source_close(&files->loose->base); + odb_source_close(&files->packed->base); } -static void odb_source_files_reprepare(struct odb_source *source) +static void odb_source_files_prepare(struct odb_source *source, + enum odb_prepare_flags flags) { struct odb_source_files *files = odb_source_files_downcast(source); - odb_source_loose_reprepare(&files->base); - packfile_store_reprepare(files->packed); + odb_source_prepare(&files->loose->base, flags); + odb_source_prepare(&files->packed->base, flags); } static int odb_source_files_read_object_info(struct odb_source *source, @@ -53,8 +56,8 @@ 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)) + if (!odb_source_read_object_info(&files->packed->base, oid, oi, flags) || + !odb_source_read_object_info(&files->loose->base, oid, oi, flags)) return 0; return -1; @@ -65,8 +68,8 @@ static int odb_source_files_read_object_stream(struct odb_read_stream **out, const struct object_id *oid) { 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)) + if (!odb_source_read_object_stream(out, &files->packed->base, oid) || + !odb_source_read_object_stream(out, &files->loose->base, oid)) return 0; return -1; } @@ -81,12 +84,12 @@ 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; } - ret = packfile_store_for_each_object(files->packed, request, cb, cb_data, opts); + ret = odb_source_for_each_object(&files->packed->base, request, cb, cb_data, opts); if (ret) return ret; @@ -101,14 +104,14 @@ static int odb_source_files_count_objects(struct odb_source *source, unsigned long count; int ret; - ret = packfile_store_count_objects(files->packed, flags, &count); + ret = odb_source_count_objects(&files->packed->base, flags, &count); if (ret < 0) goto out; 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; @@ -131,11 +134,11 @@ static int odb_source_files_find_abbrev_len(struct odb_source *source, unsigned len = min_len; int ret; - ret = packfile_store_find_abbrev_len(files->packed, oid, len, &len); + ret = odb_source_find_abbrev_len(&files->packed->base, oid, len, &len); 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; @@ -150,21 +153,22 @@ static int odb_source_files_freshen_object(struct odb_source *source, const struct object_id *oid) { 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)) + if (odb_source_freshen_object(&files->packed->base, oid) || + odb_source_freshen_object(&files->loose->base, oid)) return 1; return 0; } static int odb_source_files_write_object(struct odb_source *source, - const void *buf, unsigned long len, + const void *buf, size_t len, enum object_type type, struct object_id *oid, 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,17 +176,15 @@ 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, - struct odb_transaction **out) + struct odb_transaction **out, + enum odb_transaction_flags flags) { - struct odb_transaction *tx = odb_transaction_files_begin(source); - if (!tx) - return -1; - *out = tx; - return 0; + return odb_transaction_files_begin(source, out, flags); } static int odb_source_files_read_alternates(struct odb_source *source, @@ -264,12 +266,12 @@ 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->packed = packfile_store_new(&files->base); + files->loose = odb_source_loose_new(odb, path, local); + files->packed = odb_source_packed_new(odb, path, local); files->base.free = odb_source_files_free; files->base.close = odb_source_files_close; - files->base.reprepare = odb_source_files_reprepare; + files->base.prepare = odb_source_files_prepare; files->base.read_object_info = odb_source_files_read_object_info; files->base.read_object_stream = odb_source_files_read_object_stream; files->base.for_each_object = odb_source_files_for_each_object; diff --git a/odb/source-files.h b/odb/source-files.h index 23a3b4e04b1218..d7ac3c1c81d892 100644 --- a/odb/source-files.h +++ b/odb/source-files.h @@ -4,7 +4,7 @@ #include "odb/source.h" struct odb_source_loose; -struct packfile_store; +struct odb_source_packed; /* * The files object database source uses a combination of loose objects and @@ -13,7 +13,7 @@ struct packfile_store; struct odb_source_files { struct odb_source base; struct odb_source_loose *loose; - struct packfile_store *packed; + struct odb_source_packed *packed; }; /* Allocate and initialize a new object source. */ diff --git a/odb/source-inmemory.c b/odb/source-inmemory.c index e004566d768b01..e47bfd8fccabf0 100644 --- a/odb/source-inmemory.c +++ b/odb/source-inmemory.c @@ -52,7 +52,8 @@ static void populate_object_info(struct odb_source_inmemory *source, *oi->contentp = xmemdupz(object->buf, object->size); if (oi->mtimep) *oi->mtimep = 0; - oi->whence = OI_CACHED; + if (oi->source_infop) + oi->source_infop->source = &source->base; } static int odb_source_inmemory_read_object_info(struct odb_source *source, @@ -227,7 +228,7 @@ static int odb_source_inmemory_count_objects(struct odb_source *source, } static int odb_source_inmemory_write_object(struct odb_source *source, - const void *buf, unsigned long len, + const void *buf, size_t len, enum object_type type, struct object_id *oid, struct object_id *compat_oid UNUSED, @@ -304,7 +305,8 @@ static int odb_source_inmemory_freshen_object(struct odb_source *source, } static int odb_source_inmemory_begin_transaction(struct odb_source *source UNUSED, - struct odb_transaction **out UNUSED) + struct odb_transaction **out UNUSED, + enum odb_transaction_flags flags UNUSED) { return error("in-memory source does not support transactions"); } @@ -325,7 +327,8 @@ static void odb_source_inmemory_close(struct odb_source *source UNUSED) { } -static void odb_source_inmemory_reprepare(struct odb_source *source UNUSED) +static void odb_source_inmemory_prepare(struct odb_source *source UNUSED, + enum odb_prepare_flags flags UNUSED) { } @@ -365,7 +368,7 @@ struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb) source->base.free = odb_source_inmemory_free; source->base.close = odb_source_inmemory_close; - source->base.reprepare = odb_source_inmemory_reprepare; + source->base.prepare = odb_source_inmemory_prepare; 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; diff --git a/odb/source-loose.c b/odb/source-loose.c new file mode 100644 index 00000000000000..3f7d04a56e36ce --- /dev/null +++ b/odb/source-loose.c @@ -0,0 +1,731 @@ +#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]; + size_t 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 (oi->source_infop && !ret) + oi->source_infop->source = &loose->base; + } + + 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; + 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; + } + + oi.sizep = &st->base.size; + oi.typep = &st->base.type; + + if (parse_loose_header(st->hdr, &oi) < 0 || st->base.type < 0) + goto error; + + 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, size_t 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]; + size_t 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, + enum odb_transaction_flags flags 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_prepare(struct odb_source *source, + enum odb_prepare_flags flags) +{ + struct odb_source_loose *loose = odb_source_loose_downcast(source); + if (flags & ODB_PREPARE_FLUSH_CACHES) + 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.prepare = odb_source_loose_prepare; + 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 00000000000000..6070aaf3ce6ab2 --- /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-packed.c b/odb/source-packed.c new file mode 100644 index 00000000000000..d00aec5a452c58 --- /dev/null +++ b/odb/source-packed.c @@ -0,0 +1,850 @@ +#include "git-compat-util.h" +#include "abspath.h" +#include "chdir-notify.h" +#include "dir.h" +#include "git-zlib.h" +#include "list-objects-filter-options.h" +#include "mergesort.h" +#include "midx.h" +#include "odb/source-packed.h" +#include "odb/streaming.h" +#include "packfile.h" +#include "pack-bitmap.h" + +static int find_pack_entry(struct odb_source_packed *store, + const struct object_id *oid, + struct pack_entry *e) +{ + struct packfile_list_entry *l; + + odb_source_prepare(&store->base, 0); + if (store->midx && fill_midx_entry(store->midx, oid, e)) + return 1; + + for (l = store->packs.head; l; l = l->next) { + struct packed_git *p = l->pack; + + if (!p->multi_pack_index && packfile_fill_entry(p, oid, e)) { + if (!store->skip_mru_updates) + packfile_list_prepend(&store->packs, p); + return 1; + } + } + + return 0; +} + +static int odb_source_packed_read_object_info(struct odb_source *source, + const struct object_id *oid, + struct object_info *oi, + enum object_info_flags flags) +{ + struct odb_source_packed *packed = odb_source_packed_downcast(source); + struct pack_entry e; + int ret; + + /* + * In case the first read didn't surface the object, we have to reload + * packfiles. This may cause us to discover new packfiles that have + * been added since the last time we have prepared the packfile store. + */ + if (flags & OBJECT_INFO_SECOND_READ) + odb_source_prepare(source, ODB_PREPARE_FLUSH_CACHES); + + if (!find_pack_entry(packed, oid, &e)) + return 1; + + /* + * We know that the caller doesn't actually need the + * information below, so return early. + */ + if (!oi) + return 0; + + ret = packed_object_info(packed, e.p, e.offset, oi); + if (ret < 0) { + mark_bad_packed_object(e.p, oid); + return -1; + } + + return 0; +} + +static int odb_source_packed_read_object_stream(struct odb_read_stream **out, + struct odb_source *source, + const struct object_id *oid) +{ + struct odb_source_packed *packed = odb_source_packed_downcast(source); + struct pack_entry e; + + if (!find_pack_entry(packed, oid, &e)) + return -1; + + return packfile_read_object_stream(out, oid, e.p, e.offset); +} + +struct odb_source_packed_for_each_object_wrapper_data { + struct odb_source_packed *store; + const struct object_info *request; + odb_for_each_object_cb cb; + void *cb_data; +}; + +static int odb_source_packed_for_each_object_wrapper(const struct object_id *oid, + struct packed_git *pack, + uint32_t index_pos, + void *cb_data) +{ + struct odb_source_packed_for_each_object_wrapper_data *data = cb_data; + + if (data->request) { + off_t offset = nth_packed_object_offset(pack, index_pos); + struct object_info oi = *data->request; + + if (packed_object_info_with_index_pos(data->store, pack, offset, + &index_pos, &oi) < 0) { + mark_bad_packed_object(pack, oid); + return -1; + } + + return data->cb(oid, &oi, data->cb_data); + } else { + return data->cb(oid, NULL, data->cb_data); + } +} + +static int match_hash(unsigned len, const unsigned char *a, const unsigned char *b) +{ + do { + if (*a != *b) + return 0; + a++; + b++; + len -= 2; + } while (len > 1); + if (len) + if ((*a ^ *b) & 0xf0) + return 0; + return 1; +} + +static bool should_exclude_pack(struct packed_git *p, enum odb_for_each_object_flags flags) +{ + if ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local) + return true; + if ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) && + !p->pack_promisor) + return true; + if ((flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) && + p->pack_keep_in_core) + return true; + if ((flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) && + p->pack_keep) + return true; + return false; +} + +static int for_each_prefixed_object_in_midx( + struct odb_source_packed *source, + struct multi_pack_index *m, + const struct odb_for_each_object_options *opts, + struct odb_source_packed_for_each_object_wrapper_data *data) +{ + bool pack_errors = false; + int ret; + + for (; m; m = m->base_midx) { + uint32_t num, i, first = 0; + int len = opts->prefix_hex_len > m->source->base.odb->repo->hash_algo->hexsz ? + m->source->base.odb->repo->hash_algo->hexsz : opts->prefix_hex_len; + + if (!m->num_objects) + continue; + + num = m->num_objects + m->num_objects_in_base; + + bsearch_one_midx(opts->prefix, m, &first); + + /* + * At this point, "first" is the location of the lowest + * object with an object name that could match "opts->prefix". + * See if we have 0, 1 or more objects that actually match(es). + */ + for (i = first; i < num; i++) { + const struct object_id *current = NULL; + struct packed_git *pack; + struct object_id oid; + + current = nth_midxed_object_oid(&oid, m, i); + + if (!match_hash(len, opts->prefix->hash, current->hash)) + break; + + if (opts->flags || data->request) { + uint32_t pack_id = nth_midxed_pack_int_id(m, i); + + if (prepare_midx_pack(m, pack_id)) { + pack_errors = true; + continue; + } + + pack = nth_midxed_pack(m, pack_id); + if (should_exclude_pack(pack, opts->flags)) + continue; + } + + if (data->request) { + struct object_info oi = *data->request; + off_t offset = nth_midxed_offset(m, i); + + ret = packed_object_info(source, pack, offset, &oi); + if (ret) + goto out; + + ret = data->cb(&oid, &oi, data->cb_data); + if (ret) + goto out; + } else { + ret = data->cb(&oid, NULL, data->cb_data); + if (ret) + goto out; + } + } + } + + ret = 0; + +out: + if (!ret && pack_errors) + ret = -1; + return ret; +} + +static int for_each_prefixed_object_in_pack( + struct odb_source_packed *source, + struct packed_git *p, + const struct odb_for_each_object_options *opts, + struct odb_source_packed_for_each_object_wrapper_data *data) +{ + uint32_t num, i, first = 0; + int len = opts->prefix_hex_len > p->repo->hash_algo->hexsz ? + p->repo->hash_algo->hexsz : opts->prefix_hex_len; + int ret; + + num = p->num_objects; + bsearch_pack(opts->prefix, p, &first); + + /* + * At this point, "first" is the location of the lowest object + * with an object name that could match "bin_pfx". See if we have + * 0, 1 or more objects that actually match(es). + */ + for (i = first; i < num; i++) { + struct object_id oid; + + nth_packed_object_id(&oid, p, i); + if (!match_hash(len, opts->prefix->hash, oid.hash)) + break; + + if (data->request) { + struct object_info oi = *data->request; + off_t offset = nth_packed_object_offset(p, i); + + ret = packed_object_info(source, p, offset, &oi); + if (ret) + goto out; + + ret = data->cb(&oid, &oi, data->cb_data); + if (ret) + goto out; + } else { + ret = data->cb(&oid, NULL, data->cb_data); + if (ret) + goto out; + } + } + + ret = 0; + +out: + return ret; +} + +static int odb_source_packed_for_each_prefixed_object( + struct odb_source_packed *store, + const struct odb_for_each_object_options *opts, + struct odb_source_packed_for_each_object_wrapper_data *data) +{ + struct packfile_list_entry *e; + struct multi_pack_index *m; + bool pack_errors = false; + int ret; + + store->skip_mru_updates = true; + + m = get_multi_pack_index(store); + if (m) { + ret = for_each_prefixed_object_in_midx(store, m, opts, data); + if (ret) + goto out; + } + + for (e = packfile_store_get_packs(store); e; e = e->next) { + if (e->pack->multi_pack_index) + continue; + if (should_exclude_pack(e->pack, opts->flags)) + continue; + + if (open_pack_index(e->pack)) { + pack_errors = true; + continue; + } + + if (!e->pack->num_objects) + continue; + + ret = for_each_prefixed_object_in_pack(store, e->pack, opts, data); + if (ret) + goto out; + } + + ret = 0; + +out: + store->skip_mru_updates = false; + if (!ret && pack_errors) + ret = -1; + return ret; +} + +struct bitmapped_for_each_object_data { + struct odb_source_packed *packed; + const struct object_info *request; + const struct odb_for_each_object_options *opts; + odb_for_each_object_cb cb; + void *cb_data; +}; + +static int bitmapped_for_each_object(const struct object_id *oid, + enum object_type type UNUSED, + int flags UNUSED, + uint32_t hash UNUSED, + struct packed_git *pack, + off_t offset, + void *cb_data) +{ + struct bitmapped_for_each_object_data *data = cb_data; + + if (should_exclude_pack(pack, data->opts->flags)) + return 0; + + if (data->request) { + struct object_info oi = *data->request; + if (packed_object_info(data->packed, pack, offset, &oi) < 0) + return -1; + return data->cb(oid, &oi, data->cb_data); + } + + return data->cb(oid, NULL, data->cb_data); +} + +static int odb_source_packed_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_packed *packed = odb_source_packed_downcast(source); + struct odb_source_packed_for_each_object_wrapper_data data = { + .store = packed, + .request = request, + .cb = cb, + .cb_data = cb_data, + }; + struct bitmap_index *bitmap = NULL; + struct packfile_list_entry *e; + int pack_errors = 0, ret; + + if (opts->prefix) + return odb_source_packed_for_each_prefixed_object(packed, opts, &data); + + if (opts->filter && + opts->filter->choice != LOFC_DISABLED && + can_filter_bitmap(opts->filter)) + bitmap = prepare_bitmap_git_for_source(packed); + if (bitmap) { + struct bitmapped_for_each_object_data bitmap_data = { + .packed = packed, + .request = request, + .opts = opts, + .cb = cb, + .cb_data = cb_data, + }; + + ret = for_each_bitmapped_object(bitmap, opts->filter, + bitmapped_for_each_object, + &bitmap_data); + if (ret) + goto out; + } + + packed->skip_mru_updates = true; + + for (e = packfile_store_get_packs(packed); e; e = e->next) { + struct packed_git *p = e->pack; + + if (should_exclude_pack(p, opts->flags)) + continue; + + /* + * Objects covered by the bitmap have already been yielded + * above; skip them here to avoid duplicates. + */ + if (bitmap && bitmap_index_contains_pack(bitmap, p)) + continue; + + if (open_pack_index(p)) { + pack_errors = 1; + continue; + } + + ret = for_each_object_in_pack(p, odb_source_packed_for_each_object_wrapper, + &data, opts->flags); + if (ret) + goto out; + } + + ret = 0; + +out: + packed->skip_mru_updates = false; + free_bitmap_index(bitmap); + + if (!ret && pack_errors) + ret = -1; + return ret; +} + +static int odb_source_packed_count_objects(struct odb_source *source, + enum odb_count_objects_flags flags UNUSED, + unsigned long *out) +{ + struct odb_source_packed *packed = odb_source_packed_downcast(source); + struct packfile_list_entry *e; + struct multi_pack_index *m; + unsigned long count = 0; + int ret; + + m = get_multi_pack_index(packed); + if (m) + count += m->num_objects + m->num_objects_in_base; + + for (e = packfile_store_get_packs(packed); e; e = e->next) { + if (e->pack->multi_pack_index) + continue; + if (open_pack_index(e->pack)) { + ret = -1; + goto out; + } + + count += e->pack->num_objects; + } + + *out = count; + ret = 0; + +out: + return ret; +} + +static int extend_abbrev_len(const struct object_id *a, + const struct object_id *b, + unsigned *out) +{ + unsigned len = oid_common_prefix_hexlen(a, b); + if (len != hash_algos[a->algo].hexsz && len >= *out) + *out = len + 1; + return 0; +} + +static void find_abbrev_len_for_midx(struct multi_pack_index *m, + const struct object_id *oid, + unsigned min_len, + unsigned *out) +{ + unsigned len = min_len; + + for (; m; m = m->base_midx) { + int match = 0; + uint32_t num, first = 0; + struct object_id found_oid; + + if (!m->num_objects) + continue; + + num = m->num_objects + m->num_objects_in_base; + match = bsearch_one_midx(oid, m, &first); + + /* + * first is now the position in the packfile where we + * would insert the object ID if it does not exist (or the + * position of the object ID if it does exist). Hence, we + * consider a maximum of two objects nearby for the + * abbreviation length. + */ + + if (!match) { + if (nth_midxed_object_oid(&found_oid, m, first)) + extend_abbrev_len(&found_oid, oid, &len); + } else if (first < num - 1) { + if (nth_midxed_object_oid(&found_oid, m, first + 1)) + extend_abbrev_len(&found_oid, oid, &len); + } + if (first > 0) { + if (nth_midxed_object_oid(&found_oid, m, first - 1)) + extend_abbrev_len(&found_oid, oid, &len); + } + } + + *out = len; +} + +static void find_abbrev_len_for_pack(struct packed_git *p, + const struct object_id *oid, + unsigned min_len, + unsigned *out) +{ + int match; + uint32_t num, first = 0; + struct object_id found_oid; + unsigned len = min_len; + + num = p->num_objects; + match = bsearch_pack(oid, p, &first); + + /* + * first is now the position in the packfile where we would insert + * the object ID if it does not exist (or the position of mad->hash if + * it does exist). Hence, we consider a maximum of two objects + * nearby for the abbreviation length. + */ + if (!match) { + if (!nth_packed_object_id(&found_oid, p, first)) + extend_abbrev_len(&found_oid, oid, &len); + } else if (first < num - 1) { + if (!nth_packed_object_id(&found_oid, p, first + 1)) + extend_abbrev_len(&found_oid, oid, &len); + } + if (first > 0) { + if (!nth_packed_object_id(&found_oid, p, first - 1)) + extend_abbrev_len(&found_oid, oid, &len); + } + + *out = len; +} + +static int odb_source_packed_find_abbrev_len(struct odb_source *source, + const struct object_id *oid, + unsigned min_len, + unsigned *out) +{ + struct odb_source_packed *packed = odb_source_packed_downcast(source); + struct packfile_list_entry *e; + struct multi_pack_index *m; + + m = get_multi_pack_index(packed); + if (m) + find_abbrev_len_for_midx(m, oid, min_len, &min_len); + + for (e = packfile_store_get_packs(packed); e; e = e->next) { + if (e->pack->multi_pack_index) + continue; + if (open_pack_index(e->pack) || !e->pack->num_objects) + continue; + + find_abbrev_len_for_pack(e->pack, oid, min_len, &min_len); + } + + *out = min_len; + return 0; +} + +static int odb_source_packed_freshen_object(struct odb_source *source, + const struct object_id *oid) +{ + struct odb_source_packed *packed = odb_source_packed_downcast(source); + struct pack_entry e; + + if (!find_pack_entry(packed, oid, &e)) + return 0; + if (e.p->is_cruft) + return 0; + if (e.p->freshened) + return 1; + if (utime(e.p->pack_name, NULL)) + return 0; + e.p->freshened = 1; + + return 1; +} + +static int odb_source_packed_write_object(struct odb_source *source UNUSED, + const void *buf UNUSED, + size_t len UNUSED, + enum object_type type UNUSED, + struct object_id *oid UNUSED, + struct object_id *compat_oid UNUSED, + unsigned flags UNUSED) +{ + return error("packed backend cannot write objects"); +} + +static int odb_source_packed_write_object_stream(struct odb_source *source UNUSED, + struct odb_write_stream *stream UNUSED, + size_t len UNUSED, + struct object_id *oid UNUSED) +{ + return error("packed backend cannot write object streams"); +} + +static int odb_source_packed_begin_transaction(struct odb_source *source UNUSED, + struct odb_transaction **out UNUSED, + enum odb_transaction_flags flags UNUSED) +{ + return error("packed backend cannot begin transactions"); +} + +static int odb_source_packed_read_alternates(struct odb_source *source UNUSED, + struct strvec *out UNUSED) +{ + return 0; +} + +static int odb_source_packed_write_alternate(struct odb_source *source UNUSED, + const char *alternate UNUSED) +{ + return error("packed backend cannot write alternates"); +} + +void (*report_garbage)(unsigned seen_bits, const char *path); + +static void report_helper(const struct string_list *list, + int seen_bits, int first, int last) +{ + if (seen_bits == (PACKDIR_FILE_PACK|PACKDIR_FILE_IDX)) + return; + + for (; first < last; first++) + report_garbage(seen_bits, list->items[first].string); +} + +static void report_pack_garbage(struct string_list *list) +{ + int baselen = -1, first = 0, seen_bits = 0; + + if (!report_garbage) + return; + + string_list_sort(list); + + for (size_t i = 0; i < list->nr; i++) { + const char *path = list->items[i].string; + if (baselen != -1 && + strncmp(path, list->items[first].string, baselen)) { + report_helper(list, seen_bits, first, i); + baselen = -1; + seen_bits = 0; + } + if (baselen == -1) { + const char *dot = strrchr(path, '.'); + if (!dot) { + report_garbage(PACKDIR_FILE_GARBAGE, path); + continue; + } + baselen = dot - path + 1; + first = i; + } + if (!strcmp(path + baselen, "pack")) + seen_bits |= 1; + else if (!strcmp(path + baselen, "idx")) + seen_bits |= 2; + } + report_helper(list, seen_bits, first, list->nr); +} + +struct prepare_pack_data { + struct odb_source_packed *source; + struct string_list *garbage; +}; + +static void prepare_pack(const char *full_name, size_t full_name_len, + const char *file_name, void *_data) +{ + struct prepare_pack_data *data = (struct prepare_pack_data *)_data; + size_t base_len = full_name_len; + + if (strip_suffix_mem(full_name, &base_len, ".idx") && + !(data->source->midx && + midx_contains_pack(data->source->midx, file_name))) { + char *trimmed_path = xstrndup(full_name, full_name_len); + packfile_store_load_pack(data->source, + trimmed_path, data->source->base.local); + free(trimmed_path); + } + + if (!report_garbage) + return; + + if (!strcmp(file_name, "multi-pack-index") || + !strcmp(file_name, "multi-pack-index.d")) + return; + if (starts_with(file_name, "multi-pack-index") && + (ends_with(file_name, ".bitmap") || ends_with(file_name, ".rev"))) + return; + if (ends_with(file_name, ".idx") || + ends_with(file_name, ".rev") || + ends_with(file_name, ".pack") || + ends_with(file_name, ".bitmap") || + ends_with(file_name, ".keep") || + ends_with(file_name, ".promisor") || + ends_with(file_name, ".mtimes")) + string_list_append(data->garbage, full_name); + else + report_garbage(PACKDIR_FILE_GARBAGE, full_name); +} + +static void prepare_packed_git_one(struct odb_source_packed *source) +{ + struct string_list garbage = STRING_LIST_INIT_DUP; + struct prepare_pack_data data = { + .source = source, + .garbage = &garbage, + }; + + for_each_file_in_pack_dir(source->base.path, prepare_pack, &data); + + report_pack_garbage(data.garbage); + string_list_clear(data.garbage, 0); +} + +DEFINE_LIST_SORT(static, sort_packs, struct packfile_list_entry, next); + +static int sort_pack(const struct packfile_list_entry *a, + const struct packfile_list_entry *b) +{ + int st; + + /* + * Local packs tend to contain objects specific to our + * variant of the project than remote ones. In addition, + * remote ones could be on a network mounted filesystem. + * Favor local ones for these reasons. + */ + st = a->pack->pack_local - b->pack->pack_local; + if (st) + return -st; + + /* + * Younger packs tend to contain more recent objects, + * and more recent objects tend to get accessed more + * often. + */ + if (a->pack->mtime < b->pack->mtime) + return 1; + else if (a->pack->mtime == b->pack->mtime) + return 0; + return -1; +} + +static void odb_source_packed_prepare(struct odb_source *source, + enum odb_prepare_flags flags) +{ + struct odb_source_packed *packed = odb_source_packed_downcast(source); + + if (flags & ODB_PREPARE_FLUSH_CACHES) + packed->initialized = false; + if (packed->initialized) + return; + + prepare_multi_pack_index_one(packed); + prepare_packed_git_one(packed); + + sort_packs(&packed->packs.head, sort_pack); + for (struct packfile_list_entry *e = packed->packs.head; e; e = e->next) + if (!e->next) + packed->packs.tail = e; + + packed->initialized = true; +} + +static void odb_source_packed_reparent(const char *name UNUSED, + const char *old_cwd, + const char *new_cwd, + void *cb_data) +{ + struct odb_source_packed *packed = cb_data; + char *path = reparent_relative_path(old_cwd, new_cwd, + packed->base.path); + free(packed->base.path); + packed->base.path = path; +} + +static void odb_source_packed_close(struct odb_source *source) +{ + struct odb_source_packed *packed = odb_source_packed_downcast(source); + + for (struct packfile_list_entry *e = packed->packs.head; e; e = e->next) { + if (e->pack->do_not_close) + BUG("want to close pack marked 'do-not-close'"); + close_pack(e->pack); + } + if (packed->midx) + close_midx(packed->midx); + packed->midx = NULL; +} + +static void odb_source_packed_free(struct odb_source *source) +{ + struct odb_source_packed *packed = odb_source_packed_downcast(source); + + chdir_notify_unregister(NULL, odb_source_packed_reparent, packed); + + for (struct packfile_list_entry *e = packed->packs.head; e; e = e->next) + free(e->pack); + packfile_list_clear(&packed->packs); + + strmap_clear(&packed->packs_by_path, 0); + odb_source_release(&packed->base); + free(packed); +} + +struct odb_source_packed *odb_source_packed_new(struct object_database *odb, + const char *path, + bool local) +{ + struct odb_source_packed *packed; + + CALLOC_ARRAY(packed, 1); + odb_source_init(&packed->base, odb, ODB_SOURCE_PACKED, path, local); + strmap_init(&packed->packs_by_path); + + packed->base.free = odb_source_packed_free; + packed->base.close = odb_source_packed_close; + packed->base.prepare = odb_source_packed_prepare; + packed->base.read_object_info = odb_source_packed_read_object_info; + packed->base.read_object_stream = odb_source_packed_read_object_stream; + packed->base.for_each_object = odb_source_packed_for_each_object; + packed->base.count_objects = odb_source_packed_count_objects; + packed->base.find_abbrev_len = odb_source_packed_find_abbrev_len; + packed->base.freshen_object = odb_source_packed_freshen_object; + packed->base.write_object = odb_source_packed_write_object; + packed->base.write_object_stream = odb_source_packed_write_object_stream; + packed->base.begin_transaction = odb_source_packed_begin_transaction; + packed->base.read_alternates = odb_source_packed_read_alternates; + packed->base.write_alternate = odb_source_packed_write_alternate; + + if (!is_absolute_path(path)) + chdir_notify_register(NULL, odb_source_packed_reparent, packed); + + return packed; +} diff --git a/odb/source-packed.h b/odb/source-packed.h new file mode 100644 index 00000000000000..77309ddd0932b6 --- /dev/null +++ b/odb/source-packed.h @@ -0,0 +1,85 @@ +#ifndef ODB_SOURCE_PACKED_H +#define ODB_SOURCE_PACKED_H + +#include "odb/source.h" +#include "packfile-list.h" +#include "strmap.h" + +/* + * A store that manages packfiles for a given object database. + */ +struct odb_source_packed { + struct odb_source base; + + /* + * The list of packfiles in the order in which they have been most + * recently used. + */ + struct packfile_list packs; + + /* + * Cache of packfiles which are marked as "kept", either because there + * is an on-disk ".keep" file or because they are marked as "kept" in + * memory. + * + * Should not be accessed directly, but via + * `packfile_store_get_kept_pack_cache()`. The list of packs gets + * invalidated when the stored flags and the flags passed to + * `packfile_store_get_kept_pack_cache()` mismatch. + */ + struct { + struct packed_git **packs; + unsigned flags; + } kept_cache; + + /* The multi-pack index that belongs to this specific packfile store. */ + struct multi_pack_index *midx; + + /* + * A map of packfile names to packed_git structs for tracking which + * packs have been loaded already. + */ + struct strmap packs_by_path; + + /* + * Whether packfiles have already been populated with this store's + * packs. + */ + bool initialized; + + /* + * Usually, packfiles will be reordered to the front of the `packs` + * list whenever an object is looked up via them. This has the effect + * that packs that contain a lot of accessed objects will be located + * towards the front. + * + * 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. + * + * Setting this field to `true` thus disables these reorderings. + */ + bool skip_mru_updates; +}; + +/* + * Allocate and initialize a new empty packfile store for the given object + * database. + */ +struct odb_source_packed *odb_source_packed_new(struct object_database *odb, + const char *path, + bool local); + +/* + * Cast the given object database source to the packed backend. This will cause + * a BUG in case the source doesn't use this backend. + */ +static inline struct odb_source_packed *odb_source_packed_downcast(struct odb_source *source) +{ + if (source->type != ODB_SOURCE_PACKED) + BUG("trying to downcast source of type '%d' to packed", source->type); + return container_of(source, struct odb_source_packed, base); +} + +#endif diff --git a/odb/source.h b/odb/source.h index 0a440884e4f0ab..cd63dba91f4e2f 100644 --- a/odb/source.h +++ b/odb/source.h @@ -3,6 +3,7 @@ #include "object.h" #include "odb.h" +#include "odb/transaction.h" enum odb_source_type { /* @@ -14,6 +15,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 "packed" backend that uses packfiles. */ + ODB_SOURCE_PACKED, + /* The "in-memory" backend that stores objects in memory. */ ODB_SOURCE_INMEMORY, }; @@ -77,11 +84,12 @@ struct odb_source { void (*close)(struct odb_source *source); /* - * This callback is expected to clear underlying caches of the object - * database source. The function is called when the repository has for - * example just been repacked so that new objects will become visible. + * This callback is expected to prepare the source so that it becomes + * ready for use. It optionally clears underlying caches of the object + * database source. */ - void (*reprepare)(struct odb_source *source); + void (*prepare)(struct odb_source *source, + enum odb_prepare_flags flags); /* * This callback is expected to read object information from the object @@ -196,7 +204,7 @@ struct odb_source { * return 0 on success, a negative error code otherwise. */ int (*write_object)(struct odb_source *source, - const void *buf, unsigned long len, + const void *buf, size_t len, enum object_type type, struct object_id *oid, struct object_id *compat_oid, @@ -225,7 +233,8 @@ struct odb_source { * negative error code otherwise. */ int (*begin_transaction)(struct odb_source *source, - struct odb_transaction **out); + struct odb_transaction **out, + enum odb_transaction_flags flags); /* * This callback is expected to read the list of alternate object @@ -302,13 +311,14 @@ static inline void odb_source_close(struct odb_source *source) } /* - * Reprepare the object database source and clear any caches. Depending on the + * Prepare the object database source and clear any caches. Depending on the * backend used this may have the effect that concurrently-written objects * become visible. */ -static inline void odb_source_reprepare(struct odb_source *source) +static inline void odb_source_prepare(struct odb_source *source, + enum odb_prepare_flags flags) { - source->reprepare(source); + source->prepare(source, flags); } /* @@ -341,7 +351,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 @@ -464,9 +474,10 @@ static inline int odb_source_write_alternate(struct odb_source *source, * Returns 0 on success, a negative error code otherwise. */ static inline int odb_source_begin_transaction(struct odb_source *source, - struct odb_transaction **out) + struct odb_transaction **out, + enum odb_transaction_flags flags) { - return source->begin_transaction(source, out); + return source->begin_transaction(source, out, flags); } #endif diff --git a/odb/streaming.c b/odb/streaming.c index 7602a8d5d87519..20531e864c9561 100644 --- a/odb/streaming.c +++ b/odb/streaming.c @@ -157,26 +157,15 @@ static int open_istream_incore(struct odb_read_stream **out, .base.read = read_istream_incore, }; struct odb_incore_read_stream *st; - unsigned long size_ul; int ret; oi.typep = &stream.base.type; - /* - * object_info.sizep is unsigned long* (32-bit on Windows), but - * stream.base.size is size_t (64-bit). We use a temporary variable - * because the types are incompatible. Note: this path still truncates - * for >4GB objects, but large objects should use pack streaming - * (packfile_store_read_object_stream) which handles size_t properly. - * This incore fallback is only used for small objects or when pack - * streaming is unavailable. - */ - oi.sizep = &size_ul; + oi.sizep = &stream.base.size; oi.contentp = (void **)&stream.buf; ret = odb_read_object_info_extended(odb, oid, &oi, OBJECT_INFO_DIE_IF_CORRUPT); if (ret) return ret; - stream.base.size = size_ul; CALLOC_ARRAY(st, 1); *st = stream; diff --git a/odb/transaction.c b/odb/transaction.c index b16e07aebfc5ac..dab7da6a9a4f55 100644 --- a/odb/transaction.c +++ b/odb/transaction.c @@ -1,30 +1,41 @@ #include "git-compat-util.h" +#include "gettext.h" #include "odb/source.h" #include "odb/transaction.h" -struct odb_transaction *odb_transaction_begin(struct object_database *odb) +int odb_transaction_begin(struct object_database *odb, + struct odb_transaction **out, + enum odb_transaction_flags flags) { + int ret; + if (odb->transaction) - return NULL; + return error(_("object database transaction already pending")); - odb_source_begin_transaction(odb->sources, &odb->transaction); + ret = odb_source_begin_transaction(odb->sources, out, flags); + if (!ret) + odb->transaction = *out; - return odb->transaction; + return ret; } -void odb_transaction_commit(struct odb_transaction *transaction) +int odb_transaction_commit(struct odb_transaction *transaction) { + int ret; + if (!transaction) - return; + return 0; /* * Ensure the transaction ending matches the pending transaction. */ ASSERT(transaction == transaction->source->odb->transaction); - transaction->commit(transaction); + ret = transaction->commit(transaction); transaction->source->odb->transaction = NULL; free(transaction); + + return ret; } int odb_transaction_write_object_stream(struct odb_transaction *transaction, @@ -33,3 +44,11 @@ int odb_transaction_write_object_stream(struct odb_transaction *transaction, { return transaction->write_object_stream(transaction, stream, len, oid); } + +int odb_transaction_env(struct odb_transaction *transaction, struct strvec *env) +{ + if (!transaction) + return 0; + + return transaction->env(transaction, env); +} diff --git a/odb/transaction.h b/odb/transaction.h index 854fda06f576e4..4cb2eafcbf08f5 100644 --- a/odb/transaction.h +++ b/odb/transaction.h @@ -1,8 +1,8 @@ #ifndef ODB_TRANSACTION_H #define ODB_TRANSACTION_H +#include "gettext.h" #include "odb.h" -#include "odb/source.h" /* * A transaction may be started for an object database prior to writing new @@ -16,8 +16,11 @@ 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); + /* + * The ODB source specific callback invoked to commit a transaction. + * Returns 0 on success, a negative error code otherwise. + */ + int (*commit)(struct odb_transaction *transaction); /* * This callback is expected to write the given object stream into @@ -30,20 +33,47 @@ struct odb_transaction { int (*write_object_stream)(struct odb_transaction *transaction, struct odb_write_stream *stream, size_t len, struct object_id *oid); + + /* + * This callback is expected to populate the provided strvec with the + * environment variables that a child process should inherit so that its + * object writes participate in the transaction. Returns 0 on success, a + * negative error code otherwise. + */ + int (*env)(struct odb_transaction *transaction, struct strvec *env); +}; + +/* Flags used to configure an ODB transaction. */ +enum odb_transaction_flags { + /* Configures the transaction for use with git-receive-pack(1). */ + ODB_TRANSACTION_RECEIVE = (1 << 0), }; /* - * 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. + * Starts an ODB transaction and returns it via `out`. Subsequent objects are + * written to the transaction and not committed until odb_transaction_commit() + * is invoked on the transaction. Returns 0 on success and a negative value on + * error. Note that it is considered an error to start a new transaction if the + * ODB already has an inflight transaction pending. */ -struct odb_transaction *odb_transaction_begin(struct object_database *odb); +int odb_transaction_begin(struct object_database *odb, + struct odb_transaction **out, + enum odb_transaction_flags flags); + +static inline void odb_transaction_begin_or_die(struct object_database *odb, + struct odb_transaction **out, + enum odb_transaction_flags flags) +{ + if (odb_transaction_begin(odb, out, flags)) + die(_("failed to start ODB transaction")); +} /* - * Commits an ODB transaction making the written objects visible. If the - * specified transaction is NULL, the function is a no-op. + * Commits an ODB transaction making the written objects visible. Returns 0 on + * success, a negative error code otherwise. Note that, if the specified + * transaction is NULL, the function is a no-op and no error is returned. */ -void odb_transaction_commit(struct odb_transaction *transaction); +int odb_transaction_commit(struct odb_transaction *transaction); /* * Writes the object in the provided stream into the transaction. The resulting @@ -54,4 +84,13 @@ int odb_transaction_write_object_stream(struct odb_transaction *transaction, struct odb_write_stream *stream, size_t len, struct object_id *oid); +/* + * Populates the provided strvec with the environment variables that a child + * process should inherit so that its object writes participate in the + * transaction, suitable for using via child_process.env. Returns 0 on success, + * a negative error code otherwise. Note that, if the specified transaction is + * NULL, the function is a no-op and no error is returned. + */ +int odb_transaction_env(struct odb_transaction *transaction, struct strvec *env); + #endif diff --git a/oss-fuzz/.gitignore b/oss-fuzz/.gitignore index f2d74de457259d..dc7a127a62c723 100644 --- a/oss-fuzz/.gitignore +++ b/oss-fuzz/.gitignore @@ -5,4 +5,5 @@ fuzz-date fuzz-pack-headers fuzz-pack-idx fuzz-parse-attr-line +fuzz-reftable fuzz-url-decode-mem diff --git a/oss-fuzz/fuzz-reftable.c b/oss-fuzz/fuzz-reftable.c new file mode 100644 index 00000000000000..c46eac2c6bce62 --- /dev/null +++ b/oss-fuzz/fuzz-reftable.c @@ -0,0 +1,74 @@ +#include "git-compat-util.h" +#include "reftable/basics.h" +#include "reftable/blocksource.h" +#include "reftable/reftable-blocksource.h" +#include "reftable/reftable-error.h" +#include "reftable/reftable-iterator.h" +#include "reftable/reftable-record.h" +#include "reftable/reftable-table.h" +#include "reftable/reftable-writer.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + struct reftable_block_source source = { 0 }; + struct reftable_buf buf = REFTABLE_BUF_INIT; + struct reftable_table *table = NULL; + int err; + + if (reftable_buf_add(&buf, (const char *)data, size) < 0) + goto out; + block_source_from_buf(&source, &buf); + + err = reftable_table_new(&table, &source, "fuzz-input"); + if (err < 0) + goto out; + + /* + * Exercise the ref, log and raw block iterators so that we cover as + * much of the parsing code as possible. + */ + { + struct reftable_ref_record ref = { 0 }; + struct reftable_iterator it = { 0 }; + + reftable_table_init_ref_iterator(table, &it); + if (!reftable_iterator_seek_ref(&it, "")) + while (!reftable_iterator_next_ref(&it, &ref)) + ; + + reftable_ref_record_release(&ref); + reftable_iterator_destroy(&it); + } + + { + struct reftable_log_record log = { 0 }; + struct reftable_iterator it = { 0 }; + + reftable_table_init_log_iterator(table, &it); + if (!reftable_iterator_seek_log(&it, "")) + while (!reftable_iterator_next_log(&it, &log)) + ; + + reftable_log_record_release(&log); + reftable_iterator_destroy(&it); + } + + { + struct reftable_table_iterator it = { 0 }; + const struct reftable_block *block; + + if (!reftable_table_iterator_init(&it, table)) + while (!reftable_table_iterator_next(&it, &block)) + ; + + reftable_table_iterator_release(&it); + } + +out: + if (table) + reftable_table_decref(table); + reftable_buf_release(&buf); + return 0; +} diff --git a/oss-fuzz/meson.build b/oss-fuzz/meson.build index 878afd8426fd01..5a3854256b86e6 100644 --- a/oss-fuzz/meson.build +++ b/oss-fuzz/meson.build @@ -6,6 +6,7 @@ fuzz_programs = [ 'fuzz-pack-headers.c', 'fuzz-pack-idx.c', 'fuzz-parse-attr-line.c', + 'fuzz-reftable.c', 'fuzz-url-decode-mem.c', ] @@ -16,5 +17,6 @@ foreach fuzz_program : fuzz_programs fuzz_program, ], dependencies: [libgit_commonmain], + link_args: get_option('fuzzers_link_args'), ) endforeach diff --git a/pack-bitmap-write.c b/pack-bitmap-write.c index 1bcb3f98a42518..acbea890313b21 100644 --- a/pack-bitmap-write.c +++ b/pack-bitmap-write.c @@ -636,6 +636,8 @@ static int fill_bitmap_commit(struct bitmap_writer *writer, struct bitmap_index *old_bitmap, const uint32_t *mapping) { + struct commit *c; + struct tree *t; int found; int from_pseudo_merge = commit->object.flags & BITMAP_PSEUDO_MERGE; uint32_t pos; @@ -650,9 +652,8 @@ static int fill_bitmap_commit(struct bitmap_writer *writer, prio_queue_put(queue, commit); - while (queue->nr) { + while ((c = prio_queue_get(queue))) { struct commit_list *p; - struct commit *c = prio_queue_get(queue); if (old_bitmap && mapping) { struct ewah_bitmap *old; @@ -740,8 +741,7 @@ static int fill_bitmap_commit(struct bitmap_writer *writer, } } - while (tree_queue->nr) { - struct tree *t = prio_queue_get(tree_queue); + while ((t = prio_queue_get(tree_queue))) { int found; pos = find_object_pos(writer, &t->object.oid, &found); diff --git a/pack-bitmap.c b/pack-bitmap.c index f9af8a96bdf4ee..d8dc4ae8d1633c 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -238,7 +238,7 @@ static uint32_t bitmap_name_hash(struct bitmap_index *index, uint32_t pos) static struct repository *bitmap_repo(struct bitmap_index *bitmap_git) { if (bitmap_is_midx(bitmap_git)) - return bitmap_git->midx->source->odb->repo; + return bitmap_git->midx->source->base.odb->repo; return bitmap_git->pack->repo; } @@ -460,8 +460,8 @@ char *pack_bitmap_filename(struct packed_git *p) return xstrfmt("%.*s.bitmap", (int)len, p->pack_name); } -static int open_midx_bitmap_1(struct bitmap_index *bitmap_git, - struct multi_pack_index *midx) +static int open_midx_bitmap(struct bitmap_index *bitmap_git, + struct multi_pack_index *midx) { struct stat st; char *bitmap_name = midx_bitmap_filename(midx); @@ -523,6 +523,10 @@ static int open_midx_bitmap_1(struct bitmap_index *bitmap_git, if (midx->base_midx) { bitmap_git->base = prepare_midx_bitmap_git(midx->base_midx); + if (!bitmap_git->base) { + warning(_("could not open bitmap for base MIDX")); + goto cleanup; + } bitmap_git->base_nr = bitmap_git->base->base_nr + 1; } else { bitmap_git->base_nr = 0; @@ -539,7 +543,7 @@ static int open_midx_bitmap_1(struct bitmap_index *bitmap_git, return -1; } -static int open_pack_bitmap_1(struct bitmap_index *bitmap_git, struct packed_git *packfile) +static int open_pack_bitmap(struct bitmap_index *bitmap_git, struct packed_git *packfile) { int fd; struct stat st; @@ -603,7 +607,7 @@ static int load_reverse_index(struct repository *r, struct bitmap_index *bitmap_ /* * The multi-pack-index's .rev file is already loaded via - * open_pack_bitmap_1(). + * open_pack_bitmap(). * * But we still need to open the individual pack .rev files, * since we will need to make use of them in pack-objects. @@ -680,59 +684,53 @@ static int load_bitmap(struct repository *r, struct bitmap_index *bitmap_git, return 0; } -static int open_pack_bitmap(struct repository *r, - struct bitmap_index *bitmap_git) +static int open_bitmap_for_source(struct odb_source_packed *source, + struct bitmap_index *bitmap_git) { - struct packed_git *p; - int ret = -1; + struct multi_pack_index *midx = get_multi_pack_index(source); + struct packfile_list_entry *e; + bool found = false; - repo_for_each_pack(r, p) { - if (open_pack_bitmap_1(bitmap_git, p) == 0) { - ret = 0; - /* - * The only reason to keep looking is to report - * duplicates. - */ - if (!trace2_is_enabled()) - break; - } + if (midx && !open_midx_bitmap(bitmap_git, midx)) + found = true; + + for (e = packfile_store_get_packs(source); e; e = e->next) { + /* + * When tracing is enabled we want to keep looking to report + * duplicates even if we have already found a bitmap. + */ + if (found && !trace2_is_enabled()) + break; + + if (!open_pack_bitmap(bitmap_git, e->pack)) + found = true; } - return ret; + return found ? 0 : -1; } -static int open_midx_bitmap(struct repository *r, - struct bitmap_index *bitmap_git) +static int open_bitmap(struct repository *r, + struct bitmap_index *bitmap_git) { struct odb_source *source; - int ret = -1; + bool found = false; assert(!bitmap_git->map); odb_prepare_alternates(r->objects); for (source = r->objects->sources; source; source = source->next) { - struct multi_pack_index *midx = get_multi_pack_index(source); - if (midx && !open_midx_bitmap_1(bitmap_git, midx)) - ret = 0; - } - return ret; -} - -static int open_bitmap(struct repository *r, - struct bitmap_index *bitmap_git) -{ - int found; + struct odb_source_files *files = odb_source_files_downcast(source); - assert(!bitmap_git->map); - - found = !open_midx_bitmap(r, bitmap_git); + if (!open_bitmap_for_source(files->packed, bitmap_git)) + found = true; - /* - * these will all be skipped if we opened a midx bitmap; but run it - * anyway if tracing is enabled to report the duplicates - */ - if (!found || trace2_is_enabled()) - found |= !open_pack_bitmap(r, bitmap_git); + /* + * The only reason to keep looking after having found a bitmap + * is to report duplicates. + */ + if (found && !trace2_is_enabled()) + break; + } return found ? 0 : -1; } @@ -752,7 +750,19 @@ struct bitmap_index *prepare_midx_bitmap_git(struct multi_pack_index *midx) { struct bitmap_index *bitmap_git = xcalloc(1, sizeof(*bitmap_git)); - if (!open_midx_bitmap_1(bitmap_git, midx)) + if (!open_midx_bitmap(bitmap_git, midx)) + return bitmap_git; + + free_bitmap_index(bitmap_git); + return NULL; +} + +struct bitmap_index *prepare_bitmap_git_for_source(struct odb_source_packed *source) +{ + struct bitmap_index *bitmap_git = xcalloc(1, sizeof(*bitmap_git)); + + if (!open_bitmap_for_source(source, bitmap_git) && + !load_bitmap(source->base.odb->repo, bitmap_git, 0)) return bitmap_git; free_bitmap_index(bitmap_git); @@ -1694,7 +1704,7 @@ static void init_type_iterator(struct ewah_or_iterator *it, } } -static void show_objects_for_type( +static int show_objects_for_type( struct bitmap_index *bitmap_git, struct bitmap *objects, enum object_type object_type, @@ -1703,6 +1713,7 @@ static void show_objects_for_type( { size_t i = 0; uint32_t offset; + int ret; struct ewah_or_iterator it; eword_t filter; @@ -1748,11 +1759,17 @@ static void show_objects_for_type( hash = bitmap_name_hash(bitmap_git, index_pos); - show_reach(&oid, object_type, 0, hash, pack, ofs, payload); + ret = show_reach(&oid, object_type, 0, hash, pack, ofs, payload); + if (ret) + goto out; } } + ret = 0; + +out: ewah_or_iterator_release(&it); + return ret; } static int in_bitmapped_pack(struct bitmap_index *bitmap_git, @@ -1856,7 +1873,7 @@ static void filter_bitmap_blob_none(struct bitmap_index *bitmap_git, static unsigned long get_size_by_pos(struct bitmap_index *bitmap_git, uint32_t pos) { - unsigned long size; + size_t size; struct object_info oi = OBJECT_INFO_INIT; oi.sizep = &size; @@ -1876,7 +1893,7 @@ static unsigned long get_size_by_pos(struct bitmap_index *bitmap_git, ofs = pack_pos_to_offset(pack, pos); } - if (packed_object_info(pack, ofs, &oi) < 0) { + if (packed_object_info(NULL, pack, ofs, &oi) < 0) { struct object_id oid; nth_bitmap_object_oid(bitmap_git, &oid, pack_pos_to_index(pack, pos)); @@ -1891,7 +1908,7 @@ static unsigned long get_size_by_pos(struct bitmap_index *bitmap_git, die(_("unable to get size of %s"), oid_to_hex(&obj->oid)); } - return size; + return cast_size_t_to_ulong(size); } static void filter_bitmap_blob_limit(struct bitmap_index *bitmap_git, @@ -1975,7 +1992,7 @@ static void filter_bitmap_object_type(struct bitmap_index *bitmap_git, static int filter_bitmap(struct bitmap_index *bitmap_git, struct object_list *tip_objects, struct bitmap *to_filter, - struct list_objects_filter_options *filter) + const struct list_objects_filter_options *filter) { if (!filter || filter->choice == LOFC_DISABLED) return 0; @@ -2026,12 +2043,11 @@ static int filter_bitmap(struct bitmap_index *bitmap_git, return -1; } -static int can_filter_bitmap(struct list_objects_filter_options *filter) +bool can_filter_bitmap(const struct list_objects_filter_options *filter) { return !filter_bitmap(NULL, NULL, NULL, filter); } - static void filter_packed_objects_from_bitmap(struct bitmap_index *bitmap_git, struct bitmap *result) { @@ -2057,10 +2073,16 @@ static void filter_packed_objects_from_bitmap(struct bitmap_index *bitmap_git, } int for_each_bitmapped_object(struct bitmap_index *bitmap_git, - struct list_objects_filter_options *filter, + const struct list_objects_filter_options *filter, show_reachable_fn show_reach, void *payload) { + const enum object_type types[] = { + OBJ_COMMIT, + OBJ_TREE, + OBJ_BLOB, + OBJ_TAG, + }; struct bitmap *filtered_bitmap = NULL; uint32_t objects_nr; size_t full_word_count; @@ -2085,14 +2107,12 @@ int for_each_bitmapped_object(struct bitmap_index *bitmap_git, goto out; } - show_objects_for_type(bitmap_git, filtered_bitmap, - OBJ_COMMIT, show_reach, payload); - show_objects_for_type(bitmap_git, filtered_bitmap, - OBJ_TREE, show_reach, payload); - show_objects_for_type(bitmap_git, filtered_bitmap, - OBJ_BLOB, show_reach, payload); - show_objects_for_type(bitmap_git, filtered_bitmap, - OBJ_TAG, show_reach, payload); + for (size_t i = 0; i < ARRAY_SIZE(types); i++) { + ret = show_objects_for_type(bitmap_git, filtered_bitmap, + types[i], show_reach, payload); + if (ret) + goto out; + } ret = 0; out: @@ -3399,7 +3419,8 @@ int verify_bitmap_files(struct repository *r) odb_prepare_alternates(r->objects); for (source = r->objects->sources; source; source = source->next) { - struct multi_pack_index *m = get_multi_pack_index(source); + struct odb_source_files *files = odb_source_files_downcast(source); + struct multi_pack_index *m = get_multi_pack_index(files->packed); char *midx_bitmap_name; if (!m) diff --git a/pack-bitmap.h b/pack-bitmap.h index 19a86554579f7c..1385027c1ff5fa 100644 --- a/pack-bitmap.h +++ b/pack-bitmap.h @@ -9,6 +9,7 @@ #include "string-list.h" struct commit; +struct odb_source_packed; struct repository; struct rev_info; @@ -68,6 +69,7 @@ struct bitmapped_pack { struct bitmap_index *prepare_bitmap_git(struct repository *r); struct bitmap_index *prepare_midx_bitmap_git(struct multi_pack_index *midx); +struct bitmap_index *prepare_bitmap_git_for_source(struct odb_source_packed *source); /* * Given a bitmap index, determine whether it contains the pack either directly @@ -90,13 +92,17 @@ int test_bitmap_pseudo_merge_objects(struct repository *r, uint32_t n); struct list_objects_filter_options; +/* Check whether the filter can be computed via the bitmap. */ +bool can_filter_bitmap(const struct list_objects_filter_options *filter); + /* * Filter bitmapped objects and iterate through all resulting objects, * executing `show_reach` for each of them. Returns `-1` in case the filter is - * not supported, `0` otherwise. + * not supported, `0` otherwise. Aborts iteration and bubbles up the return + * value in case `show_reach()` returns non-zero. */ int for_each_bitmapped_object(struct bitmap_index *bitmap_git, - struct list_objects_filter_options *filter, + const struct list_objects_filter_options *filter, show_reachable_fn show_reach, void *payload); diff --git a/pack-check.c b/pack-check.c index 2792f34d2595bf..c3b8db7c5c41a6 100644 --- a/pack-check.c +++ b/pack-check.c @@ -69,7 +69,7 @@ static int verify_packfile(struct repository *r, if (!is_pack_valid(p)) return error("packfile %s cannot be accessed", p->pack_name); - r->hash_algo->init_fn(&ctx); + git_hash_init(&ctx, r->hash_algo); do { unsigned long remaining; unsigned char *in = use_pack(p, w_curs, offset, &remaining); @@ -143,9 +143,8 @@ static int verify_packfile(struct repository *r, data = NULL; data_valid = 0; } else { - unsigned long sz; - data = unpack_entry(r, p, entries[i].offset, &type, &sz); - size = sz; + data = unpack_entry(r, p, entries[i].offset, &type, + &size); data_valid = 1; } diff --git a/pack-objects.h b/pack-objects.h index 83299d47324f1e..e97e84ddcb9d5b 100644 --- a/pack-objects.h +++ b/pack-objects.h @@ -141,7 +141,7 @@ struct packing_data { uint32_t index_size; unsigned int *in_pack_pos; - unsigned long *delta_size; + size_t *delta_size; /* * Only one of these can be non-NULL and they have different diff --git a/pack-revindex.c b/pack-revindex.c index 1b67863606a75f..62387ae6320181 100644 --- a/pack-revindex.c +++ b/pack-revindex.c @@ -383,13 +383,13 @@ int load_midx_revindex(struct multi_pack_index *m) * not want to accidentally call munmap() in the middle of the * MIDX. */ - trace2_data_string("load_midx_revindex", m->source->odb->repo, + trace2_data_string("load_midx_revindex", m->source->base.odb->repo, "source", "midx"); m->revindex_data = (const uint32_t *)m->chunk_revindex; return 0; } - trace2_data_string("load_midx_revindex", m->source->odb->repo, + trace2_data_string("load_midx_revindex", m->source->base.odb->repo, "source", "rev"); if (m->has_chain) @@ -401,7 +401,7 @@ int load_midx_revindex(struct multi_pack_index *m) midx_get_checksum_hash(m), MIDX_EXT_REV); - ret = load_revindex_from_disk(m->source->odb->repo->hash_algo, + ret = load_revindex_from_disk(m->source->base.odb->repo->hash_algo, revindex_name.buf, m->num_objects, &m->revindex_map, diff --git a/pack-write.c b/pack-write.c index 83eaf88541eefb..24033a9101545a 100644 --- a/pack-write.c +++ b/pack-write.c @@ -402,8 +402,8 @@ void fixup_pack_header_footer(const struct git_hash_algo *hash_algo, char *buf; ssize_t read_result; - hash_algo->init_fn(&old_hash_ctx); - hash_algo->init_fn(&new_hash_ctx); + git_hash_init(&old_hash_ctx, hash_algo); + git_hash_init(&new_hash_ctx, hash_algo); if (lseek(pack_fd, 0, SEEK_SET) != 0) die_errno("Failed seeking to start of '%s'", pack_name); @@ -455,7 +455,7 @@ void fixup_pack_header_footer(const struct git_hash_algo *hash_algo, * pack, which also means making partial_pack_offset * big enough not to matter anymore. */ - hash_algo->init_fn(&old_hash_ctx); + git_hash_init(&old_hash_ctx, hash_algo); partial_pack_offset = ~partial_pack_offset; partial_pack_offset -= MSB(partial_pack_offset, 1); } diff --git a/packfile-list.c b/packfile-list.c new file mode 100644 index 00000000000000..01fb913abf78fc --- /dev/null +++ b/packfile-list.c @@ -0,0 +1,86 @@ +#include "git-compat-util.h" +#include "packfile.h" +#include "packfile-list.h" + +void packfile_list_clear(struct packfile_list *list) +{ + struct packfile_list_entry *e, *next; + + for (e = list->head; e; e = next) { + next = e->next; + free(e); + } + + list->head = list->tail = NULL; +} + +static struct packfile_list_entry *packfile_list_remove_internal(struct packfile_list *list, + struct packed_git *pack) +{ + struct packfile_list_entry *e, *prev; + + for (e = list->head, prev = NULL; e; prev = e, e = e->next) { + if (e->pack != pack) + continue; + + if (prev) + prev->next = e->next; + if (list->head == e) + list->head = e->next; + if (list->tail == e) + list->tail = prev; + + return e; + } + + return NULL; +} + +void packfile_list_remove(struct packfile_list *list, struct packed_git *pack) +{ + free(packfile_list_remove_internal(list, pack)); +} + +void packfile_list_prepend(struct packfile_list *list, struct packed_git *pack) +{ + struct packfile_list_entry *entry; + + entry = packfile_list_remove_internal(list, pack); + if (!entry) { + entry = xmalloc(sizeof(*entry)); + entry->pack = pack; + } + entry->next = list->head; + + list->head = entry; + if (!list->tail) + list->tail = entry; +} + +void packfile_list_append(struct packfile_list *list, struct packed_git *pack) +{ + struct packfile_list_entry *entry; + + entry = packfile_list_remove_internal(list, pack); + if (!entry) { + entry = xmalloc(sizeof(*entry)); + entry->pack = pack; + } + entry->next = NULL; + + if (list->tail) { + list->tail->next = entry; + list->tail = entry; + } else { + list->head = list->tail = entry; + } +} + +struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs, + const struct object_id *oid) +{ + for (; packs; packs = packs->next) + if (find_pack_entry_one(oid, packs->pack)) + return packs->pack; + return NULL; +} diff --git a/packfile-list.h b/packfile-list.h new file mode 100644 index 00000000000000..1b05e2aa36de08 --- /dev/null +++ b/packfile-list.h @@ -0,0 +1,28 @@ +#ifndef PACKFILE_LIST_H +#define PACKFILE_LIST_H + +struct object_id; + +struct packfile_list { + struct packfile_list_entry *head, *tail; +}; + +struct packfile_list_entry { + struct packfile_list_entry *next; + struct packed_git *pack; +}; + +void packfile_list_clear(struct packfile_list *list); +void packfile_list_remove(struct packfile_list *list, struct packed_git *pack); +void packfile_list_prepend(struct packfile_list *list, struct packed_git *pack); +void packfile_list_append(struct packfile_list *list, struct packed_git *pack); + +/* + * Find the pack within the "packs" list whose index contains the object + * "oid". For general object lookups, you probably don't want this; use + * find_pack_entry() instead. + */ +struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs, + const struct object_id *oid); + +#endif diff --git a/packfile.c b/packfile.c index 89366abfe32386..0eee45055f833e 100644 --- a/packfile.c +++ b/packfile.c @@ -8,7 +8,6 @@ #include "pack.h" #include "repository.h" #include "dir.h" -#include "mergesort.h" #include "packfile.h" #include "delta.h" #include "hash-lookup.h" @@ -48,89 +47,6 @@ static size_t pack_mapped; #define SZ_FMT PRIuMAX static inline uintmax_t sz_fmt(size_t s) { return s; } -void packfile_list_clear(struct packfile_list *list) -{ - struct packfile_list_entry *e, *next; - - for (e = list->head; e; e = next) { - next = e->next; - free(e); - } - - list->head = list->tail = NULL; -} - -static struct packfile_list_entry *packfile_list_remove_internal(struct packfile_list *list, - struct packed_git *pack) -{ - struct packfile_list_entry *e, *prev; - - for (e = list->head, prev = NULL; e; prev = e, e = e->next) { - if (e->pack != pack) - continue; - - if (prev) - prev->next = e->next; - if (list->head == e) - list->head = e->next; - if (list->tail == e) - list->tail = prev; - - return e; - } - - return NULL; -} - -void packfile_list_remove(struct packfile_list *list, struct packed_git *pack) -{ - free(packfile_list_remove_internal(list, pack)); -} - -void packfile_list_prepend(struct packfile_list *list, struct packed_git *pack) -{ - struct packfile_list_entry *entry; - - entry = packfile_list_remove_internal(list, pack); - if (!entry) { - entry = xmalloc(sizeof(*entry)); - entry->pack = pack; - } - entry->next = list->head; - - list->head = entry; - if (!list->tail) - list->tail = entry; -} - -void packfile_list_append(struct packfile_list *list, struct packed_git *pack) -{ - struct packfile_list_entry *entry; - - entry = packfile_list_remove_internal(list, pack); - if (!entry) { - entry = xmalloc(sizeof(*entry)); - entry->pack = pack; - } - entry->next = NULL; - - if (list->tail) { - list->tail->next = entry; - list->tail = entry; - } else { - list->head = list->tail = entry; - } -} - -struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs, - const struct object_id *oid) -{ - for (; packs; packs = packs->next) - if (find_pack_entry_one(oid, packs->pack)) - return packs->pack; - return NULL; -} - void pack_report(struct repository *repo) { fprintf(stderr, @@ -859,7 +775,7 @@ struct packed_git *add_packed_git(struct repository *r, const char *path, return p; } -void packfile_store_add_pack(struct packfile_store *store, +void packfile_store_add_pack(struct odb_source_packed *store, struct packed_git *pack) { if (pack->pack_fd != -1) @@ -869,7 +785,7 @@ void packfile_store_add_pack(struct packfile_store *store, strmap_put(&store->packs_by_path, pack->pack_name, pack); } -struct packed_git *packfile_store_load_pack(struct packfile_store *store, +struct packed_git *packfile_store_load_pack(struct odb_source_packed *store, const char *idx_path, int local) { struct strbuf key = STRBUF_INIT; @@ -885,7 +801,7 @@ struct packed_git *packfile_store_load_pack(struct packfile_store *store, p = strmap_get(&store->packs_by_path, key.buf); if (!p) { - p = add_packed_git(store->source->odb->repo, idx_path, + p = add_packed_git(store->base.odb->repo, idx_path, strlen(idx_path), local); if (p) packfile_store_add_pack(store, p); @@ -895,52 +811,6 @@ struct packed_git *packfile_store_load_pack(struct packfile_store *store, return p; } -void (*report_garbage)(unsigned seen_bits, const char *path); - -static void report_helper(const struct string_list *list, - int seen_bits, int first, int last) -{ - if (seen_bits == (PACKDIR_FILE_PACK|PACKDIR_FILE_IDX)) - return; - - for (; first < last; first++) - report_garbage(seen_bits, list->items[first].string); -} - -static void report_pack_garbage(struct string_list *list) -{ - int i, baselen = -1, first = 0, seen_bits = 0; - - if (!report_garbage) - return; - - string_list_sort(list); - - for (i = 0; i < list->nr; i++) { - const char *path = list->items[i].string; - if (baselen != -1 && - strncmp(path, list->items[first].string, baselen)) { - report_helper(list, seen_bits, first, i); - baselen = -1; - seen_bits = 0; - } - if (baselen == -1) { - const char *dot = strrchr(path, '.'); - if (!dot) { - report_garbage(PACKDIR_FILE_GARBAGE, path); - continue; - } - baselen = dot - path + 1; - first = i; - } - if (!strcmp(path + baselen, "pack")) - seen_bits |= 1; - else if (!strcmp(path + baselen, "idx")) - seen_bits |= 2; - } - report_helper(list, seen_bits, first, list->nr); -} - void for_each_file_in_pack_subdir(const char *objdir, const char *subdir, each_file_in_pack_dir_fn fn, @@ -983,116 +853,9 @@ void for_each_file_in_pack_dir(const char *objdir, for_each_file_in_pack_subdir(objdir, NULL, fn, data); } -struct prepare_pack_data { - struct odb_source *source; - struct string_list *garbage; -}; - -static void prepare_pack(const char *full_name, size_t full_name_len, - const char *file_name, void *_data) -{ - struct prepare_pack_data *data = (struct prepare_pack_data *)_data; - struct odb_source_files *files = odb_source_files_downcast(data->source); - size_t base_len = full_name_len; - - if (strip_suffix_mem(full_name, &base_len, ".idx") && - !(files->packed->midx && - midx_contains_pack(files->packed->midx, file_name))) { - char *trimmed_path = xstrndup(full_name, full_name_len); - packfile_store_load_pack(files->packed, - trimmed_path, data->source->local); - free(trimmed_path); - } - - if (!report_garbage) - return; - - if (!strcmp(file_name, "multi-pack-index") || - !strcmp(file_name, "multi-pack-index.d")) - return; - if (starts_with(file_name, "multi-pack-index") && - (ends_with(file_name, ".bitmap") || ends_with(file_name, ".rev"))) - return; - if (ends_with(file_name, ".idx") || - ends_with(file_name, ".rev") || - ends_with(file_name, ".pack") || - ends_with(file_name, ".bitmap") || - ends_with(file_name, ".keep") || - ends_with(file_name, ".promisor") || - ends_with(file_name, ".mtimes")) - string_list_append(data->garbage, full_name); - else - report_garbage(PACKDIR_FILE_GARBAGE, full_name); -} - -static void prepare_packed_git_one(struct odb_source *source) -{ - struct string_list garbage = STRING_LIST_INIT_DUP; - struct prepare_pack_data data = { - .source = source, - .garbage = &garbage, - }; - - for_each_file_in_pack_dir(source->path, prepare_pack, &data); - - report_pack_garbage(data.garbage); - string_list_clear(data.garbage, 0); -} - -DEFINE_LIST_SORT(static, sort_packs, struct packfile_list_entry, next); - -static int sort_pack(const struct packfile_list_entry *a, - const struct packfile_list_entry *b) -{ - int st; - - /* - * Local packs tend to contain objects specific to our - * variant of the project than remote ones. In addition, - * remote ones could be on a network mounted filesystem. - * Favor local ones for these reasons. - */ - st = a->pack->pack_local - b->pack->pack_local; - if (st) - return -st; - - /* - * Younger packs tend to contain more recent objects, - * and more recent objects tend to get accessed more - * often. - */ - if (a->pack->mtime < b->pack->mtime) - return 1; - else if (a->pack->mtime == b->pack->mtime) - return 0; - return -1; -} - -void packfile_store_prepare(struct packfile_store *store) +struct packfile_list_entry *packfile_store_get_packs(struct odb_source_packed *store) { - if (store->initialized) - return; - - prepare_multi_pack_index_one(store->source); - prepare_packed_git_one(store->source); - - sort_packs(&store->packs.head, sort_pack); - for (struct packfile_list_entry *e = store->packs.head; e; e = e->next) - if (!e->next) - store->packs.tail = e; - - store->initialized = true; -} - -void packfile_store_reprepare(struct packfile_store *store) -{ - store->initialized = false; - packfile_store_prepare(store); -} - -struct packfile_list_entry *packfile_store_get_packs(struct packfile_store *store) -{ - packfile_store_prepare(store); + odb_source_prepare(&store->base, 0); if (store->midx) { struct multi_pack_index *m = store->midx; @@ -1103,37 +866,6 @@ struct packfile_list_entry *packfile_store_get_packs(struct packfile_store *stor return store->packs.head; } -int packfile_store_count_objects(struct packfile_store *store, - enum odb_count_objects_flags flags UNUSED, - unsigned long *out) -{ - struct packfile_list_entry *e; - struct multi_pack_index *m; - unsigned long count = 0; - int ret; - - m = get_multi_pack_index(store->source); - if (m) - count += m->num_objects + m->num_objects_in_base; - - for (e = packfile_store_get_packs(store); e; e = e->next) { - if (e->pack->multi_pack_index) - continue; - if (open_pack_index(e->pack)) { - ret = -1; - goto out; - } - - count += e->pack->num_objects; - } - - *out = count; - ret = 0; - -out: - return ret; -} - unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, size_t *sizep) { @@ -1164,11 +896,12 @@ unsigned long unpack_object_header_buffer(const unsigned char *buf, } /* - * Size_t variant for >4GB delta results on Windows. + * Read a delta object's header at curpos in p (already inflated as needed) + * and return the size of the result object (the post-application target). */ -static size_t get_size_from_delta_sz(struct packed_git *p, - struct pack_window **w_curs, - off_t curpos) +size_t get_size_from_delta(struct packed_git *p, + struct pack_window **w_curs, + off_t curpos) { const unsigned char *data; unsigned char delta_head[20], *in; @@ -1215,18 +948,10 @@ static size_t get_size_from_delta_sz(struct packed_git *p, data = delta_head; /* ignore base size */ - get_delta_hdr_size_sz(&data, delta_head+sizeof(delta_head)); + get_delta_hdr_size(&data, delta_head+sizeof(delta_head)); /* Read the result size */ - return get_delta_hdr_size_sz(&data, delta_head+sizeof(delta_head)); -} - -unsigned long get_size_from_delta(struct packed_git *p, - struct pack_window **w_curs, - off_t curpos) -{ - size_t size = get_size_from_delta_sz(p, w_curs, curpos); - return cast_size_t_to_ulong(size); + return get_delta_hdr_size(&data, delta_head+sizeof(delta_head)); } int unpack_object_header(struct packed_git *p, @@ -1454,7 +1179,7 @@ struct delta_base_cache_entry { struct delta_base_cache_key key; struct list_head lru; void *data; - unsigned long size; + size_t size; enum object_type type; }; @@ -1525,7 +1250,7 @@ static void detach_delta_base_cache_entry(struct delta_base_cache_entry *ent) } static void *cache_or_unpack_entry(struct repository *r, struct packed_git *p, - off_t base_offset, unsigned long *base_size, + off_t base_offset, size_t *base_size, enum object_type *type) { struct delta_base_cache_entry *ent; @@ -1558,8 +1283,8 @@ void clear_delta_base_cache(void) } static void add_delta_base_cache(struct packed_git *p, off_t base_offset, - void *base, unsigned long base_size, - unsigned long delta_base_cache_limit, + void *base, size_t base_size, + size_t delta_base_cache_limit, enum object_type type) { struct delta_base_cache_entry *ent; @@ -1599,8 +1324,9 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset, hashmap_add(&delta_base_cache, &ent->ent); } -static int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_offset, - uint32_t *maybe_index_pos, struct object_info *oi) +int packed_object_info_with_index_pos(struct odb_source_packed *source, + struct packed_git *p, off_t obj_offset, + uint32_t *maybe_index_pos, struct object_info *oi) { struct pack_window *w_curs = NULL; size_t size; @@ -1614,8 +1340,8 @@ static int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_off * a "real" type later if the caller is interested. */ if (oi->contentp) { - *oi->contentp = cache_or_unpack_entry(p->repo, p, obj_offset, oi->sizep, - &type); + *oi->contentp = cache_or_unpack_entry(p->repo, p, obj_offset, + oi->sizep, &type); if (!*oi->contentp) type = OBJ_BAD; } else if (oi->sizep || oi->typep || oi->delta_base_oid) { @@ -1631,18 +1357,13 @@ static int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_off ret = -1; goto out; } - /* - * Use size_t variant to avoid die() on >4GB deltas. - * oi->sizep is unsigned long, so truncation may occur, - * but streaming code uses its own size_t tracking. - */ - size = get_size_from_delta_sz(p, &w_curs, tmp_pos); + size = get_size_from_delta(p, &w_curs, tmp_pos); if (size == 0) { ret = -1; goto out; } } - *oi->sizep = (unsigned long)size; + *oi->sizep = size; } if (oi->disk_sizep || (oi->mtimep && p->is_cruft)) { @@ -1700,23 +1421,28 @@ static int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_off oidclr(oi->delta_base_oid, p->repo->hash_algo); } - oi->whence = OI_PACKED; - oi->u.packed.offset = obj_offset; - oi->u.packed.pack = p; + if (oi->source_infop) { + if (!source) + BUG("cannot request source without an owning source"); + oi->source_infop->source = &source->base; - switch (type) { - case OBJ_NONE: - oi->u.packed.type = PACKED_OBJECT_TYPE_UNKNOWN; - break; - case OBJ_REF_DELTA: - oi->u.packed.type = PACKED_OBJECT_TYPE_REF_DELTA; - break; - case OBJ_OFS_DELTA: - oi->u.packed.type = PACKED_OBJECT_TYPE_OFS_DELTA; - break; - default: - oi->u.packed.type = PACKED_OBJECT_TYPE_FULL; - break; + oi->source_infop->u.packed.offset = obj_offset; + oi->source_infop->u.packed.pack = p; + + switch (type) { + case OBJ_NONE: + oi->source_infop->u.packed.type = PACKED_OBJECT_TYPE_UNKNOWN; + break; + case OBJ_REF_DELTA: + oi->source_infop->u.packed.type = PACKED_OBJECT_TYPE_REF_DELTA; + break; + case OBJ_OFS_DELTA: + oi->source_infop->u.packed.type = PACKED_OBJECT_TYPE_OFS_DELTA; + break; + default: + oi->source_infop->u.packed.type = PACKED_OBJECT_TYPE_FULL; + break; + } } ret = 0; @@ -1726,16 +1452,17 @@ static int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_off return ret; } -int packed_object_info(struct packed_git *p, off_t obj_offset, +int packed_object_info(struct odb_source_packed *source, + struct packed_git *p, off_t obj_offset, struct object_info *oi) { - return packed_object_info_with_index_pos(p, obj_offset, NULL, oi); + return packed_object_info_with_index_pos(source, p, obj_offset, NULL, oi); } static void *unpack_compressed_entry(struct packed_git *p, struct pack_window **w_curs, off_t curpos, - unsigned long size) + size_t size) { int st; git_zstream stream; @@ -1790,11 +1517,11 @@ int do_check_packed_object_crc; struct unpack_entry_stack_ent { off_t obj_offset; off_t curpos; - unsigned long size; + size_t size; }; void *unpack_entry(struct repository *r, struct packed_git *p, off_t obj_offset, - enum object_type *final_type, unsigned long *final_size) + enum object_type *final_type, size_t *final_size) { struct pack_window *w_curs = NULL; off_t curpos = obj_offset; @@ -1911,7 +1638,7 @@ void *unpack_entry(struct repository *r, struct packed_git *p, off_t obj_offset, void *delta_data; void *base = data; void *external_base = NULL; - unsigned long delta_size, base_size = size; + size_t delta_size, base_size = size; int i; off_t base_obj_offset = obj_offset; @@ -1964,10 +1691,8 @@ void *unpack_entry(struct repository *r, struct packed_git *p, off_t obj_offset, (uintmax_t)curpos, p->pack_name); data = NULL; } else { - unsigned long sz; data = patch_delta(base, base_size, delta_data, - delta_size, &sz); - size = sz; + delta_size, &size); /* * We could not apply the delta; warn the user, but @@ -2132,9 +1857,9 @@ int is_pack_valid(struct packed_git *p) return !open_packed_git(p); } -static int fill_pack_entry(const struct object_id *oid, - struct pack_entry *e, - struct packed_git *p) +int packfile_fill_entry(struct packed_git *p, + const struct object_id *oid, + struct pack_entry *e) { off_t offset; @@ -2160,81 +1885,7 @@ static int fill_pack_entry(const struct object_id *oid, return 1; } -static int find_pack_entry(struct packfile_store *store, - const struct object_id *oid, - struct pack_entry *e) -{ - struct packfile_list_entry *l; - - packfile_store_prepare(store); - if (store->midx && fill_midx_entry(store->midx, oid, e)) - return 1; - - for (l = store->packs.head; l; l = l->next) { - struct packed_git *p = l->pack; - - if (!p->multi_pack_index && fill_pack_entry(oid, e, p)) { - if (!store->skip_mru_updates) - packfile_list_prepend(&store->packs, p); - return 1; - } - } - - return 0; -} - -int packfile_store_freshen_object(struct packfile_store *store, - const struct object_id *oid) -{ - struct pack_entry e; - if (!find_pack_entry(store, oid, &e)) - return 0; - if (e.p->is_cruft) - return 0; - if (e.p->freshened) - return 1; - if (utime(e.p->pack_name, NULL)) - return 0; - e.p->freshened = 1; - return 1; -} - -int packfile_store_read_object_info(struct packfile_store *store, - const struct object_id *oid, - struct object_info *oi, - enum object_info_flags flags) -{ - struct pack_entry e; - int ret; - - /* - * In case the first read didn't surface the object, we have to reload - * packfiles. This may cause us to discover new packfiles that have - * been added since the last time we have prepared the packfile store. - */ - if (flags & OBJECT_INFO_SECOND_READ) - packfile_store_reprepare(store); - - if (!find_pack_entry(store, oid, &e)) - return 1; - - /* - * We know that the caller doesn't actually need the - * information below, so return early. - */ - if (!oi) - return 0; - - ret = packed_object_info(e.p, e.offset, oi); - if (ret < 0) { - mark_bad_packed_object(e.p, oid); - return -1; - } - - return 0; -} - -static void maybe_invalidate_kept_pack_cache(struct packfile_store *store, +static void maybe_invalidate_kept_pack_cache(struct odb_source_packed *store, unsigned flags) { if (!store->kept_cache.packs) @@ -2245,7 +1896,7 @@ static void maybe_invalidate_kept_pack_cache(struct packfile_store *store, store->kept_cache.flags = 0; } -struct packed_git **packfile_store_get_kept_pack_cache(struct packfile_store *store, +struct packed_git **packfile_store_get_kept_pack_cache(struct odb_source_packed *store, unsigned flags) { maybe_invalidate_kept_pack_cache(store, flags); @@ -2286,14 +1937,12 @@ struct packed_git **packfile_store_get_kept_pack_cache(struct packfile_store *st int has_object_pack(struct repository *r, const struct object_id *oid) { struct odb_source *source; - struct pack_entry e; odb_prepare_alternates(r->objects); for (source = r->objects->sources; source; source = source->next) { struct odb_source_files *files = odb_source_files_downcast(source); - int ret = find_pack_entry(files->packed, oid, &e); - if (ret) - return ret; + if (!odb_source_read_object_info(&files->packed->base, oid, NULL, 0)) + return 1; } return 0; @@ -2313,7 +1962,7 @@ int has_object_kept_pack(struct repository *r, const struct object_id *oid, for (; *cache; cache++) { struct packed_git *p = *cache; - if (fill_pack_entry(oid, &e, p)) + if (packfile_fill_entry(p, oid, &e)) return 1; } } @@ -2365,373 +2014,6 @@ int for_each_object_in_pack(struct packed_git *p, return r; } -struct packfile_store_for_each_object_wrapper_data { - struct packfile_store *store; - const struct object_info *request; - odb_for_each_object_cb cb; - void *cb_data; -}; - -static int packfile_store_for_each_object_wrapper(const struct object_id *oid, - struct packed_git *pack, - uint32_t index_pos, - void *cb_data) -{ - struct packfile_store_for_each_object_wrapper_data *data = cb_data; - - if (data->request) { - off_t offset = nth_packed_object_offset(pack, index_pos); - struct object_info oi = *data->request; - - if (packed_object_info_with_index_pos(pack, offset, - &index_pos, &oi) < 0) { - mark_bad_packed_object(pack, oid); - return -1; - } - - return data->cb(oid, &oi, data->cb_data); - } else { - return data->cb(oid, NULL, data->cb_data); - } -} - -static int match_hash(unsigned len, const unsigned char *a, const unsigned char *b) -{ - do { - if (*a != *b) - return 0; - a++; - b++; - len -= 2; - } while (len > 1); - if (len) - if ((*a ^ *b) & 0xf0) - return 0; - return 1; -} - -static int for_each_prefixed_object_in_midx( - struct packfile_store *store, - struct multi_pack_index *m, - const struct odb_for_each_object_options *opts, - struct packfile_store_for_each_object_wrapper_data *data) -{ - int ret; - - for (; m; m = m->base_midx) { - uint32_t num, i, first = 0; - int len = opts->prefix_hex_len > m->source->odb->repo->hash_algo->hexsz ? - m->source->odb->repo->hash_algo->hexsz : opts->prefix_hex_len; - - if (!m->num_objects) - continue; - - num = m->num_objects + m->num_objects_in_base; - - bsearch_one_midx(opts->prefix, m, &first); - - /* - * At this point, "first" is the location of the lowest - * object with an object name that could match "opts->prefix". - * See if we have 0, 1 or more objects that actually match(es). - */ - for (i = first; i < num; i++) { - const struct object_id *current = NULL; - struct object_id oid; - - current = nth_midxed_object_oid(&oid, m, i); - - if (!match_hash(len, opts->prefix->hash, current->hash)) - break; - - if (data->request) { - struct object_info oi = *data->request; - - ret = packfile_store_read_object_info(store, current, - &oi, 0); - if (ret) - goto out; - - ret = data->cb(&oid, &oi, data->cb_data); - if (ret) - goto out; - } else { - ret = data->cb(&oid, NULL, data->cb_data); - if (ret) - goto out; - } - } - } - - ret = 0; - -out: - return ret; -} - -static int for_each_prefixed_object_in_pack( - struct packfile_store *store, - struct packed_git *p, - const struct odb_for_each_object_options *opts, - struct packfile_store_for_each_object_wrapper_data *data) -{ - uint32_t num, i, first = 0; - int len = opts->prefix_hex_len > p->repo->hash_algo->hexsz ? - p->repo->hash_algo->hexsz : opts->prefix_hex_len; - int ret; - - num = p->num_objects; - bsearch_pack(opts->prefix, p, &first); - - /* - * At this point, "first" is the location of the lowest object - * with an object name that could match "bin_pfx". See if we have - * 0, 1 or more objects that actually match(es). - */ - for (i = first; i < num; i++) { - struct object_id oid; - - nth_packed_object_id(&oid, p, i); - if (!match_hash(len, opts->prefix->hash, oid.hash)) - break; - - if (data->request) { - struct object_info oi = *data->request; - - ret = packfile_store_read_object_info(store, &oid, &oi, 0); - if (ret) - goto out; - - ret = data->cb(&oid, &oi, data->cb_data); - if (ret) - goto out; - } else { - ret = data->cb(&oid, NULL, data->cb_data); - if (ret) - goto out; - } - } - - ret = 0; - -out: - return ret; -} - -static int packfile_store_for_each_prefixed_object( - struct packfile_store *store, - const struct odb_for_each_object_options *opts, - struct packfile_store_for_each_object_wrapper_data *data) -{ - struct packfile_list_entry *e; - struct multi_pack_index *m; - bool pack_errors = false; - int ret; - - if (opts->flags) - BUG("flags unsupported"); - - store->skip_mru_updates = true; - - m = get_multi_pack_index(store->source); - if (m) { - ret = for_each_prefixed_object_in_midx(store, m, opts, data); - if (ret) - goto out; - } - - for (e = packfile_store_get_packs(store); e; e = e->next) { - if (e->pack->multi_pack_index) - continue; - - if (open_pack_index(e->pack)) { - pack_errors = true; - continue; - } - - if (!e->pack->num_objects) - continue; - - ret = for_each_prefixed_object_in_pack(store, e->pack, opts, data); - if (ret) - goto out; - } - - ret = 0; - -out: - store->skip_mru_updates = false; - if (!ret && pack_errors) - ret = -1; - return ret; -} - -int packfile_store_for_each_object(struct packfile_store *store, - const struct object_info *request, - odb_for_each_object_cb cb, - void *cb_data, - const struct odb_for_each_object_options *opts) -{ - struct packfile_store_for_each_object_wrapper_data data = { - .store = store, - .request = request, - .cb = cb, - .cb_data = cb_data, - }; - struct packfile_list_entry *e; - int pack_errors = 0, ret; - - if (opts->prefix) - return packfile_store_for_each_prefixed_object(store, opts, &data); - - store->skip_mru_updates = true; - - for (e = packfile_store_get_packs(store); e; e = e->next) { - struct packed_git *p = e->pack; - - if ((opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local) - continue; - if ((opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) && - !p->pack_promisor) - continue; - if ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) && - p->pack_keep_in_core) - continue; - if ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) && - p->pack_keep) - continue; - if (open_pack_index(p)) { - pack_errors = 1; - continue; - } - - ret = for_each_object_in_pack(p, packfile_store_for_each_object_wrapper, - &data, opts->flags); - if (ret) - goto out; - } - - ret = 0; - -out: - store->skip_mru_updates = false; - - if (!ret && pack_errors) - ret = -1; - return ret; -} - -static int extend_abbrev_len(const struct object_id *a, - const struct object_id *b, - unsigned *out) -{ - unsigned len = oid_common_prefix_hexlen(a, b); - if (len != hash_algos[a->algo].hexsz && len >= *out) - *out = len + 1; - return 0; -} - -static void find_abbrev_len_for_midx(struct multi_pack_index *m, - const struct object_id *oid, - unsigned min_len, - unsigned *out) -{ - unsigned len = min_len; - - for (; m; m = m->base_midx) { - int match = 0; - uint32_t num, first = 0; - struct object_id found_oid; - - if (!m->num_objects) - continue; - - num = m->num_objects + m->num_objects_in_base; - match = bsearch_one_midx(oid, m, &first); - - /* - * first is now the position in the packfile where we - * would insert the object ID if it does not exist (or the - * position of the object ID if it does exist). Hence, we - * consider a maximum of two objects nearby for the - * abbreviation length. - */ - - if (!match) { - if (nth_midxed_object_oid(&found_oid, m, first)) - extend_abbrev_len(&found_oid, oid, &len); - } else if (first < num - 1) { - if (nth_midxed_object_oid(&found_oid, m, first + 1)) - extend_abbrev_len(&found_oid, oid, &len); - } - if (first > 0) { - if (nth_midxed_object_oid(&found_oid, m, first - 1)) - extend_abbrev_len(&found_oid, oid, &len); - } - } - - *out = len; -} - -static void find_abbrev_len_for_pack(struct packed_git *p, - const struct object_id *oid, - unsigned min_len, - unsigned *out) -{ - int match; - uint32_t num, first = 0; - struct object_id found_oid; - unsigned len = min_len; - - num = p->num_objects; - match = bsearch_pack(oid, p, &first); - - /* - * first is now the position in the packfile where we would insert - * the object ID if it does not exist (or the position of mad->hash if - * it does exist). Hence, we consider a maximum of two objects - * nearby for the abbreviation length. - */ - if (!match) { - if (!nth_packed_object_id(&found_oid, p, first)) - extend_abbrev_len(&found_oid, oid, &len); - } else if (first < num - 1) { - if (!nth_packed_object_id(&found_oid, p, first + 1)) - extend_abbrev_len(&found_oid, oid, &len); - } - if (first > 0) { - if (!nth_packed_object_id(&found_oid, p, first - 1)) - extend_abbrev_len(&found_oid, oid, &len); - } - - *out = len; -} - -int packfile_store_find_abbrev_len(struct packfile_store *store, - const struct object_id *oid, - unsigned min_len, - unsigned *out) -{ - struct packfile_list_entry *e; - struct multi_pack_index *m; - - m = get_multi_pack_index(store->source); - if (m) - find_abbrev_len_for_midx(m, oid, min_len, &min_len); - - for (e = packfile_store_get_packs(store); e; e = e->next) { - if (e->pack->multi_pack_index) - continue; - if (open_pack_index(e->pack) || !e->pack->num_objects) - continue; - - find_abbrev_len_for_pack(e->pack, oid, min_len, &min_len); - } - - *out = min_len; - return 0; -} - struct add_promisor_object_data { struct repository *repo; struct oidset *set; @@ -2832,37 +2114,6 @@ int parse_pack_header_option(const char *in, unsigned char *out, unsigned int *l return 0; } -struct packfile_store *packfile_store_new(struct odb_source *source) -{ - struct packfile_store *store; - CALLOC_ARRAY(store, 1); - store->source = source; - strmap_init(&store->packs_by_path); - return store; -} - -void packfile_store_free(struct packfile_store *store) -{ - for (struct packfile_list_entry *e = store->packs.head; e; e = e->next) - free(e->pack); - packfile_list_clear(&store->packs); - - strmap_clear(&store->packs_by_path, 0); - free(store); -} - -void packfile_store_close(struct packfile_store *store) -{ - for (struct packfile_list_entry *e = store->packs.head; e; e = e->next) { - if (e->pack->do_not_close) - BUG("want to close pack marked 'do-not-close'"); - close_pack(e->pack); - } - if (store->midx) - close_midx(store->midx); - store->midx = NULL; -} - struct odb_packed_read_stream { struct odb_read_stream base; struct packed_git *pack; @@ -2986,15 +2237,3 @@ int packfile_read_object_stream(struct odb_read_stream **out, return 0; } - -int packfile_store_read_object_stream(struct odb_read_stream **out, - struct packfile_store *store, - const struct object_id *oid) -{ - struct pack_entry e; - - if (!find_pack_entry(store, oid, &e)) - return -1; - - return packfile_read_object_stream(out, oid, e.p, e.offset); -} diff --git a/packfile.h b/packfile.h index 49d6bdecf6ea18..e1f77152b5c4bf 100644 --- a/packfile.h +++ b/packfile.h @@ -5,9 +5,10 @@ #include "object.h" #include "odb.h" #include "odb/source-files.h" +#include "odb/source-packed.h" #include "oidset.h" +#include "packfile-list.h" #include "repository.h" -#include "strmap.h" /* in odb.h */ struct object_info; @@ -54,133 +55,18 @@ struct packed_git { char pack_name[FLEX_ARRAY]; /* more */ }; -struct packfile_list { - struct packfile_list_entry *head, *tail; -}; - -struct packfile_list_entry { - struct packfile_list_entry *next; - struct packed_git *pack; -}; - -void packfile_list_clear(struct packfile_list *list); -void packfile_list_remove(struct packfile_list *list, struct packed_git *pack); -void packfile_list_prepend(struct packfile_list *list, struct packed_git *pack); -void packfile_list_append(struct packfile_list *list, struct packed_git *pack); - -/* - * Find the pack within the "packs" list whose index contains the object - * "oid". For general object lookups, you probably don't want this; use - * find_pack_entry() instead. - */ -struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs, - const struct object_id *oid); - -/* - * A store that manages packfiles for a given object database. - */ -struct packfile_store { - struct odb_source *source; - - /* - * The list of packfiles in the order in which they have been most - * recently used. - */ - struct packfile_list packs; - - /* - * Cache of packfiles which are marked as "kept", either because there - * is an on-disk ".keep" file or because they are marked as "kept" in - * memory. - * - * Should not be accessed directly, but via - * `packfile_store_get_kept_pack_cache()`. The list of packs gets - * invalidated when the stored flags and the flags passed to - * `packfile_store_get_kept_pack_cache()` mismatch. - */ - struct { - struct packed_git **packs; - unsigned flags; - } kept_cache; - - /* The multi-pack index that belongs to this specific packfile store. */ - struct multi_pack_index *midx; - - /* - * A map of packfile names to packed_git structs for tracking which - * packs have been loaded already. - */ - struct strmap packs_by_path; - - /* - * Whether packfiles have already been populated with this store's - * packs. - */ - bool initialized; - - /* - * Usually, packfiles will be reordered to the front of the `packs` - * list whenever an object is looked up via them. This has the effect - * 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 - * 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. - * - * Setting this field to `true` thus disables these reorderings. - */ - bool skip_mru_updates; -}; - -/* - * Allocate and initialize a new empty packfile store for the given object - * database source. - */ -struct packfile_store *packfile_store_new(struct odb_source *source); - -/* - * Free the packfile store and all its associated state. All packfiles - * tracked by the store will be closed. - */ -void packfile_store_free(struct packfile_store *store); - -/* - * Close all packfiles associated with this store. The packfiles won't be - * free'd, so they can be re-opened at a later point in time. - */ -void packfile_store_close(struct packfile_store *store); - -/* - * Prepare the packfile store by loading packfiles and multi-pack indices for - * all alternates. This becomes a no-op if the store is already prepared. - * - * It shouldn't typically be necessary to call this function directly, as - * functions that access the store know to prepare it. - */ -void packfile_store_prepare(struct packfile_store *store); - -/* - * Clear the packfile caches and try to look up any new packfiles that have - * appeared since last preparing the packfiles store. - * - * This function must be called under the `odb_read_lock()`. - */ -void packfile_store_reprepare(struct packfile_store *store); - /* * Add the pack to the store so that contained objects become accessible via * the store. This moves ownership into the store. */ -void packfile_store_add_pack(struct packfile_store *store, +void packfile_store_add_pack(struct odb_source_packed *store, struct packed_git *pack); /* * Get all packs managed by the given store, including packfiles that are * referenced by multi-pack indices. */ -struct packfile_list_entry *packfile_store_get_packs(struct packfile_store *store); +struct packfile_list_entry *packfile_store_get_packs(struct odb_source_packed *store); struct repo_for_each_pack_data { struct odb_source *source; @@ -238,54 +124,26 @@ static inline void repo_for_each_pack_data_next(struct repo_for_each_pack_data * ((p) = (eack_pack_data.entry ? eack_pack_data.entry->pack : NULL)); \ repo_for_each_pack_data_next(&eack_pack_data)) -int packfile_store_read_object_stream(struct odb_read_stream **out, - struct packfile_store *store, - const struct object_id *oid); - -/* - * Try to read the object identified by its ID from the object store and - * populate the object info with its data. Returns 1 in case the object was - * not found, 0 if it was and read successfully, and a negative error code in - * case the object was corrupted. - */ -int packfile_store_read_object_info(struct packfile_store *store, - const struct object_id *oid, - struct object_info *oi, - enum object_info_flags flags); - /* * Open the packfile and add it to the store if it isn't yet known. Returns * either the newly opened packfile or the preexisting packfile. Returns a * `NULL` pointer in case the packfile could not be opened. */ -struct packed_git *packfile_store_load_pack(struct packfile_store *store, +struct packed_git *packfile_store_load_pack(struct odb_source_packed *store, const char *idx_path, int local); -int packfile_store_freshen_object(struct packfile_store *store, - const struct object_id *oid); - enum kept_pack_type { KEPT_PACK_ON_DISK = (1 << 0), KEPT_PACK_IN_CORE = (1 << 1), KEPT_PACK_IN_CORE_OPEN = (1 << 2), }; -/* - * Count the number objects contained in the given packfile store. If - * successful, the number of objects will be written to the `out` pointer. - * - * Return 0 on success, a negative error code otherwise. - */ -int packfile_store_count_objects(struct packfile_store *store, - enum odb_count_objects_flags flags, - unsigned long *out); - /* * Retrieve the cache of kept packs from the given packfile store. Accepts a * combination of `kept_pack_type` flags. The cache is computed on demand and * will be recomputed whenever the flags change. */ -struct packed_git **packfile_store_get_kept_pack_cache(struct packfile_store *store, +struct packed_git **packfile_store_get_kept_pack_cache(struct odb_source_packed *store, unsigned flags); struct pack_window { @@ -356,26 +214,6 @@ int for_each_object_in_pack(struct packed_git *p, each_packed_object_fn, void *data, enum odb_for_each_object_flags flags); -/* - * Iterate through all packed objects in the given packfile store 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 `packfile_store_read_object_info()` was called for the - * object. - * - * The flags parameter is a combination of `odb_for_each_object_flags`. - */ -int packfile_store_for_each_object(struct packfile_store *store, - const struct object_info *request, - odb_for_each_object_cb cb, - void *cb_data, - const struct odb_for_each_object_options *opts); - -int packfile_store_find_abbrev_len(struct packfile_store *store, - const struct object_id *oid, - unsigned min_len, - unsigned *out); - /* A hook to report invalid files in pack directory */ #define PACKDIR_FILE_PACK 1 #define PACKDIR_FILE_IDX 2 @@ -454,10 +292,15 @@ off_t nth_packed_object_offset(const struct packed_git *, uint32_t n); */ off_t find_pack_entry_one(const struct object_id *oid, struct packed_git *); +int packfile_fill_entry(struct packed_git *p, + const struct object_id *oid, + struct pack_entry *e); + int is_pack_valid(struct packed_git *); -void *unpack_entry(struct repository *r, struct packed_git *, off_t, enum object_type *, unsigned long *); +void *unpack_entry(struct repository *r, struct packed_git *, off_t, + enum object_type *, size_t *); unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, size_t *sizep); -unsigned long get_size_from_delta(struct packed_git *, struct pack_window **, off_t); +size_t get_size_from_delta(struct packed_git *, struct pack_window **, off_t); int unpack_object_header(struct packed_git *, struct pack_window **, off_t *, size_t *); off_t get_delta_base(struct packed_git *p, struct pack_window **w_curs, off_t *curpos, enum object_type type, @@ -477,8 +320,12 @@ extern int do_check_packed_object_crc; * Look up the object info for a specific offset in the packfile. * Returns zero on success, a negative error code otherwise. */ -int packed_object_info(struct packed_git *pack, +int packed_object_info(struct odb_source_packed *source, + struct packed_git *pack, off_t offset, struct object_info *); +int packed_object_info_with_index_pos(struct odb_source_packed *source, + struct packed_git *p, off_t obj_offset, + uint32_t *maybe_index_pos, struct object_info *oi); void mark_bad_packed_object(struct packed_git *, const struct object_id *); const struct packed_git *has_packed_and_bad(struct repository *, const struct object_id *); diff --git a/parse-options.c b/parse-options.c index a676da86f5d617..08c21d9fc0a585 100644 --- a/parse-options.c +++ b/parse-options.c @@ -583,7 +583,7 @@ static enum parse_opt_result parse_long_opt( ambiguous.option->long_name, (abbrev.flags & OPT_UNSET) ? "no-" : "", abbrev.option->long_name); - return PARSE_OPT_HELP; + return PARSE_OPT_HELP_ERROR; } if (abbrev.option) { if (*arg_end) @@ -1037,6 +1037,7 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx, usage_with_options(usagestr, options); case PARSE_OPT_COMPLETE: case PARSE_OPT_HELP: + case PARSE_OPT_HELP_ERROR: case PARSE_OPT_ERROR: case PARSE_OPT_DONE: case PARSE_OPT_NON_OPTION: @@ -1072,6 +1073,7 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx, case PARSE_OPT_NON_OPTION: case PARSE_OPT_SUBCOMMAND: case PARSE_OPT_HELP: + case PARSE_OPT_HELP_ERROR: case PARSE_OPT_COMPLETE: BUG("parse_short_opt() cannot return these"); case PARSE_OPT_DONE: @@ -1099,6 +1101,7 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx, case PARSE_OPT_SUBCOMMAND: case PARSE_OPT_COMPLETE: case PARSE_OPT_HELP: + case PARSE_OPT_HELP_ERROR: BUG("parse_short_opt() cannot return these"); case PARSE_OPT_DONE: break; @@ -1133,6 +1136,8 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx, goto unknown; case PARSE_OPT_HELP: goto show_usage; + case PARSE_OPT_HELP_ERROR: + goto show_usage_stderr; case PARSE_OPT_NON_OPTION: case PARSE_OPT_SUBCOMMAND: case PARSE_OPT_COMPLETE: @@ -1149,7 +1154,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. @@ -1166,6 +1171,9 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx, show_usage: return usage_with_options_internal(ctx, usagestr, options, USAGE_NORMAL, USAGE_TO_STDOUT); + show_usage_stderr: + return usage_with_options_internal(ctx, usagestr, options, + USAGE_NORMAL, USAGE_TO_STDERR); } int parse_options_end(struct parse_opt_ctx_t *ctx) @@ -1197,6 +1205,8 @@ int parse_options(int argc, const char **argv, parse_options_start_1(&ctx, argc, argv, prefix, options, flags); switch (parse_options_step(&ctx, options, usagestr)) { case PARSE_OPT_HELP: + exit(0); + case PARSE_OPT_HELP_ERROR: case PARSE_OPT_ERROR: exit(129); case PARSE_OPT_COMPLETE: @@ -1363,7 +1373,7 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t parse_options_check_harder(opts); if (!usagestr) - return PARSE_OPT_HELP; + return err ? PARSE_OPT_HELP_ERROR : PARSE_OPT_HELP; if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL) fprintf(outfile, "cat <<\\EOF\n"); @@ -1474,9 +1484,9 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t fputc('\n', outfile); if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL) - fputs("EOF\n", outfile); + fputs("EOF\nexit 0\n", outfile); - return PARSE_OPT_HELP; + return err ? PARSE_OPT_HELP_ERROR : PARSE_OPT_HELP; } void NORETURN usage_with_options(const char * const *usagestr, @@ -1495,11 +1505,11 @@ void show_usage_with_options_if_asked(int ac, const char **av, if (!strcmp(av[1], "-h")) { usage_with_options_internal(NULL, usagestr, opts, USAGE_NORMAL, USAGE_TO_STDOUT); - exit(129); + exit(0); } else if (!strcmp(av[1], "--help-all")) { usage_with_options_internal(NULL, usagestr, opts, USAGE_FULL, USAGE_TO_STDOUT); - exit(129); + exit(0); } } } diff --git a/parse-options.h b/parse-options.h index 0d1f738f8d8671..3ec8ba5cc83c60 100644 --- a/parse-options.h +++ b/parse-options.h @@ -57,7 +57,8 @@ enum parse_opt_option_flags { }; enum parse_opt_result { - PARSE_OPT_COMPLETE = -3, + PARSE_OPT_COMPLETE = -4, + PARSE_OPT_HELP_ERROR = -3, PARSE_OPT_HELP = -2, PARSE_OPT_ERROR = -1, /* must be the same as error() */ PARSE_OPT_DONE = 0, /* fixed so that "return 0" works */ diff --git a/patch-delta.c b/patch-delta.c index b5c8594db60dd1..42199fa95625d8 100644 --- a/patch-delta.c +++ b/patch-delta.c @@ -12,13 +12,13 @@ #include "git-compat-util.h" #include "delta.h" -void *patch_delta(const void *src_buf, unsigned long src_size, - const void *delta_buf, unsigned long delta_size, - unsigned long *dst_size) +void *patch_delta(const void *src_buf, size_t src_size, + const void *delta_buf, size_t delta_size, + size_t *dst_size) { const unsigned char *data, *top; unsigned char *dst_buf, *out, cmd; - unsigned long size; + size_t size; if (delta_size < DELTA_SIZE_MIN) return NULL; diff --git a/path-walk.c b/path-walk.c index 94ff90bd1566b6..9ca3248ea8f53d 100644 --- a/path-walk.c +++ b/path-walk.c @@ -368,7 +368,7 @@ static int walk_path(struct path_walk_context *ctx, struct oid_array filtered = OID_ARRAY_INIT; for (size_t i = 0; i < list->oids.nr; i++) { - unsigned long size; + size_t size; if (odb_read_object_info(ctx->repo->objects, &list->oids.oid[i], @@ -699,6 +699,7 @@ int walk_objects_by_path(struct path_walk_info *info) int ret; size_t commits_nr = 0, paths_nr = 0; struct commit *c; + char *path; struct type_and_oid_list *root_tree_list; struct type_and_oid_list *commit_list; struct path_walk_context ctx = { @@ -808,8 +809,7 @@ int walk_objects_by_path(struct path_walk_info *info) free(commit_list); trace2_region_enter("path-walk", "path-walk", info->revs->repo); - while (!ret && ctx.path_stack.nr) { - char *path = prio_queue_get(&ctx.path_stack); + while (!ret && (path = prio_queue_get(&ctx.path_stack))) { paths_nr++; ret = walk_path(&ctx, path); @@ -821,12 +821,12 @@ int walk_objects_by_path(struct path_walk_info *info) if (!strmap_empty(&ctx.paths_to_lists)) { struct hashmap_iter iter; struct strmap_entry *entry; + char *path; strmap_for_each_entry(&ctx.paths_to_lists, &iter, entry) push_to_stack(&ctx, entry->key); - while (!ret && ctx.path_stack.nr) { - char *path = prio_queue_get(&ctx.path_stack); + while (!ret && (path = prio_queue_get(&ctx.path_stack))) { paths_nr++; ret = walk_path(&ctx, path); diff --git a/path.c b/path.c index d7e17bf17404de..c3a709a9284b7f 100644 --- a/path.c +++ b/path.c @@ -1579,6 +1579,75 @@ char *xdg_cache_home(const char *filename) return NULL; } +void format_path(struct strbuf *dest, const char *path, + const char *prefix, enum path_format format) +{ + strbuf_reset(dest); + + switch (format) { + case PATH_FORMAT_UNMODIFIED: + strbuf_addstr(dest, path); + break; + + case PATH_FORMAT_RELATIVE: { + struct strbuf relative_buf = STRBUF_INIT; + struct strbuf real_path = STRBUF_INIT; + struct strbuf real_prefix = STRBUF_INIT; + char *cwd = NULL; + + /* + * We don't ever produce a relative path if prefix is NULL, + * so set the prefix to the current directory so that we can + * produce a relative path whenever possible. + */ + if (!prefix) + prefix = cwd = xgetcwd(); + + if (!is_absolute_path(path)) { + strbuf_realpath_forgiving(&real_path, path, 1); + path = real_path.buf; + } + if (!is_absolute_path(prefix)) { + strbuf_realpath_forgiving(&real_prefix, prefix, 1); + prefix = real_prefix.buf; + } + + strbuf_addstr(dest, relative_path(path, prefix, &relative_buf)); + + strbuf_release(&relative_buf); + strbuf_release(&real_path); + strbuf_release(&real_prefix); + free(cwd); + break; + } + + case PATH_FORMAT_RELATIVE_IF_SHARED: { + struct strbuf relative_buf = STRBUF_INIT; + + /* + * If we're using RELATIVE_IF_SHARED mode, then we want an + * absolute path unless the two share a common prefix, so don't + * default the prefix to the current working directory. Doing so + * would cause a relative path to always be produced if possible. + */ + strbuf_addstr(dest, relative_path(path, prefix, &relative_buf)); + strbuf_release(&relative_buf); + break; + } + + case PATH_FORMAT_CANONICAL: + /* + * strbuf_realpath_forgiving inherently resets the destination + * buffer, safely aligning with our replace semantics. + */ + strbuf_realpath_forgiving(dest, path, 1); + break; + + default: + BUG("unknown path_format value %d", format); + } +} + REPO_GIT_PATH_FUNC(squash_msg, "SQUASH_MSG") REPO_GIT_PATH_FUNC(merge_msg, "MERGE_MSG") REPO_GIT_PATH_FUNC(merge_rr, "MERGE_RR") diff --git a/path.h b/path.h index 0434ba5e07e806..7e7408dd05fce3 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); @@ -262,6 +262,36 @@ enum scld_error safe_create_leading_directories_no_share(char *path); int safe_create_file_with_leading_directories(struct repository *repo, const char *path); +/** + * The formatting strategy to apply when writing a path into a buffer. + */ +enum path_format { + /* Output the path exactly as-is without any modifications. */ + PATH_FORMAT_UNMODIFIED, + + /* Output a path relative to the provided directory prefix. */ + PATH_FORMAT_RELATIVE, + + /* Output a relative path only if the path shares a root with the prefix. */ + PATH_FORMAT_RELATIVE_IF_SHARED, + + /* Output a fully resolved, absolute canonical path. */ + PATH_FORMAT_CANONICAL +}; + +/** + * Format a path according to the specified formatting strategy and store + * the result in the given strbuf, replacing any existing contents. + * + * `dest` : The string buffer to store the formatted path into. + * `path` : The path string that needs to be formatted. + * `prefix` : The directory prefix to calculate relative offsets against. + * Pass NULL to default to the current working directory where applicable. + * `format` : The formatting behavior rule to execute. + */ +void format_path(struct strbuf *dest, const char *path, + const char *prefix, enum path_format format); + # ifdef USE_THE_REPOSITORY_VARIABLE # include "strbuf.h" # include "repository.h" diff --git a/po/AGENTS.md b/po/AGENTS.md index feb90d4d24ab27..94af128b46d063 100644 --- a/po/AGENTS.md +++ b/po/AGENTS.md @@ -127,6 +127,52 @@ etc.), and quotes exactly as in `msgid`. Only reorder placeholders with positional syntax when needed (see Placeholder Reordering below). +### Preserving Quotation Marks + +Some languages use language-specific UTF-8 quotation marks (curly/smart +quotes) rather than ASCII straight quotes. **Always preserve these +characters exactly as they appear in the source.** Do **not** convert them +to ASCII straight quotes. + +**Protected quotation marks** (non-exhaustive list): + +| Character | Unicode | Name | Languages | +|-----------|---------|------|-----------| +| „ | U+201E | DOUBLE LOW-9 QUOTATION MARK | Bulgarian, German, etc. | +| " | U+201C | LEFT DOUBLE QUOTATION MARK | Bulgarian, etc. | +| " | U+201D | RIGHT DOUBLE QUOTATION MARK | English, German, etc. | +| ' | U+2018 | LEFT SINGLE QUOTATION MARK | English, etc. | +| ' | U+2019 | RIGHT SINGLE QUOTATION MARK | English, etc. | +| « | U+00AB | LEFT-POINTING DOUBLE ANGLE QUOTATION MARK | French, Russian, etc. | +| » | U+00BB | RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK | French, Russian, etc. | +| ‹ | U+2039 | SINGLE LEFT-POINTING ANGLE QUOTATION MARK | French, etc. | +| › | U+203A | SINGLE RIGHT-POINTING ANGLE QUOTATION MARK | French, etc. | + +**Why this matters in PO files**: In PO file format, the ASCII straight +double quote `"` (U+0022) is the **string delimiter**. If a translation +contains a curly quote that is incorrectly converted to `"` (U+0022), +the PO parser will interpret it as the end of the string, causing: + +1. **String truncation**: The `msgstr` value is cut short at the + spurious quote character. +2. **Syntax errors**: `msgfmt --check` fails with parse errors at + the line where the string was prematurely terminated. +3. **Data loss**: Content after the accidental quote delimiter is + misinterpreted or lost. + +**Rules**: + +- **Never** replace language-specific quotation marks with ASCII + straight quotes `"` (U+0022) or `'` (U+0027). +- Apply this rule when translating PO files, PO multi-line strings, + and GETTEXT JSON `msgstr` array values. +- Apply this rule when generating suggested translations + (`suggest_msgstr`) during review. +- If the source `msgid` uses ASCII straight quotes, preserve them + as-is in the translation unless the target language convention + requires different quotation marks. + + ### Placeholder Reordering When reordering placeholders relative to `msgid`, use positional syntax (`%n$`) @@ -387,7 +433,10 @@ read and write this format. - **Placeholders**: Preserve variables (`%s`, `{name}`, `$1`) exactly; use positional parameters when reordering (see "Placeholder Reordering" above). - **Special characters**: Preserve escape sequences (`\n`, `\"`, `\\`, `\t`), - placeholders exactly as in `msgid`. See "Preserving Special Characters" above. + placeholders exactly as in `msgid`. Preserve language-specific quotation + marks (curly/smart quotes like „, ", ", ', ') — do not convert them to + ASCII straight quotes. See "Preserving Special Characters" and + "Preserving Quotation Marks" above. - **Plurals and gender**: Correct forms and agreement. - **Context fit**: Suitable for UI space, tone, and use (e.g. error vs. tooltip). - **Cultural appropriateness**: No offensive or ambiguous content. diff --git a/po/TEAMS b/po/TEAMS index 647a107a7a3f68..fe73dedefbbfd0 100644 --- a/po/TEAMS +++ b/po/TEAMS @@ -83,12 +83,12 @@ Members: Trần Ngọc Quân Nguyễn Thái Ngọc Duy Language: zh_CN (Simplified Chinese) -Repository: https://github.com/dyrone/git/ -Leader: Teng Long +Repository: https://github.com/lilydjwg/git-po +Leader: 依云 Members: Ray Chen - 依云 Fangyi Zhou Jiang Xin + Teng Long Language: zh_TW (Traditional Chinese) Repository: https://github.com/l10n-tw/git-po diff --git a/po/bg.po b/po/bg.po index 867678dcf73f6d..e11e53618242d5 100644 --- a/po/bg.po +++ b/po/bg.po @@ -266,6 +266,11 @@ # rate limited скоростта е ограничена # submodule подмодул # credential идентификационни данни +# backfill допълване на информация +# negotiate/negotiation договарям, договаряне +# API програмен интерфейс, библиотека +# compact уплътнявам +# compaction уплътняване # ------------------------ # „$var“ - може да не сработва за shell има gettext и eval_gettext - проверка - намират се лесно по „$ # ------------------------ @@ -296,8 +301,8 @@ msgid "" msgstr "" "Project-Id-Version: git v2.53.0-rc0\n" "Report-Msgid-Bugs-To: Git Mailing List \n" -"POT-Creation-Date: 2026-04-19 16:08+0200\n" -"PO-Revision-Date: 2026-04-19 16:07+0200\n" +"POT-Creation-Date: 2026-06-24 13:30+0200\n" +"PO-Revision-Date: 2026-06-24 14:05+0200\n" "Last-Translator: Alexander Shopov \n" "Language-Team: Bulgarian \n" "Language: bg\n" @@ -1972,30 +1977,30 @@ msgstr "" #, c-format msgid "" -"The merge base %s is %s.\n" +"The merge base %s is '%s'.\n" "This means the first '%s' commit is between %s and [%s].\n" msgstr "" -"Базата за сливане „%s“ е %s.\n" -"Следователно първото %s подаване е между „%s“ и [%s].\n" +"Базата за сливане „%s“ е „%s“.\n" +"Следователно първото подаване „%s“ е между „%s“ и [%s].\n" #, c-format msgid "" -"Some %s revs are not ancestors of the %s rev.\n" +"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" msgstr "" -"Някои от указателите от „%s“ не са предшественици на указателя „%s“.\n" +"Някои от версиите от „%s“ не са предшественици на версията „%s“.\n" "Двоичното търсене с git bisect няма да работи правилно.\n" "Дали не сте объркали указателите „%s“ и „%s“?\n" #, c-format msgid "" "the merge base between %s and [%s] must be skipped.\n" -"So we cannot be sure the first %s commit is between %s and %s.\n" +"So we cannot be sure the first '%s' commit is between %s and %s.\n" "We continue anyway." msgstr "" -"базата за сливане между „%s“ и [%s] трябва да се прескочи.\n" -"Не може да сме сигурни, че първото %s подаване е между „%s“ и „%s“.\n" +"базата за сливане между %s и [%s] трябва да се прескочи.\n" +"Не може да сме сигурни, че първото подаване „%s“ е между „%s“ и „%s“.\n" "Двоичното търсене продължава." #, c-format @@ -2003,7 +2008,7 @@ msgid "Bisecting: a merge base must be tested\n" msgstr "Двоично търсене: трябва да се провери база за сливане\n" #, c-format -msgid "a %s revision is needed" +msgid "a '%s' revision is needed" msgstr "необходима е версия „%s“" #, c-format @@ -2022,7 +2027,7 @@ msgid "reading bisect refs failed" msgstr "неуспешно прочитане на указателите за двоично търсене" #, c-format -msgid "%s was both %s and %s\n" +msgid "%s was both '%s' and '%s'\n" msgstr "„%s“ e както „%s“, така и „%s“\n" #, c-format @@ -2798,8 +2803,24 @@ msgstr "git archive: протоколна грешка" msgid "git archive: expected a flush" msgstr "git archive: очакваше се изчистване на буферите чрез „flush“" -msgid "git backfill [--min-batch-size=] [--[no-]sparse]" -msgstr "git backfill [--min-batch-size=БРОЙ] [--[no-]sparse]" +msgid "" +"git backfill [--min-batch-size=] [--[no-]sparse] [--[no-]include-edges] " +"[]" +msgstr "" +"git backfill [--min-batch-size=БРОЙ] [--[no-]sparse] [--[no-]include-edges] " +"[ДИАПАЗОН_НА_ВЕРСИИТЕ]" + +#, c-format +msgid "'%s' cannot be used with 'git backfill'" +msgstr "опцията „%s“ е несъвместима с „git backfill“" + +msgid "cannot backfill with these filter options" +msgstr "не може да се допълва информация с тези опции на филтър" + +msgid "cannot backfill with blob size limits" +msgstr "" +"не може да се допълва информация при такива граници на размерите на обекти-" +"BLOB" msgid "problem loading sparse-checkout" msgstr "проблем при зареждане на частично хранилище" @@ -2810,6 +2831,11 @@ msgstr "Минимален БРОЙ обекти заявявани наведн msgid "Restrict the missing objects to the current sparse-checkout" msgstr "Ограничаване на липсващите обекти до текущото частично хранилище" +msgid "Include blobs from boundary commits in the backfill" +msgstr "" +"Включване на обектите-BLOB от граничните подавания при допълването на " +"информацията" + #, c-format msgid "unrecognized argument: %s" msgstr "непознат аргумент: %s" @@ -2929,30 +2955,33 @@ msgstr "двоично търсене само по „%s“ подаване." msgid "Are you sure [Y/n]? " msgstr "Да се продължи ли? „Y“ — ДА, „n“ — не" -msgid "status: waiting for both good and bad commits\n" -msgstr "състояние: чакане и за добро, и за лошо подаване\n" +#, c-format +msgid "status: waiting for both '%s' and '%s' commits\n" +msgstr "състояние: чакане и за „%s“, и за „%s“ подаване\n" #, c-format -msgid "status: waiting for bad commit, %d good commit known\n" -msgid_plural "status: waiting for bad commit, %d good commits known\n" -msgstr[0] "състояние: чакане за лошо подаване, известно е %d добро подаване\n" +msgid "status: waiting for '%s' commit, %d '%s' commit known\n" +msgid_plural "status: waiting for '%s' commit, %d '%s' commits known\n" +msgstr[0] "" +"състояние: чакане за „%s“ подаване, известно е %d подаване означено „%s“\n" msgstr[1] "" -"състояние: чакане за лошо подаване, известни са %d добри подавания\n" +"състояние: чакане за „%s“ подаване, известни са %d подавания означени „%s“\n" -msgid "status: waiting for good commit(s), bad commit known\n" +#, c-format +msgid "status: waiting for '%s' commit(s), '%s' commit known\n" msgstr "" -"състояние: чакане за добро подаване, известно е поне едно лошо подаване\n" +"състояние: чакане за „%s“ подаване, известно е поне едно „%s“ подаване\n" msgid "no terms defined" msgstr "не са указани управляващи думи" #, c-format msgid "" -"Your current terms are %s for the old state\n" -"and %s for the new state.\n" +"Your current terms are '%s' for the old state\n" +"and '%s' for the new state.\n" msgstr "" -"Текущите управляващи думи са: %s за старото състояние\n" -"и %s за новото състояние.\n" +"Текущите управляващи думи са: „%s“ за старото състояние\n" +"и „%s“ за новото състояние.\n" #, c-format msgid "" @@ -3040,12 +3069,12 @@ msgid "bisect run failed: no command provided." msgstr "неуспешно двоично търсене, не е зададена команда." #, c-format -msgid "unable to verify %s on good revision" -msgstr "„%s“ не може да провери с добра версия" +msgid "unable to verify %s on '%s' revision" +msgstr "%s не може да се провери при версия „%s“" #, c-format -msgid "bogus exit code %d for good revision" -msgstr "неправилен изходен код %d за добро подаване" +msgid "bogus exit code %d for '%s' revision" +msgstr "неправилен изходен код %d при версия „%s“" #, c-format msgid "bisect run failed: exit code %d from %s is < 0 or >= 128" @@ -3063,8 +3092,9 @@ msgstr "двоичното търсене не може да продължи" msgid "bisect run success" msgstr "успешно двоично търсене" -msgid "bisect found first bad commit" -msgstr "двоичното търсене откри първото лошо подаване" +#, c-format +msgid "bisect found first '%s' commit\n" +msgstr "двоичното търсене откри първото „%s“ подаване\n" #, c-format msgid "bisect run failed: 'git bisect %s' exited with error code %d" @@ -3766,6 +3796,12 @@ msgstr "Разпакетиране на пратки от обекти" msgid "cannot read object %s '%s'" msgstr "обектът „%s“ (%s) не може да се прочете" +#, c-format +msgid "mailmap: invalid boolean '%s'" +msgstr "" +"файл за съответствията на имената и адресите на е-поща: неправилна булева " +"стойност „%s“" + msgid "flush is only for --buffer mode" msgstr "изчистването на буферите (flush) се ползва само за „--buffer“" @@ -4128,15 +4164,6 @@ msgstr "дървото не може да се прочете (%s)" msgid "you need to resolve your current index first" msgstr "първо трябва да коригирате индекса си" -#, c-format -msgid "" -"cannot continue with staged changes in the following files:\n" -"%s" -msgstr "" -"не може да се продължи с промѐни в следните файлове, които са добавени в " -"индекса:\n" -"%s" - #, c-format msgid "Can not do reflog for '%s': %s\n" msgstr "Журналът на указателите за „%s“ не може да се проследи: %s\n" @@ -4227,6 +4254,10 @@ msgstr "Преди това „HEAD“ сочеше към" msgid "You are on a branch yet to be born" msgstr "В момента сте на клон, който все още не е създаден" +#, c-format +msgid "The following paths have local changes:\n" +msgstr "Следните пътища са с локални промени:\n" + #, c-format msgid "" "'%s' could be both a local file and a tracking branch.\n" @@ -5821,6 +5852,22 @@ msgstr "неправилен брой аргументи, трябва да е msgid "wrong number of arguments, should be from %d to %d" msgstr "неправилен брой аргументи, трябва да е от %d до %d включително" +#, c-format +msgid "missing value to set to the variable '%s'" +msgstr "липсва стойност, която да се зададе на променливата „%s“" + +#, c-format +msgid "did you mean \"git config set %s %s\"?" +msgstr "" +"следната команда ли искахте да изпълните:\n" +"\n" +" git config set %s %s" + +#, c-format +msgid "missing value to set to a variable with an invalid name '%s'" +msgstr "" +"липсва стойност, която да се зададе на променливата с неправилно име „%s“" + #, c-format msgid "invalid key pattern: %s" msgstr "неправилен шаблон за ключ: „%s“" @@ -7274,14 +7321,18 @@ msgstr "стойността „%2$s“ за опцията „%1$s“ не е msgid "option \"%s\" is ignored for %s" msgstr "опцията „%s“ се прескача при „%s“" -#, c-format -msgid "%s is not a valid object" -msgstr "„%s“ е неправилен обект" - #, c-format msgid "the object %s does not exist" msgstr "обектът „%s“ не съществува" +#, c-format +msgid "ignoring %s=%s because it does not match any refs" +msgstr "„%s=%s“ се прескача, защото не съвпада с никой указател" + +#, c-format +msgid "ignoring %s because the protocol does not support it" +msgstr "„%s“ се прескача, защото протоколът не го поддържа" + #, c-format msgid "" "Run 'git remote set-head %s %s' to follow the change, or set\n" @@ -7494,6 +7545,10 @@ msgstr "ВЕРСИЯ" msgid "report that we have only objects reachable from this object" msgstr "докладване, че всички обекти може са достижими при започване от този" +msgid "ensure this ref is always sent as a negotiation have" +msgstr "" +"уверете се, че този указател винаги бива пращан по време на договарянето" + msgid "do not fetch a packfile; instead, print ancestors of negotiation tips" msgstr "" "без доставяне на пакетни файлове, вместо това да се извеждат предшественици " @@ -7511,11 +7566,6 @@ msgstr "запазване на гра̀фа с подаванията след msgid "accept refspecs from stdin" msgstr "четене на указателите от стандартния вход" -msgid "--negotiate-only needs one or more --negotiation-tip=*" -msgstr "" -"Опцията „--negotiate-only“ изисква една или повече опции „--negotiation-" -"tip=*“" - msgid "negative depth in --deepen is not supported" msgstr "отрицателна дълбочина като аргумент на „--deepen“ не се поддържа" @@ -7545,6 +7595,10 @@ msgstr "опцията „--negotiate-only“ изисква хранилище" msgid "protocol does not support --negotiate-only, exiting" msgstr "протоколът не поддържа опцията „--negotiate-only“, изход от програмата" +#, c-format +msgid "%s needs one or more %s" +msgstr "опцията „%s“ изисква поне една опция „%s“" + msgid "" "--filter can only be used with the remote configured in " "extensions.partialclone" @@ -7876,6 +7930,10 @@ msgstr "дължината на „%s“ e извън диапазона: %d" msgid "value of '%s' not bool or int: %d" msgstr "СТОЙНОСТТА на „%s“ не е нито булева, нито целочислена, а трябва: %d" +#, c-format +msgid "daemon did not stop within %d seconds" +msgstr "демонът не спря в рамките на %d секунди" + #, c-format msgid "fsmonitor-daemon is watching '%s'\n" msgstr "fsmonitor-daemon наблюдава „%s“\n" @@ -7922,6 +7980,9 @@ msgstr "процесът „fsmonitor--daemon“ вече е стартирал msgid "running fsmonitor-daemon in '%s'\n" msgstr "процесът „fsmonitor-daemon“ се стартира в „%s“\n" +msgid "setsid failed" +msgstr "неуспешно изпълнение на „setsid“" + #, c-format msgid "starting fsmonitor-daemon in '%s'\n" msgstr "стартиране на процеса „fsmonitor-daemon“ в „%s“\n" @@ -8616,6 +8677,13 @@ msgstr "употреба: %s%s" msgid "'git help config' for more information" msgstr "За повече информация изпълнете „git help config“" +msgid "" +"git history fixup [--dry-run] [--update-refs=(branches|head)] [--" +"reedit-message] [--empty=(drop|keep|abort)]" +msgstr "" +"git history fixup ПОДАВАНЕ [--dry-run] [--update-refs=(branches|head)] [--" +"reedit-message] [--empty=(drop|keep|abort)]" + msgid "git history reword [--dry-run] [--update-refs=(branches|head)]" msgstr "" "git history reword ПОДАВАНЕ [--dry-run] [--update-refs=(branches|head)]" @@ -8680,25 +8748,85 @@ msgstr "указателят „%s“ не може да се обнови: %s" msgid "failed to commit ref transaction: %s" msgstr "транзакцията за указатели не може да се завърши: %s" +#, c-format +msgid "unable to parse parent of %s" +msgstr "родителското подаване на „%s“ не може да се анализира" + +#, c-format +msgid "" +"unrecognized '--empty=' action '%s'; valid values are \"drop\", \"keep\", " +"and \"abort\"." +msgstr "" +"неправилно действие за „--empty=“: „%s“, вариантите са „drop“ (прескачане), " +"„keep“ (запазване) и „abort“ (преустановяване)" + msgid "control which refs should be updated" msgstr "кои указатели да се обновят" msgid "perform a dry-run without updating any refs" msgstr "пробно изпълнение без промяна на указатели" +msgid "open an editor to modify the commit message" +msgstr "отваряне на текстов редактор за промяна на съобщението при подаване" + +msgid "how to handle commits that become empty" +msgstr "как да се обработват оказалите се празни подавания" + msgid "command expects a single revision" msgstr "командата изисква точно една версия" +msgid "cannot run fixup in a bare repository" +msgstr "" +"вкарването на подаване в предходното не може да се изпълни в голо хранилище" + #, c-format msgid "commit cannot be found: %s" msgstr "подаването липсва: %s" -msgid "failed writing reworded commit" -msgstr "подаването с променено съобщение не може да се презапише" +msgid "cannot get tree for HEAD" +msgstr "дървото към указателя „HEAD“ не може да се получи" + +msgid "unable to read index" +msgstr "индексът не може да се прочете" + +msgid "nothing to fixup: no staged changes" +msgstr "няма какво да се добави към предходно подаване: в индекса няма промени" + +msgid "unable to write index as a tree" +msgstr "индексът не може да се запише като дърво" + +#, c-format +msgid "cannot get tree for commit %s" +msgstr "дървото към подаване „%s“ не може да се получи" + +msgid "merge failed while applying fixup" +msgstr "неуспешно сливане по време на вкарване на подаване в предходното му" + +msgid "fixup would produce conflicts; aborting" +msgstr "" +"вкарването на подаването в предходното ще доведе до конфликти, " +"преустановяване на действието" + +#, c-format +msgid "cannot drop root commit %s: it has no parent to replay onto" +msgstr "" +"първото подаване „%s“ не може да се прескочи, защото няма родителско " +"подаване, върху което да се пребазират другите" + +#, c-format +msgid "fixup makes commit %s empty" +msgstr "" +"вкарването на подаването в предходното ще доведе до празно подаване „%s“" + +msgid "failed writing fixed-up commit" +msgstr "неуспешно запазване на подаване, в което е вкарано друго" msgid "failed replaying descendants" msgstr "неуспешно повторно прилагане на наследниците" +msgid "failed writing reworded commit" +msgstr "подаването с променено съобщение не може да се презапише" + msgid "unable to populate index with tree" msgstr "индексът не може да се попълни с дървото" @@ -8734,9 +8862,11 @@ msgstr "подаване за сливане не може да се разде msgid "" "git hook run [--allow-unknown-hook-name] [--ignore-missing] [--to-" -"stdin=] [-- ]" +"stdin=] [(-j|--jobs) ]\n" +" [-- ]" msgstr "" "git hook run [--allow-unknown-hook-name] [--ignore-missing] [--to-stdin=ПЪТ] " +"[(-j|--jobs) БРОЙ]\n" "КУКА [-- АРГУМЕНТ_ЗА_КУКА…]" msgid "" @@ -8777,6 +8907,17 @@ msgstr "прескачане на заявена КУКА, която липсв msgid "file to read into hooks' stdin" msgstr "файл за изчитане от стандартния вход на куката" +msgid "run up to hooks simultaneously (-1 for CPU count)" +msgstr "" +"изпълняване на максимално този БРОЙ куки едновременно (-1 ползва броя " +"налични процесори)" + +#, c-format +msgid "invalid value for -j: %d (use -1 for CPU count or a positive integer)" +msgstr "" +"неправилна стойност за „-j“: %d (ползвайте положително число или -1 за броя " +"налични процесори)" + #, c-format msgid "object type mismatch at %s" msgstr "неправилен вид на обекта „%s“" @@ -8826,6 +8967,9 @@ msgstr "повреден обект в пакетния файл при отме msgid "inflate returned %d" msgstr "декомпресирането с „inflate“ върна %d" +msgid "object size too large for this platform" +msgstr "размерът на обекта е прекалено голям за тази платформа" + msgid "offset value overflow for delta base object" msgstr "стойността на отместването за обекта-разлика води до препълване" @@ -9803,7 +9947,7 @@ msgstr "" "откриване на предшествениците за еднократно сливане на множество истории" msgid "list revs not reachable from others" -msgstr "извеждане на недостижимите от другите указатели" +msgstr "извеждане на недостижимите от другите версии" msgid "is the first one ancestor of the other?" msgstr "ПОДАВАНЕ_1 предшественик ли е на ПОДАВАНЕ_2?" @@ -10261,18 +10405,22 @@ msgstr "разрешаване на създаването на повече о msgid "" "git multi-pack-index [] write [--preferred-pack=]\n" " [--[no-]bitmap] [--[no-]incremental] [--[no-]stdin-packs]\n" -" [--refs-snapshot=]" +" [--refs-snapshot=] [--[no-]write-chain-file]\n" +" [--base=]" msgstr "" "git multi-pack-index [ОПЦИЯ…] write [--preferred-pack=ПАКЕТ]\n" " [--[no-]bitmap] [--[no-]incremental] [--[no-]stdin-packs]\n" -" [--refs-snapshot=ПЪТ]" +" [--refs-snapshot=ПЪТ] [--[no-]write-chain-file]\n" +" [--base=КОНТРОЛНА_СУМА]" msgid "" "git multi-pack-index [] compact [--[no-]incremental]\n" -" [--[no-]bitmap] " +" [--[no-]bitmap] [--base=] [--[no-]write-chain-file]\n" +" " msgstr "" "git multi-pack-index [ОПЦИЯ…] compact [--[no-]incremental]\n" -" [--[no-]bitmap] ОТ ДО" +" [--[no-]bitmap] [--base=КОНТРОЛНА_СУМА] [--[no-]write-chain-file]\n" +" ОТ ДО" msgid "git multi-pack-index [] verify" msgstr "git multi-pack-index [ОПЦИЯ…] verify" @@ -10301,9 +10449,18 @@ msgstr "" msgid "write multi-pack bitmap" msgstr "запазване на многопакетната битова маска" +msgid "checksum" +msgstr "сума за проверка" + +msgid "base MIDX for incremental writes" +msgstr "базов индекс за множество пакети за нарастващи записи" + msgid "write a new incremental MIDX" msgstr "запазване на нов файл с нарастващ индекс за множество пакети" +msgid "write the multi-pack-index chain file" +msgstr "записване на верижния файл с индекса за множество пакети" + msgid "write multi-pack index containing only given indexes" msgstr "" "запазване на битовата маска за множество пакети, съдържаща само дадените " @@ -10312,12 +10469,21 @@ msgstr "" msgid "refs snapshot for selecting bitmap commits" msgstr "снимка на указателите за избор на подавания по битова маска" +#, c-format +msgid "cannot use %s without %s" +msgstr "„%s“ изисква „%s“" + +msgid "cannot use --base without --no-write-chain-file" +msgstr "опцията „--base“ изисква „--no-write-chain-file“" + #, c-format msgid "could not find MIDX: %s" msgstr "липсва индекс за множество пакети: %s" msgid "MIDX compaction endpoints must be unique" -msgstr "крайните точки на индекс за множество пакети трябва да са уникални" +msgstr "" +"крайните точки при уплътняване на индекс за множество пакети трябва да са " +"уникални" #, c-format msgid "MIDX %s must be an ancestor of %s" @@ -10455,6 +10621,50 @@ msgstr "да се извеждат и недефинираните имена ( msgid "dereference tags in the input (internal use)" msgstr "извеждане на идентификаторите на обекти-етикети (за вътрешни нужди)" +#, c-format +msgid "'%s' needs to be either text, revs, or rev" +msgstr "" +"„%s“ трябва да е „text“ (текст), „rev“ (една версия) или „revs“ (повече от " +"една версия)" + +msgid "" +"(EXPERIMENTAL!) git format-rev --stdin-mode= --format= [--" +"[no-]notes=] [-z] [--[no-]null-output] [--[no-]null-input]" +msgstr "" +"(ЕКСПЕРИМЕНТАЛНО!) git format-rev --stdin-mode=РЕЖИМ --format=ФОРМАТ [--" +"[no-]notes=УКАЗАТЕЛ] [-z] [--[no-]null-output] [--[no-]null-input]" + +msgid "pretty format to use" +msgstr "ФОРМАТ, който да се ползва" + +msgid "stdin-mode" +msgstr "РЕЖИМ на стандартния вход" + +msgid "how revs are processed" +msgstr "как се обработват версиите" + +msgid "notes" +msgstr "бележки" + +msgid "display notes for pretty format" +msgstr "извеждане на бележките за ФОРМА̀ТА, който да се ползва" + +msgid "z" +msgstr "z" + +msgid "Use NUL for input and output termination" +msgstr "Входът и изходът да ползват нулевия знак „NUL“ за разделител" + +msgid "Use NUL for input termination" +msgstr "Входът да ползва нулевия знак „NUL“ за разделител" + +msgid "Use NUL for output termination" +msgstr "Изходът да ползва нулевия знак „NUL“ за разделител" + +#, c-format +msgid "'%s' is required" +msgstr "опцията „%s“ е задължителна" + msgid "git notes [--ref ] [list []]" msgstr "git notes [--ref УКАЗАТЕЛ_ЗА_БЕЛЕЖКА] [list [ОБЕКТ]]" @@ -10813,6 +11023,10 @@ msgstr "неправилна опция „--name-hash-version“: %d" msgid "currently, --write-bitmap-index requires --name-hash-version=1" msgstr "текущо опцията „--write-bitmap-index“ изисква „--name-hash-version=1“" +#, c-format +msgid "write_reuse_object: unable to parse object header of %s" +msgstr "write_reuse_object: заглавната част на „%s“ не може да се анализира" + #, c-format msgid "" "write_reuse_object: could not locate %s, expected at offset % in " @@ -10999,9 +11213,6 @@ msgstr "непакетираният обект в „%s“ не може да msgid "unable to force loose object" msgstr "оставането на обекта непакетиран не може да се наложи" -msgid "failed to pack objects via path-walk" -msgstr "неуспешно пакетиране на обекти чрез обхождане на дървото" - #, c-format msgid "not a rev '%s'" msgstr "„%s“ не е версия" @@ -11010,6 +11221,9 @@ msgstr "„%s“ не е версия" msgid "bad revision '%s'" msgstr "неправилна версия „%s“" +msgid "failed to pack objects via path-walk" +msgstr "неуспешно пакетиране на обекти чрез обхождане на дървото" + msgid "unable to add recent objects" msgstr "скорошните обекти не може да се добавят" @@ -11652,6 +11866,10 @@ msgstr "" msgid "invalid value for '%s'" msgstr "Неправилна стойност за „%s“" +#, c-format +msgid "could not push to %s" +msgstr "не може да се изтласка към „%s“" + msgid "repository" msgstr "хранилище" @@ -11728,6 +11946,14 @@ msgid "" "and then push using the remote name\n" "\n" " git push \n" +"\n" +"To push to multiple remotes at once, configure a remote group using\n" +"\n" +" git config remotes. \" \"\n" +"\n" +"and then push using the group name\n" +"\n" +" git push \n" msgstr "" "Не е указано хранилище, към което да се изтласква.\n" "Или укажете адреса на командния ред, или настройте отдалечено хранилище с " @@ -11738,6 +11964,18 @@ msgstr "" "и след това изтласкайте, като укажете името на отдалеченото хранилище:\n" "\n" " git push ИМЕ\n" +"\n" +"За изтласкване едновременно към множество отдалечени хранилища, настройте " +"група за изтласкване:\n" +"\n" +" git config remotes.ИМЕ_НА_ГРУПА \"ИМЕ_НА_ХРАНИЛИЩЕ…\"\n" +"\n" +"и след това изтласкайте чрез:\n" +"\n" +" git push ИМЕ_НА_ГРУПА\n" + +msgid "push options must not have new line characters" +msgstr "опциите за изтласкване не трябва да съдържат знак за нов ред" msgid "--all can't be combined with refspecs" msgstr "опцията „--all“ е несъвместима с указването на версия" @@ -11745,8 +11983,9 @@ msgstr "опцията „--all“ е несъвместима с указван msgid "--mirror can't be combined with refspecs" msgstr "опцията „--mirror“ е несъвместима с указването на версия" -msgid "push options must not have new line characters" -msgstr "опциите за изтласкване не трябва да съдържат знак за нов ред" +msgid "--atomic can only be used when pushing to one remote" +msgstr "" +"опцията „--atomic“ поддържа изтласкване само към едно отдалечено хранилище" msgid "git range-diff [] .. .." msgstr "git range-diff [ОПЦИЯ…] СТАРА_БАЗА..СТАР_ВРЪХ НОВА_БАЗА..НОВ_ВРЪХ" @@ -11764,9 +12003,6 @@ msgstr "неправилна стойност за максималната па msgid "use simple diff colors" msgstr "използване на прости цветове за разликите" -msgid "notes" -msgstr "бележки" - msgid "passed to 'git log'" msgstr "подава се на командата „git log“" @@ -12065,9 +12301,6 @@ msgid "(REMOVED) was: try to recreate merges instead of ignoring them" msgstr "" "(ОСТАРЯЛО) бе: опит за пресъздаване на сливанията вместо да се прескачат" -msgid "how to handle commits that become empty" -msgstr "как да се обработват оказалите се празни подавания" - msgid "keep commits which start empty" msgstr "запазванe на първоначално празните подавания" @@ -13009,11 +13242,11 @@ msgstr "повече подробности. Поставя се пред по msgid "" "git repack [-a] [-A] [-d] [-f] [-F] [-l] [-n] [-q] [-b] [-m]\n" "[--window=] [--depth=] [--threads=] [--keep-pack=]\n" -"[--write-midx] [--name-hash-version=] [--path-walk]" +"[--write-midx[=]] [--name-hash-version=] [--path-walk]" msgstr "" "git repack [-a] [-A] [-d] [-f] [-F] [-l] [-n] [-q] [-b] [-m]\n" "[--window=БРОЙ] [--depth=БРОЙ] [--threads=БРОЙ] [--keep-pack=ИМЕ_НА_ПАКЕТ]\n" -"[--write-midx] [--name-hash-version=ВЕРСИЯ] [--path-walk]" +"[--write-midx[=РЕЖИМ]] [--name-hash-version=ВЕРСИЯ] [--path-walk]" msgid "" "Incremental repacks are incompatible with bitmap indexes. Use\n" @@ -13023,6 +13256,10 @@ msgstr "" "Ползвайте опцията „--no-write-bitmap-index“ или изключете настройката\n" "„pack.writeBitmaps“." +#, c-format +msgid "unknown value for %s: %s" +msgstr "непозната стойност за „%s“: „%s“" + msgid "pack everything in a single pack" msgstr "пакетиране на всичко в пакет" @@ -13126,6 +13363,10 @@ msgstr "префикс на имената на пакетите за филтр msgid "cannot delete packs in a precious-objects repo" msgstr "пакетите в хранилище с важни обекти не може да се трият" +#, c-format +msgid "invalid value for %s: %d" +msgstr "неправилна стойност за „%s“: %d" + #, c-format msgid "option '%s' can only be used along with '%s'" msgstr "опцията „%s“ изисква „%s“" @@ -13332,11 +13573,11 @@ msgid "" msgstr "" "(ЕКСПЕРИМЕНТАЛНО!) git replay ([--contained] --onto=НОВА_БАЗА | --" "advance=КЛОН | --revert=КЛОН)\n" -"[--ref=УКАЗАТЕЛ] [--ref-action[=РЕЖИМ]] ДИАПАЗОН_ПОДАВАНИЯ" +"[--ref=УКАЗАТЕЛ] [--ref-action[=РЕЖИМ]] ДИАПАЗОН_НА_ВЕРСИИТЕ" msgid "update all branches that point at commits in " msgstr "" -"обновяване на всички кло̀ни, които сочат към подавания в ДИАПАЗОНа_ПОДАВАНИЯ" +"обновяване на всички кло̀ни, които сочат към подавания в ДИАПАЗОНа_НА_ВЕРСИИТЕ" msgid "replay onto given commit" msgstr "прилагането наново върху даденото ПОДАВАНЕ" @@ -14011,8 +14252,8 @@ msgstr "такъв указател няма: %s" #, c-format msgid "cannot handle more than %d rev." msgid_plural "cannot handle more than %d revs." -msgstr[0] "не може да се обработи повече от %d указател." -msgstr[1] "не може да се обработят повече от %d указатели." +msgstr[0] "не може да се обработи повече от %d версия." +msgstr[1] "не може да се обработят повече от %d версии." #, c-format msgid "'%s' is not a valid ref." @@ -14037,9 +14278,6 @@ msgstr "заглавната част не може да се прочете" msgid "unknown index version" msgstr "неподдържана версия на индекса" -msgid "unable to read index" -msgstr "индексът не може да се прочете" - msgid "corrupt index file" msgstr "повреден файл за индекса" @@ -14317,8 +14555,12 @@ msgstr "git stash drop [-q|--quiet] [СКАТАНО]" msgid "git stash pop [--index] [-q | --quiet] []" msgstr "git stash pop [--index] [-q|--quiet] [СКАТАНО]" -msgid "git stash apply [--index] [-q | --quiet] []" -msgstr "git stash apply [--index] [-q|--quiet] [СКАТАНО]" +msgid "" +"git stash apply [--index] [-q | --quiet] [--label-ours=